This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
Every git repository is a graveyard, and nobody holds the ceremony.
Somewhere in your history there's a pricing.html that shipped before the
product existed, an app_v2.py from the rewrite that never finished, a
stylesheet in hotpink you were proud of for six hours. git log will tell
you when they died. It will never tell you what they meant — so they rot,
unmourned, while you scroll past their filenames looking for something else.
ofrenda is the ceremony. It's a Día de los Muertos altar for your code:
$ ofrenda examples/graveyard-repo
ofrenda :: 4 departed :: model gemma3:4b
[1/4] app_v2.py
“Served 'eventually'. Rest now.”
[2/4] index.html
“A valiant start, now dust.”
[3/4] pricing.html
“A valiant, if misguided, attempt.”
4 remembered, 0 unspoken
ofrenda lit → …/ofrenda.html
The tool walks your history for every deleted file and digs up its whole
life: the commit that created it, the people who touched it, how many
commits it survived — and, on the eve of its death, the literal content it
was still carrying. Then it hands that record to a model running on your own machine and asks it to say a few words. What comes back is one
I interpreted "Ghost in the Machine" this way: the ghosts aren't monsters,
they're the dead we didn't memorialize, and the machine finally gets to
speak for them. Halloween gives you the graveyard; Día de los Muertos tells
you what to do there — remember deliberately, warmly, and with candles.
Who this is for: anyone who has ever rewritten a project "from scratch" and
later wondered what the old version actually did; maintainers doing cleanups
who want a moment of gratitude before the rm; teams onboarding someone who
needs to understand not just what exists, but what was tried and abandoned.
No deployment needed — it's your own machine, and the page it prints is a
single file you can open with the Wi-Fi off.
git clone https://github.com/itzneel05/ofrenda && cd ofrenda
ollama pull gemma3:4b
pip install -e .
python examples/make_demo_repo.py # grows a small graveyard to mourn
ofrenda examples/graveyard-repo # writes ofrenda.html
Real captured output — four dead files, four eulogies, straight from
gemma3:4b:
ofrenda :: 4 departed :: model gemma3:4b
[1/4] app_v2.py
“Served 'eventually'. Rest now.”
[2/4] index.html
“A valiant start, now dust.”
[3/4] style.css
“A vibrant blush, now gone.”
[4/4] pricing.html
“A valiant, if misguided, attempt.”
4 remembered, 0 unspoken
ofrenda lit → C:\...\ofrenda.html
And what the model wrote for pricing.html, verbatim from --json:
{
"path": "pricing.html",
"epitaph": "A valiant, if misguided, attempt.",
"story": "Launched with a bold promise of zero cost, pricing.html bravely represented the nascent product — a testament to early enthusiasm and, frankly, a bit of optimism. Its short existence ended abruptly when the Demo Author deemed its message 'sad'.",
"cause_of_death": "deleted",
"beloved": ["Zero cost", "Early optimism", "A brief, memorable moment"]
}
Every claim in that story is grounded in evidence the tool actually fed it:
the file's content (<h1>Only $0 — because nothing works yet</h1>), the
commit that killed it (fix: pricing page was making everyone sad), and
its two-commit lifespan.
Other flags: --limit 10 (ten newest deaths), --model gemma3:1b
(smaller machines), --json (for scripting), --out altar.html.
https://github.com/itzneel05/ofrenda
ofrenda/gitdead.py — the archaeology: git log --diff-filter=D for
deaths, --follow for birth/lifespan/contributors, git show <death>^:<path>
for the content at death. Parses git's raw record separators byte-carefully
(a fun bug: Python's splitlines() treats \x1e as a line break, which
silently swallowed every finding until a test caught it).ofrenda/eulogy.py — prompt built from the evidence, plus a tolerant JSON
parser for whatever the model decides to wrap it in (fences, chatty
prefixes, trailing "Hope that helps!").ofrenda/client.py — stdlib Ollama client. No base-URL flag, no API key:
127.0.0.1:11434 and nowhere else.ofrenda/render.py — the altar itself: one HTML file, zero scripts, zero
CDNs, everything escaped.tests/ — 24 tests,
flowchart TD
A["git log --diff-filter=D<br/>which files died, and who killed them"] --> C["life record per file"]
B["git log --follow + git show death^:path<br/>birth, lifespan, contributors, final content"] --> C
C --> D["prompt built from the evidence"]
D --> E["gemma3:4b on 127.0.0.1:11434<br/>(local, via Ollama)"]
E --> F["epitaph · story · cause of death · beloved[]"]
F --> G["HTML-escaped into one self-contained<br/>candle-lit ofrenda page"]
Zero runtime dependencies. Python 3.9+, git, Ollama, done.
Built with AI assistance in OpenCode, with Google's open-weight Gemma 3 running locally through Ollama.
Because the code that gets deleted is often the code you'd least want to
upload. Personal projects with your real name in the history. A client's
proprietary logic you still have an NDA about. The half-finished side
business. A repository that contains a layoff's worth of commit messages.
Asking a cloud API to read every deleted file in your history — and write
about your team while it's at it — is a question you'd never get a yes to,
which is exactly why the tool would be useless if it asked.
With an open-weight model on localhost:
--model gemma3:1b for speed, fine-tune
Gemma on I didn't expect this part to land. The demo repo is synthetic — six commits
I wrote ten minutes earlier — and the model still produced, for a
two-line file named app_v2.py that returned the string 'eventually':
"Served 'eventually'. Rest now."
That's a two-line function getting a headstone. I ran it again and got a
different epitaph, same feeling. The point of Día de los Muertos was never
accuracy about the dead — it was the remembering. A tool that makes you stop
and read what your cleanup commit actually destroyed, for eight seconds
per file, on your own machine, is a small machine for exactly that.
gemma3:4b (and gemma3:1b) via Ollama is the
sole author: every epitaph, story, cause of death and beloved-trait list
comes from the model. There is deliberately no template fallback — if the
model is missing, the tool stops, because a mad-libs condolence would
miss the entire point.