git séance:查問 request 專案歷史,並讓回答引用來源
git séance: I asked the ghost of `request` why it died, and made it cite its sources
Jay Pokale 建立 git séance,讓本地 Gemma 4(透過 Ollama)根據專案歷史回答問題,並為主張附上 issue 或 commit 引用。
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
request was the HTTP library of Node.js for a decade. In 2019 its author wrote
#3142, "Request's Past, Present and Future", and in 2020 it went into
maintenance mode. The code is still on npm. The reasons are scattered across 3,477 issues and pull requests, 12,441
comments and 2,290 commits, and the people who knew them have moved on.
git séance lets you ask that history a question. A local open-weight model (Gemma 4 through Ollama) answers as the
project's ghost, but it may only speak from the history: every claim cites an issue ([#3142]) or a commit
([b66e4cd]), and the code removes any citation that isn't in what was actually retrieved.
It's for anyone who inherits or depends on an abandoned project and wants to know why it is the way it is, before
they "fix" something that was a deliberate decision ten years ago.
Demo
(
( )
|
.-'-.
| |
'---'
You light a candle for request/request (2011-2026).
3477 threads, 12441 comments and 2290 commits stir in the dark. Ask, and they will answer.
you › How did request get automatic gzip decompression of responses?
The spirits whisper that the automatic decompression began with a dedicated effort to "Add support for gzip
content decoding" [#951]. This feature was implemented via a pull request, merging into the project history with
the commit [b66e4cd]. The goal was to allow the library to "Decompress 'x-gzip' content-encoding responses when
{gzip: true}" [#2446], enabling the module to handle responses that had a `Content-Encoding` header [#1680]. Later,
the project refined this process, introducing "More lenient gzip decompression" to ensure that "malformed but
otherwise acceptable server responses are still properly decompressed" [#2492].
[#2492] thread by addaleax, 2016-12-21 https://github.com/request/request/pull/2492
[#2446] thread by ghost, 2016-11-02 https://github.com/request/request/issues/2446
[#1680] comment by simov, 2015-07-23 https://github.com/request/request/issues/1680#issuecomment-124089311
[#951] thread by kevinoid, 2014-07-01 https://github.com/request/request/pull/951
[b66e4cd] commit by Mikeal Rogers, 2014-07-02 https://github.com/request/request/commit/b66e4cd
39s
That's a real, unedited run on my laptop. The right answer is PR #951 by kevinoid, merged in July 2014, after three
earlier attempts (#303, #458, #464) were closed. The answer isn't perfect: the "x-gzip" quote comes from a later issue
(#2446), not the original feature. The citation is real, so the checker can't catch that; it only guarantees that
every citation points at something that exists.
Code
JayPokale
/
git-seance
Ask a dead open-source project why it did what it did. GraphRAG over its history with LanceDB, lance-graph and a local Gemma; every claim cites an issue or commit.
git séance 🕯️
Ask a dead open-source project why it did what it did, and get an answer that cites its own history.
git séance turns a repository's issues, pull requests, comments and commits into a graph, and answers questions
with a local open-weight model (Gemma 4 through Ollama) that may only speak from that history. Every claim cites an
issue ([#3142]) or a commit ([a1b2c3d]), and the code removes any citation that isn't in the retrieved evidence.
The first project summoned is request, the Node.js HTTP library that went
into maintenance mode in 2020: 3,477 issues and pull requests, 12,441 comments and 2,290 commits.
How it works
-
Ingest (
seance/ingest.py): issues, pull requests and their comments from the GitHub API, commits from a blobless clone. -
Build (
seance/build.py): node tablesThread,Comment,Commitand relationship tablesREPLIES(comment → thread)…
How I Built It
History as a graph
ingest.py downloads issues, PRs and comments from the GitHub API and reads commits from a blobless clone.
build.py turns them into node tables (Thread, Comment, Commit) and relationship tables, embeds every node with
nomic-embed-text, and stores it all in LanceDB:
| relationship | from → to | where it comes from | edges |
|---|---|---|---|
REPLIES |
comment → thread | the comment's issue | 12,441 |
MENTIONS |
commit → thread | "Fixes #123" and friends in the message | 806 |
REFERS |
thread → thread |
#123 and issue links in a thread or its comments |
1,675 |
PART_OF |
commit → PR | every commit of a merged PR's branch | 1,002 |
The graph queries run with lance-graph, which executes Cypher over
those Arrow tables. I've been sending fixes to lance-graph's planner, so I wanted to use it on something real:
MATCH (m:Commit)-[:PART_OF]->(t:Thread) WHERE m.sha IN ['10246c8...'] RETURN t.number AS number
(One honest wrinkle: lance-graph doesn't parse [:MENTIONS|PART_OF] yet, so the code runs one query per
relationship type.)
The first GraphRAG lost to plain search
To measure anything, I wrote down 10 questions about request and checked by hand which threads answer them, then
scored plain vector search against graph retrieval with the same evidence budget (16 items).
My first graph version ranked whole threads and kept the top four. It did worse than plain vector search: 5/10
against 8/10. Two reasons. One-line comments like "same here" score higher than the thread that actually answers the
question (0.83 against 0.71). And four threads were too few: vector search spreads its 16 items over a dozen threads.
So the graph now adds to vector search instead of replacing it. Ten slots go to the nearest threads, comments and
commits; six go to what the graph says matters, ranking threads by how many of their parts matched, and adding their
opening post and the commit that merged them.
The edge that mattered: commits belong to PRs
The gzip question is the one plain search gets wrong, and it shows why the graph helps. The commit that added gzip,
10246c8 [PATCH v2] Add support for gzip content decoding, never mentions its PR. Only the merge commit does
("Merge pull request #951"). The PART_OF edge (from git rev-list merge^1..merge^2 for every merge commit) links
the commit to PR #951, so the commit that vector search finds leads to the PR and its discussion.
Citations are checked in code
Gemma has to end every claim with a citation. A regex then checks each one against the retrieved evidence and drops
any that isn't there. That check had its own bug: Gemma writes citations as [\#3142], [2780] and [#12, #34],
and my first regex only accepted [#3142]. It wasn't removing anything, but it wasn't counting anything either, so
the first scores said Gemma cited the right thread in 4 of 10 answers. After fixing the parser and re-checking the
same saved answers:
| 10 questions, 16 evidence items | answering thread retrieved | answer cites it | invented citations removed |
|---|---|---|---|
| vector search only | 8/10 | 7/10 | 0 |
| graph (lance-graph) + vector | 9/10 | 9/10 | 0 |
Gemma 4 E4B, about a minute per answer on a 6 GB GTX 1660 Ti.
The caveats matter here:
- I chose the 10 questions and their answers myself, and I corrected one label (gzip) after seeing what retrieval found: I had first marked #303, which turned out to be a closed attempt, not the PR that landed.
- 10 questions is small; treat these as directional, not a benchmark.
- Neither mode finds the thread where Node 0.8 support was dropped (#1427). It's a three-line issue, and dozens of "doesn't work on node X" comments outscore it.
- "Invented citations removed: 0" means Gemma never cited something that wasn't in its evidence in these runs. It doesn't mean every sentence is right, as the demo above shows.
Why Does Open Innovation Matter?
-
The project is open, so its whole memory is open. Every issue, comment and commit of
requestis public. That's what makes a séance possible at all: a closed project's reasons die with its team. - A local model makes re-asking free. Every number above comes from running the same questions again and again while changing the retrieval. On a paid API I'd have stopped after the first run looked fine, and I'd never have seen that my first GraphRAG was worse than plain search.
-
Nothing to shut down. The ghost runs on Gemma, LanceDB and lance-graph on one laptop. Unlike
requestitself, it can't be deprecated out from under you. - Where closed would win: a frontier model would write smoother answers and would probably quote the right thread more often. But the citation check matters more than fluency here: every answer can be checked against the history.
Prize Categories
- Best Use of Gemma: Gemma 4 E4B (via Ollama) answers every question from retrieved evidence, with citations that are checked in code, and is evaluated against plain vector search on hand-checked questions.
Built with heavy help from Claude Code (an AI coding agent): it wrote most of the code, chose and checked the
evaluation questions, ran the evaluations, and drafted this post.
來源:DEV Community · dev.to