This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
Every codebase is haunted.
There's a time.sleep(3) nobody dares delete. A retry loop set to seven for a reason nobody remembers. A constant changed by a commit titled "update". The people who knew why have moved on, and git blame only tells you who and when. Never why.
So I built Poltergit, a séance for your git history. You point at a line of code, and the line answers in its own voice, citing the commits and discussions behind it. Every ghost brings receipts. And when the record has nothing to say, it says so.
What I Built
Poltergit is an open-source tool that runs entirely on your laptop, as a command line and a small local web "séance room".
-
Summon a line.
poltergit why src/client.py:13walks that line's history withgit log -L: every commit that touched it, with its diff and message. If you opt in, it also pulls the GitHub pull requests, reviews and issues around those commits. - The line speaks. Gemma 4, running locally, answers in the first person, as the code and never as the person who wrote it. Every claim carries a citation and an exact quote.
- The receipts are checked. Before you see an answer, every quote is matched word for word against its source. If one doesn't match, the whole answer is thrown away and you get the labelled source excerpts instead.
- Silence is an answer. If the history doesn't explain the line, Poltergit says the record is silent instead of guessing.
- Hear the spirit. Kokoro, an open text-to-speech model, reads the answer aloud with a quiet séance-room echo.
-
An ofrenda for deleted code.
poltergit ofrendabuilds a Día de los Muertos memorial page. Every deleted file and Python function gets an obituary: when it arrived, how long it served, and the commit that retired it.
How I read the theme: the ghosts in the machine are the reasons behind code, still there long after the people who wrote them have left. The séance is the spooky half: a voice from the other side, answering questions. The ofrenda is the honouring half: it remembers what code did before it was removed.
Demo
The 44-second video walks through a séance: Gemma's answer, the commit behind it, the answer spoken aloud, and the ofrenda. The wait for CPU inference is cut, and the video says so.
The demo repository is fictional and clearly labelled. poltergit demo creates it with six real commits, so anyone can reproduce every answer below. Here is line 13, time.sleep(3):
"My existence is tied to preventing a reconnect storm, as a three-second delay gives that reservation time to expire." [1]
Source [1] is the commit "fix: wait three seconds before reconnecting", whose message explains that the gateway keeps a closed session reserved for two seconds. The footer shows what the answer cost: 1,503 tokens, $0 in inference, and every quote matched.
Line 5, MAGIC_NUMBER = 43, was changed by a commit titled "update". The honest answer is the record is silent, and that's what Poltergit says. Once, in live testing, Gemma tried to give a reason anyway, with a citation that didn't match its source. The check caught it, threw the answer away, and showed the commit text instead, clearly labelled.
Code
CodeNeuron58
/
Poltergit
A séance for your git history. Every ghost brings receipts.
The code is MIT-licensed Python. CI runs the tests on Ubuntu and Windows, including a pgvector integration test against a real Postgres database.
How I Built It
Everything that thinks runs on my laptop through Ollama. There's no API key and no cloud model.
1. The history walk: git
git log -L follows one line through every commit that changed it, even when its line number moved. Poltergit reads the committed HEAD only, warns you about uncommitted changes, and trims the prompt without dropping the line's oldest history.
2. The voice: Gemma 4 E4B
Gemma gets the line, the code around it and its history, and must answer in structured JSON: a list of claims, each with a source ID and an exact quote, or silent: true. Two rules are enforced in code, not just requested in the prompt:
- every quote must be at least 12 characters long and appear word for word in its source;
- the answer must speak as the code.
Break either rule and the answer is discarded. Matching quotes can't prove the reasoning is right, but it guarantees that every sentence you see points at words that really exist in the record.
3. The memory: Tiger Data, pgvector and EmbeddingGemma
Line history alone misses things. The retry loop on line 9 was only ever touched by the commit that introduced the client, so on its own the séance has nothing to say. The real reason lives in a different commit: the one that raised MAX_RETRIES from 3 to 7.
So poltergit index embeds the repository's history with EmbeddingGemma (768 dimensions, also local) and stores it in Postgres with pgvector on a free Tiger Cloud service. A single SQL query runs two searches and merges them with Reciprocal Rank Fusion:
- vector similarity over the embeddings, using an HNSW cosine index;
- keyword ranking over Postgres full-text search.
Every search is scoped to one repository, revision and embedding model, so histories never mix.
Line 9: for attempt in range(MAX_RETRIES):
|
What the line says |
|---|---|
| Line history only | The record is silent |
| With hybrid search on Tiger Data | "I exist to tolerate the gateway maintenance window, as seven attempts cover the gateway's eighteen-second maintenance window…", with a citation |
4. The receipts for the receipts: Sentry Agent Tracing
Every séance is a Sentry transaction (gen_ai.invoke_agent, agent name "Poltergit") with a span for each step. Prompts and source code are never sent: send_default_pii is off, so Sentry sees timings, the model and token counts, not my code.
One trace tells the whole story. Walking the history took 677 ms and checking the quotes took 0.12 ms. Gemma took 2.89 minutes on my CPU-only laptop, for 728 tokens in and 834 out. Inference cost: $0.
How the traces helped me debug:
- A timeout that looked like a broken model. My first cold run failed with a model error at 181 seconds. The trace showed the model was working, just slowly (6 to 11 tokens a second without a GPU), and my 180-second timeout was the real problem. It's now configurable, and the setup guide says to raise it on CPU-only machines.
- Missing AI spans. A test of the exact data sent to Sentry showed the AI child spans weren't inside the transaction, because the SDK streams them separately by default. I turned that off, so each séance arrives as one complete trace.
-
Traces that never arrived. The command-line tool exited before the SDK sent anything. I'd disabled its default integrations, including the one that flushes on exit. One explicit
flush()before exiting fixed it.
5. The rest
- Kokoro, an open 82M-parameter text-to-speech model run through ONNX, reads the answer slightly slowed, with quiet delayed reflections for the séance-room echo.
- FastAPI, Typer and Jinja2 power the local séance room, the CLI and the static ofrenda export. The server only listens on localhost and uses anti-CSRF tokens.
- The ofrenda's birth dates are real. Function obituaries come from comparing Python syntax trees across up to 200 revisions of a file, so each function's birth date is when it actually appeared.
Why Does Open Innovation Matter?
- Your history stays yours. A repository's history is a company's diary: messages, reviews, internal reasons. Gemma, EmbeddingGemma and Kokoro all run on the laptop, so no closed AI API ever reads it. The only things that leave are what you choose: embeddings in your own database, and traces with no content in them.
- The safety net needs the whole loop. Checking every quote word for word only works because I control everything: the prompt, the structured output, and what happens when an answer fails. I could test that the net holds, and it did, catching a made-up citation in live testing.
- It costs nothing to ask. Every séance is $0 in inference. A tool you consult on every confusing line in a legacy codebase can't charge per question.
- The past should outlive the vendor. A tool for reading history shouldn't depend on an API that can be retired. The open weights on my disk will still be there in ten years.
The Spirit of It
The moment it landed wasn't on my demo repository. It was on request, the npm library that was deprecated in 2020.
I asked line 3 of its README why it exists. That's the line that reads "As of Feb 11th 2020, request is fully deprecated." Poltergit found the commit "doc: note full deprecation (#3267)" and the pull request behind it, whose entire description is two words: "It's time." There wasn't enough written down for Gemma to say more, so the séance showed me the excerpts, labelled, and fell silent. That felt right.
Then I asked line 49 of its package.json, the uuid dependency. Its history holds a 2016 commit: "using defunctzombie fork of uuid because node-uuid has been abandoned by developer". Gemma answered by citing that commit. A deprecated library, still carrying a line that only exists because another library was abandoned before it. That's exactly the kind of ghost I built this for.
Prize Categories
- Best Use of Gemma: Gemma 4 E4B writes every answer locally through Ollama, as structured JSON that's checked quote by quote, and EmbeddingGemma creates the 768-dimension vectors behind the hybrid search. Two Gemma models, both local, $0 in inference.
- Best Use of Tiger Data: the repository's history is embedded into pgvector on a free Tiger Cloud service, and one SQL query merges vector and keyword search with Reciprocal Rank Fusion, scoped by repository, revision and model. It turns line 9 from "the record is silent" into a cited answer.
-
Best Use of Sentry Agent Tracing: every séance is a
gen_ai.invoke_agenttransaction with spans for the history walk, the Gemma call (model, tokens, latency) and quote verification, and no prompts or code are sent. The traces found a timeout, missing AI spans and a bug where traces were lost on exit.
Thanks for reading. Which line in your codebase would you summon first? 🕯️



Top comments (1)
The valuable question is not whether a tool can narrate a line’s history, but whether a reviewer can inspect the basis for that narration. Linking each explanation to commits, diffs, issues, and surviving tests would let the story be useful without granting it authority it has not earned.