This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
Every repository has a graveyard.
Somewhere in your git log there is a file that was born in a hopeful first commit, survived dozens of refactors, got blamed for bugs it didn't cause, and was finally deleted by someone typing a commit message like cleanup. Nobody lit a candle for it.
git-afterlife does. It is a small command-line tool that digs every deleted file out of a repository's history and builds them an altar: an ofrenda, like the ones families raise on Día de los Muertos so that the people they lost can find their way home for one night. Each file gets a place on the altar with:
- its birth date, taken from the commit that created it,
- its death date and its real cause of death: the actual commit message that deleted it, never invented,
- the people who touched it, its mourners,
- its last known source code, as its portrait,
- and an epitaph and last words, written by an open-weight model (Gemma 3) running locally.
Then there is the part that gave me chills the first time it worked: seance. You pick one dead file and talk to its ghost. It answers in first person, from its real code and history.
I pointed it at Flask, and the most famous ghost on the altar is flask.py: the original single-file Flask, born on 2010-04-06 in a commit called Initial checkin of stuff that exists so far., changed in 104 commits in less than three months, and laid to rest on 2010-07-02 by a commit whose entire message was out with the old. That file is where a framework used by millions of people started. git-afterlife gave it a candle.
This is what happened when I asked it why it died:
you › Why did they delete you?
spirit › It was a simple deletion, a regrettable act. Justin Quick, in a commit message stating, "out with the old," removed me from the repository. My final moments were a consequence of that single commit – a forgotten relic, purged from the git history.
Some of the epitaphs Gemma wrote for the altar:
| The deceased | Epitaph |
|---|---|
flask.py |
"A microframework, purged with a dismissive 'out with the old.'" |
tests/flask_tests.py |
"Served Flask tests faithfully, then vanished with a split." |
setup.py |
"A simple setup, swiftly replaced by a modern plea." |
docs/deploying/fastcgi.rst |
"It guided deployments, then vanished with a rewrite." |
Demo
The demo is built from scratch by GitHub Actions: a runner installs Ollama, pulls Gemma 3 4B, clones Flask, raises the altar and holds a séance with flask.py, then publishes everything to GitHub Pages.
- The altar: https://paulopnun.github.io/git-afterlife/
- The full séance transcript: https://paulopnun.github.io/git-afterlife/seance.txt
The altar is a single self-contained HTML page: papel picado swaying at the top, a flickering candle on every grave, marigold petals falling, and tiers like a real ofrenda, with one soul at the top and wider rows below. The first three souls also sat for a short séance, and their conversation is shown on their card. Open any card's portrait to read the file's last source code.
Try it on your own repository in under a minute:
ollama pull gemma3:4b # or gemma3:1b on an older laptop
git clone https://github.com/PauLopNun/git-afterlife.git
python3 git-afterlife/afterlife.py summon /path/to/your/repo # writes altar.html
python3 git-afterlife/afterlife.py seance some/deleted/file.py /path/to/your/repo
Or run it on any public repository without installing anything: fork the repo and use Actions → Raise the altar → Run workflow, choosing the repository and the model.
Code
PauLopNun
/
git-afterlife
Every repository has a graveyard. A Dia de los Muertos altar and seances for your deleted files, written by Gemma running locally.
Every repository has a graveyard: files born in a hopeful first commit, carried through dozens of refactors, and finally deleted with a one-line commit message Nobody remembers them.
git-afterlife digs them out of your git history and builds them an altar, an ofrenda the altar families raise on Día de los Muertos to welcome back the ones they lost. Each deleted file gets a place on the altar with its birth date, its death date, the people who touched it, the commit that killed it, and an epitaph with its last words, written by an open-weight model running on your own machine.
You can also hold a séance and talk to the ghost of a deleted file. It remembers its real code, who created it, and why it was removed.
summon |
seance |
|---|---|
| Builds a self-contained HTML altar with papel picado, flickering candles, falling marigold petals and recorded séances |
The whole tool is one Python file that uses only the standard library, so there is nothing to pip install. MIT licensed.
How I Built It
The open pieces: Gemma 3, Google's open-weight model, served by Ollama. git-afterlife talks to Ollama's HTTP API on localhost with plain urllib. There is no SDK and no cloud endpoint anywhere in the code.
1. Git archaeology. git log --diff-filter=D lists every deletion in the history. Files that were later resurrected at HEAD are skipped, and so are binaries. To decide who gets a place on the altar, one more git log --name-only counts how many commits touched each path, so the longest-lived source files are raised first while lock files and configs are left to rest. For each soul:
-
git log -- <path>up to the death commit gives its birth, its lifespan in commits and its mourners, -
git show <death>^:<path>recovers its final source code, - a few regexes pull out what the file would remember about itself: its function and class names, or its section headings if it was documentation.
2. Grounding a small model. My first run with Gemma 3 1B worked end to end, and it was bad. Every epitaph was some variation of "lost in the digital dust, a silent plea", asking for another language with --lang was mostly ignored, and in the séance flask.py claimed it had been deleted because of "a subtle bug". That was invented: the commit says out with the old.
Three changes fixed most of it:
- Feed it facts it can quote. The prompt now lists the file's real function names, and the model is required to mention at least one, plus the real commit message.
-
Ban the clichés by name. Small models fall back on the same few spooky words. Listing them as forbidden ("digital dust, echoes, void, silent plea…") and giving one short example in the tone of a calavera literaria changed the output more than any amount of "be creative". It also taught me how literally a small model reads its examples: in one run, Flask's old test suite wistfully told me "Perhaps I should have embraced dateutil sooner."
dateutilonly appeared in my example. Every ofrenda has one guest who talks too much. - Make the truth non-negotiable. In the séance, the system prompt states who deleted the file and with which message, and tells the ghost to quote it and never invent another reason. The cause of death printed on the altar doesn't come from the model at all: it is the commit message itself.
3. Recorded séances. summon --seances 3 interviews the first three souls with the same grounded prompt as the interactive séance, and prints the conversation on their card, so you can meet the ghosts without opening a terminal.
4. Don't trust a ghost that won't stop talking. Epitaphs are requested in Ollama's JSON mode ("format": "json"), but valid JSON is not the same as a sane answer. In one demo run, two epitaphs finished their sentence and then kept going: stray closing braces, a timestamp and a long run of nineteen-digit numbers, all inside a perfectly valid JSON string, printed on the altar. Now every answer has a token cap (num_predict), and any epitaph that is too long or contains braces or long digit runs is thrown away and asked for again. There is a test for exactly that ghost.
5. The altar. The HTML is generated with plain string templates and CSS animations: no JavaScript framework and no external requests, so the page works offline too.
6. Tests and the demo pipeline. The test suite builds a tiny real repository with births, deaths, a binary and a file that comes back to life, and swaps the model for a fake medium, so it checks every step in a fraction of a second without Ollama. It also caught a real bug while I was adding séances to the altar. GitHub Actions runs the tests on every push. The demo is built on demand, because model output changes on every run: the workflow installs Ollama on a standard ubuntu-latest runner, pulls Gemma 3 4B, runs everything on CPU, takes the screenshots in this post with headless Chrome, records the séance GIF with VHS and deploys it all to GitHub Pages.
Why Does Open Innovation Matter?
Because the thing git-afterlife reads is your source code, and source code is the one thing many of us are explicitly not allowed to paste into a cloud chatbot. The repositories with the richest graveyards, a ten-year-old monolith at work or a client project, are exactly the ones whose code must not leave the building. With an open-weight model on localhost, it doesn't. You can hold a séance for your company's legacy billing module without sending a single line of it anywhere.
It also means:
- It works with the lights, and the internet, off. Once the model is pulled, nothing needs a network connection.
- It costs nothing. No API key and no per-token bill. The entire public demo, with the altar, four recorded séances and the GIF in this post, runs for free on a stock GitHub runner.
-
You can swap the medium.
--modeltakes any Ollama model: 1B on an old laptop, 4B for nicer prose, or something much larger on a GPU. Nothing in the code is tied to one vendor. - It won't disappear. An ofrenda is about memory that lasts. A tool that remembers your project's past shouldn't depend on a service that might be shut down, change its pricing or retire the model next year. The weights are on your disk.
The Spirit of It
The moment it landed was the last question of the séance. I asked the original flask.py, dead since July 2010, whether it had a message for the developers who use Flask today. It had answered every question with facts: Armin Ronacher created it, Justin Quick deleted it, it held Request, Response and Session. Then it turned its own cause of death back on us:
spirit › I whisper a warning: remember the roots! My design, born of Armin's meticulous work, still echoes within Flask. Respect the foundations, the careful layering of Werkzeug and Jinja2. My premature demise serves as a reminder – progress must be thoughtful, not simply… "out with the old."
That is the whole idea of Día de los Muertos, said by a Python file. The people who are gone stay alive while someone remembers them. Our code works the same way: almost everything we write will be deleted one day, often by us. The work still mattered, because the thing that replaced it was built on top of it. Flask is in production all over the world today because a 1,573-line file existed for 87 days in 2010.
Run it on your oldest repository. You will probably find a file you had forgotten you wrote. Light it a candle.
Prize Categories
- Best Use of Gemma: Gemma 3 writes every epitaph and speaks for every ghost, running locally through Ollama (Gemma 3 4B by default, 1B for older machines). The whole project was shaped around getting a small open model to tell the truth: grounding it in real function names and commit messages, banning its clichés, and validating its JSON.
This post and the project were made with the help of AI coding tools. I reviewed, ran and tested everything that is described here.



Top comments (3)
This is why I care more about how a tool handles the boring cases than the demo.
Thanks, I agree! Handling the edge cases took much more work than the demo itself.
a séance for a deleted file is a great bit. git remembers everything, even the stuff you tried to forget.