Every codebase has them: files that nobody has touched in months, quietly accumulating in the repository like sediment. That authentication helper from the old API. The utility function someone wrote for a migration that finished six months ago. The config file for a service you decommissioned. They're not hurting anything, until they are. Someone new joins the team, sees `auth-old.js`, and spends an hour trying to understand if it's still used.
zombiefiles is a dead-simple CLI that scans your git history and finds these forgotten files. It lists every git-tracked file via `git ls-files`, checks the last commit date for each one via `git log`, filters by a staleness threshold (default: 3 months), and sorts the results oldest-first. That's it. No configuration files, no plugins, no daemon process.
The output is deliberately minimal: filename, how long it's been untouched. Run `npx zombiefiles` in any git repo and you immediately see the dead weight. Adjust the threshold with `--months 6` if 3 months is too aggressive for your codebase.
The philosophy here is that the best developer tools are the ones with the smallest API surface. zombiefiles does one thing, finds stale files, and does it in a single command with zero setup. If you need more sophisticated dead code detection, use a proper static analysis tool. But for the 80% case of "what files has nobody touched in forever," this is all you need.
Built with
Links
Features
- Zero-config scanning, run npx zombiefiles in any git repo, no setup required
- Git-native detection, uses git ls-files and git log, so it respects .gitignore automatically
- Configurable staleness threshold, default 3 months, adjustable with --months flag
- Sorted by staleness, oldest zombies surface first, so you see the worst offenders immediately
- Cross-repo scanning, point it at any directory with --dir flag
Challenges
The main engineering challenge was performance. Running `git log` for every single file in a large repo is O(n) git operations, which can be slow for repos with thousands of files. The solution was to batch the git log calls and parallelize where possible, but there's a fundamental tradeoff between accuracy (checking each file individually) and speed (bulk operations that might miss edge cases). For most repos under 10k files, the current approach is fast enough.
What I learned
zombiefiles is a case study in the value of constraint. The entire tool is under 200 lines of TypeScript. No dependencies beyond Commander.js for argument parsing. No config files. No plugins. The temptation to add features, "what if it could also detect unused exports?" "what if it integrated with your CI?", is real, but resisting it is what keeps the tool useful. The best CLI tools are the ones you can explain in one sentence.