This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
Every abandoned repository is a ghost. Someone's decisions are still in there, along with their abandoned ideas and a last commit that reads like a final breath, but nobody can talk to it.
Necropsy performs an autopsy on a dead Git repository and then lets you hold a séance with it.
- The autopsy finds the time of death, the last-gasp commit, the activity curve, the bus factor, and the project's "eras". Its graveyard lists tombstones for deleted files and functions, each carrying the deleting commit's message as its epitaph.
- The séance is a chat with the project's ghost. You drag a time slider to any date in the project's life, and the ghost knows only what existed on that date. Every claim it makes cites real commits, and a verifier removes any claim it can't back up. When the evidence isn't there, it answers: "The dead do not remember that."
I took "Ghost in the Machine" literally: a ghost made of a machine's own memory. A repository is a recorded history of someone's mind, and I wanted to make it answerable without letting it make things up.
Code
🪦 Necropsy
Hold a séance with any dead repository.
Necropsy performs an autopsy on an abandoned Git repository (when it died, how it declined, what code was lost) and opens a séance: a chat with the project's ghost, which can only answer from what existed on the date you pick. Every claim cites real commits, and anything that can't be verified is removed.
Everything runs locally on open-weight models. After the first clone, it works with the network unplugged.
Built for the Hacktoberfest "Ghost in the Machine" open-source AI challenge.
Status: in development. Items under Roadmap show what is done. Commands in this README describe the planned interface; adjust them to the final
Makefile.
Contents
The most important idea in the project fits in two lines:
n = bisect_right(item_dates, as_of_end) # commit dates sorted ascending
scores = vectors[:n] @ query_vec # later commits are not in this slice
Commit embeddings are stored sorted by date, so "everything up to the chosen date" is a prefix of the list. Commits after that date are never scored, so they can't influence which evidence comes back. Filtering after ranking would let the future leak into the past.
How I Built It
Open-source AI in the core:
-
Ghost model: an open-weight Qwen3 model ([EDIT: confirm the exact tag you ran, e.g.
qwen3:8b, and your fallback]) served locally by Ollama. It writes the ghost's answers as structured JSON: an answer plus a list of claims, each with commit hashes. -
Embeddings:
BAAI/bge-small-en-v1.5runs in-process through sentence-transformers. It powers retrieval, the verifier's support check, and the clustering behind the project's "eras". It's small enough to run on a CPU. - Everything else is ordinary, auditable code: PyDriller reads the Git history, tree-sitter finds deleted functions, SQLite stores it all in one file, scikit-learn and UMAP cluster the eras, FastAPI serves the API, and a static HTML/JS frontend with D3 draws the charts and the time slider. There's no framework build step and no CDN.
Why it's more than a chatbot over a repo:
- The time axis is the product. The date filter is structural (the prefix slice above), and a mandatory automated test builds a fixture repo with known dates and checks that nothing newer than the chosen date reaches retrieval, the prompt, the graveyard, or the statistics.
- The ghost has to show its evidence. The verifier is plain code, not another model. It checks that each cited commit exists, is dated before the chosen date, was in the evidence the model was shown, and is semantically close to the claim. Small local models cite things that don't exist, so unsupported claims are deleted.
- Numbers never come from the LLM. Time of death, decline onset and bus factor are formulas over commit data, so they're reproducible. The model explains them but never computes them.
- Eras are display-only. Clusters are computed from the whole history, so they depend on the future. Their labels never enter the ghost's context.
- The ghost speaks as the project, not as a person. It quotes real commit messages verbatim and never impersonates a named maintainer.
[EDIT: describe what actually works at submission time, and list anything still planned. Judges can tell.]
Why Does Open Innovation Matter?
Necropsy exists because of what a closed API makes impossible.
- Dead private repos. The projects most worth autopsying are often old company code or unreleased side projects. They can't be pasted into a hosted service. With local inference they never leave the machine.
- It costs nothing to run. Indexing thousands of commits and asking unlimited questions costs electricity, not API credits. An autopsy that you'd think twice about running if it cost per query becomes something you run on every repo.
- Offline. After the initial clone and one-time model downloads, you can unplug the network and keep working.
- Reproducibility and preservation. This one is personal to the theme. A tool about preserving history shouldn't depend on a model that gets quietly updated or retired. Pinned open weights mean an autopsy produced today can be reproduced years from now.
- The model is a replaceable part. Swapping an 8B model for a 4B one is a one-line config change. [EDIT: if your eval harness ran, add one real sentence on what the comparison showed. Don't invent numbers.]
A hosted model would give me a nicer answer on a good day. It couldn't give me privacy, an offline demo, or an answer that can be reproduced next year.
My Agent Session
I gave the agent the product, technical and architecture documents and an AGENTS.md of non-negotiables, such as the time-travel invariant, verified citations, and no closed APIs, so it built against a spec instead of improvising. [EDIT: confirm this is what you did.]
The Spirit of It
I pointed Necropsy at [your abandoned project], dragged the slider back to [month/year], and asked [your question]. It answered [what it said], and cited [the commit]. I had forgotten [what you'd forgotten].
Top comments (0)