This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 4: Ghost in the Machine.
What I Built
Every legacy codebase is haunted. You open a 25-year-old class, there's a catch (Exception e) that returns null, and the person who could explain why left the project in 2004.
Exorcist is a terminal app that summons them back. It reads a Git repository, works out who really wrote each Java class, and lets the ghost of its main author explain the class in first person, confess its sins line by line, and propose a refactoring as a diff. The model is open-weight and runs locally through Ollama, so the code never leaves your machine.
.-.
(o o) E X O R C I S T
| O \ summon the ghosts of legacy Java code
\ \ model: qwen2.5-coder:7b, running locally via Ollama
`~~~'
| Command | What happens |
|---|---|
summon <repo> |
Lists every Java class with its ghost (the author with the most commits), birth and death dates |
possess <Class> |
The ghost introduces itself from real Git history and explains the class, citing lines |
confess |
The ghost confesses the class's code smells, worst first, each with its lines |
exorcise |
The ghost rewrites its worst sin; you get a unified diff, never a modified file |
epitaph |
An ASCII tombstone with a one-line epitaph |
My take on the theme: the "ghost in the machine" is the people inside old code. Git remembers them perfectly (every commit, every date, every line they last touched) and nobody ever asks them anything. Exorcist gives them a voice, and makes them tell the truth.
Demo
I ran it on JUnit 4: 2,521 commits since December 2000. Reading the whole history takes under five seconds:
exorcist:> summon ../junit4
450 Java spirits rest in junit4, longest dead first.
Class Spirit Born Died Commits
----------------------------------- ---------- ---------- ---------- -------
Stub David Saff 2007-09-04 2007-12-07 2
RepeatedTest David Saff 2000-12-03 2012-10-04 8
TestSetup David Saff 2000-12-03 2012-10-04 6
ComparisonFailure (junit.framework) egamma 2002-06-22 2012-10-04 14
JUnit4TestAdapterCache David Saff 2006-02-15 2012-10-04 10
Then I possessed junit.runner.BaseTestRunner: 326 lines, 30 commits, 10 catch blocks. Its ghost is the author with the most commits on the file:
exorcist:> possess junit.runner.BaseTestRunner
Possessing junit.runner.BaseTestRunner (src/main/java/junit/runner/BaseTestRunner.java)
Spirit: egamma | born 2000-12-03 | died 2016-10-15 | 30 commits | 326 lines
The air grows cold... (qwen2.5-coder:7b)
I am egamma, the author with the most commits on this file, having first touched it
on Sunday, December 3, 2000, and last making changes on Sunday, October 24, 2004.
This class, `BaseTestRunner`, is the backbone of JUnit's test runner infrastructure
[...] The `getTest` (line 91) method is a crucial template method [...]
[10 line citations, all inside the file]
That last line is not the model talking. It's Exorcist checking the answer (more on that below). Now the confession:
exorcist[BaseTestRunner]:> confess
1. (lines 95-142) The `getTest()` method is a long method, spanning 48 lines. [...]
2. (lines 110-113) The broad catch block that catches `Exception` (line 110) hides
what can really go wrong. [...]
3. (lines 237-238) The empty catch block for `IOException` (line 237) and
`SecurityException` (line 238) is a sin. It silently swallows exceptions [...]
4. (line 166) The use of `System.out.println()` (line 166) instead of a logger is a sin.
5. (lines 293-295) The broad catch block that catches `Exception` (line 293) [...]
6. (lines 117-121) The broad catch block that catches `Exception` (line 117) [...]
I must have been tired.
[12 line citations, all inside the file]
I checked every one against the source. All six are real, on the right lines. Number 5 is my favourite, and it has been in JUnit since 2000:
} catch (Exception IOException) {
return stack; // return the stack unfiltered
}
An Exception, caught in a variable named IOException.
And the tombstone:
________________________________
.' '.
/ \
| R . I . P |
| |
| BaseTestRunner |
| junit.runner |
| |
| haunted by |
| egamma |
| |
| 2000-12-03 - 2016-10-15 |
| |
| "BaseTestRunner: The guardian of |
| tests, old and new." |
| |
| 30 commits | 10 mourners |
~~~~|______________________________________|~~~~
Code
MathFe
/
exorcist
Hacktoberfest 2026 final-week project: summon the ghosts of legacy Java code. A Spring Shell CLI where the Git author of each class explains it, confesses its code smells and proposes refactorings, powered by a local LLM via Ollama.
Exorcist
Summon the ghosts of legacy Java code.
Exorcist is a terminal app that reads a Git repository, finds who wrote each Java class, and lets the ghost of its main author explain the class in first person, confess its sins and propose a refactoring Everything runs locally: the "ghost" is an open-weight model served by Ollama, so your code never leaves your machine.
.-.
(o o) E X O R C I S T
| O \ summon the ghosts of legacy Java code
\ \ model: qwen2.5-coder:7b, running locally via Ollama
`~~~'
The tone is theatrical, but the facts are not. Every claim the ghost makes must cite line numbers, its life story comes only from real commits, and refactorings are shown as a diff. Exorcist never writes to your files.
Requirements
-
JDK 21 or newer
-
Ollama running locally, with a model pulled. The default is
qwen2.5-coder:7b…
Java 21, Spring Boot, Spring Shell, Spring AI (Ollama), JGit and JavaParser. 98 tests, none of which need a model or a network.
How I Built It
The hard part was not the ghost voice. That's one paragraph of system prompt. The hard part was that a 7B model will happily lie, and a tool that explains legacy code with confident lies is worse than no tool. So most of Exorcist is about handing the model facts and checking what comes back.
1. The ghosts come from Git, not from the model. One walk over the history (merges skipped, renames followed) finds who touched each file. Over 25 years people change names and e-mails: JUnit has dsaff <dsaff> and David Saff <saff@google.com>, who only meet through a third identity, dsaff <david@saff.net>. A small union-find merges them, but never through shared placeholder addresses like noreply@github.com, which would merge strangers. git blame tells the ghost which lines other hands wrote last.
2. No line number, no claim. The source goes to the model with line numbers, and every rule in the prompt points back at them. Then every answer is checked: Exorcist counts the citations, flags lines that don't exist and numbered items that cite nothing. That's the [12 line citations, all inside the file] line, in green when it's clean and yellow when it isn't.
3. Sins are found before they're confessed. JavaParser scans the class for empty or broad catch blocks, printStackTrace(), System.out, deep nesting, long methods, public mutable fields, Vector/Hashtable, @Deprecated code and TODOs, each with exact lines. The model gets them as evidence to judge, not as a script to read.
4. The model writes code. Exorcist writes the diff. exorcise doesn't ask the model for a patch, because small models produce broken ones. It picks a sin the static scan confirms, widens it to the smallest complete code that fixes it (the whole try statement, the statement, the method), and asks for REPLACE LINES a-b plus a code block. Exorcist splices it in, fixes the indentation the model forgot, checks that the file still parses, and computes the diff with JGit. Nothing is ever written to disk.
5. Prompts fit a small model's memory. Long files are cut to a window around the lines that matter, and the model is told exactly what it can't see.
Where the ghosts still lie
It would be odd to write about honesty and then hide the failures, so here they are, all from my own test runs.
-
The first
confessrun invented anIOExceptionin two items (with the right line numbers, wrong content) and repeated one sin twice. Citations keep claims anchored and checkable. They don't make them true. -
The first
exorciseproduced a diff that doesn't compile. It deleted thereturn nullafter a failure and caught exceptions thetrybody can never throw. Exorcist only checks that the result parses, and that's the next thing I want to fix: compiling the spliced file against the project's classpath. -
gemma3:4bwas worse. It called a three-line method "bloated", rewrote a whole method into a 13-line slot (Exorcist did catch that one: "the file no longer parses"), and, while blaming lines on other contributors, called a real JUnit maintainer "not a very good programmer". The authors are real people, so the prompt now has a fifth rule: judge the code, never the people. On the rerun, it criticized commits, not their authors.
So treat Exorcist the way you'd treat a séance: entertaining, sometimes eerily accurate, and never something you act on without checking. Every claim comes with the lines you need to check it.
I built it with Claude Code as a pair programmer. Writing the tests found four real bugs in the code we had written together, including one where, with a small prompt window, the model could be asked to rewrite lines it was never shown.
Why open matters here
Your code never leaves your laptop. The code people most need to understand is legacy code inside companies: banks, insurers, government. It's exactly the code you're not allowed to paste into a hosted chatbot. With Ollama and an open-weight model, Exorcist sends the source of your proprietary 2003 billing class nowhere. No API key, no account, no terms of service.
It works with the lights off. Once the model is pulled, Exorcist needs no network at all. I ran every demo above with the repository and the model on the same machine.
It costs nothing per question. A single possess prompt carries a full class, its outline and its Git history: several thousand tokens. Exploring a 450-class codebase that way on a paid API adds up fast. Locally it's just electricity, so you can ask the ghost again, and again.
You choose the brain. Swapping models is one environment variable (EXORCIST_MODEL=gemma3:4b), which is how I found out that model size matters more than I expected for keeping a ghost honest. With a closed API I couldn't have compared them on the same prompts and the same machine in an evening.
Old code deserves to be remembered by the people who wrote it. It shouldn't have to be shipped off to someone else's server for that to happen.
Happy Halloween, and feliz Día de los Muertos. 🕯️
Top comments (0)