<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Triin Kask</title>
    <description>The latest articles on DEV Community by Triin Kask (@triink).</description>
    <link>https://dev.to/triink</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F489646%2F3431abb3-e49a-42c6-a3dd-cff3b57c1d1b.png</url>
      <title>DEV Community: Triin Kask</title>
      <link>https://dev.to/triink</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/triink"/>
    <language>en</language>
    <item>
      <title>git séance: I asked a local Gemma to channel the ghosts of a dead AngularJS codebase</title>
      <dc:creator>Triin Kask</dc:creator>
      <pubDate>Thu, 29 Oct 2026 16:06:22 +0000</pubDate>
      <link>https://dev.to/triink/git-seance-i-asked-a-local-gemma-to-channel-the-ghosts-of-a-dead-angularjs-codebase-19bg</link>
      <guid>https://dev.to/triink/git-seance-i-asked-a-local-gemma-to-channel-the-ghosts-of-a-dead-angularjs-codebase-19bg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every legacy codebase has a line like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;$timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// do not lower!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Nobody remembers who wrote it. Nobody dares to change it. The person who knew why left the company years ago.&lt;/p&gt;

&lt;p&gt;But the reason usually isn't gone. It's buried in commit messages that nobody reads. The code is haunted, and the ghost has been trying to tell us something the whole time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;git séance&lt;/strong&gt; is a small command-line tool that holds a séance for your source code. You point it at a line, and a local Gemma model becomes the ghost of that line. It tells you, in the first person, how it was born, who changed it, and what it has survived, citing the real commits as it goes.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;seance why src/app.js:214   &lt;span class="c"&gt;# the ghost of one line tells its story&lt;/span&gt;
seance haunt                &lt;span class="c"&gt;# a graveyard of forgotten files and ancient TODOs, with epitaphs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I work with AngularJS code every day, so the theme's "forgotten technology" angle felt personal. AngularJS reached end-of-life at the end of 2021, yet a lot of it still runs in production. Those codebases are full of ghosts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/TriinK/git-seance/blob/master/docs/demo.gif" rel="noopener noreferrer"&gt;https://github.com/TriinK/git-seance/blob/master/docs/demo.gif&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/TriinK" rel="noopener noreferrer"&gt;
        TriinK
      &lt;/a&gt; / &lt;a href="https://github.com/TriinK/git-seance" rel="noopener noreferrer"&gt;
        git-seance
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🕯 git-seance&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Ask the ghosts in your git history.&lt;/strong&gt; Point git-seance at any line of legacy code and a local &lt;a href="https://ai.google.dev/gemma" rel="nofollow noopener noreferrer"&gt;Gemma&lt;/a&gt; model tells you its story, in the first person, citing the real commits that shaped it.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/TriinK/git-seance/master/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FTriinK%2Fgit-seance%2Fmaster%2Fdocs%2Fdemo.gif" alt="seance haunt and seance why running on the archived angular.js repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;$ git clone https://github.com/angular/angular.js &amp;amp;&amp;amp; cd angular.js   # archived, 9,000+ commits
$ seance why src/ng/httpBackend.js:59
   55 | }
   56 |
   57 | function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
   58 |   // TODO(vojta): fix the signature
&amp;gt;&amp;gt; 59 |   return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) {
   60 |     url = url || $browser.url();
   61 | 
   62 |     if (lowercase(method) === 'jsonp') {
   63 |       var callbackPath = callbacks.createCallback(url);

5 commit(s) have touched this line.

🕯  Lighting the candles... summoning gemma4:e4b

I am the silent witness to this function's birth. I remember the beginning, when I was simpler, carrying only the basic parameters [5ad0c7d0e].

The journey began with the&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/TriinK/git-seance" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The rule I set myself: &lt;strong&gt;the AI tells the story, but git provides the facts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Evidence, gathered with plain git.&lt;/strong&gt; &lt;code&gt;git log -L214,214:src/app.js&lt;/code&gt; is one of git's most underrated features. It returns every commit that ever changed that exact line, with full commit messages and diffs. For &lt;code&gt;haunt&lt;/code&gt;, a single pass over &lt;code&gt;git log --name-only&lt;/code&gt; finds files untouched for years, and &lt;code&gt;git grep&lt;/code&gt; plus &lt;code&gt;git blame&lt;/code&gt; dates every TODO, FIXME and HACK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The medium: Gemma 4 E4B, running locally through Ollama.&lt;/strong&gt; The evidence and the surrounding code go to &lt;code&gt;gemma4:e4b&lt;/code&gt; with a system prompt that sets strict rules. Speak as the ghost. Use only the history provided. Cite a commit hash for every event. If the reason was never written down, say it was lost to time instead of guessing. Finish with practical "advice from beyond". The answer streams to the terminal token by token, which makes the séance feel properly slow and eerie.&lt;/p&gt;

&lt;p&gt;I chose E4B because it runs comfortably on a normal developer laptop, and its 128K context window means even a heavily rewritten line's whole history fits in one prompt.&lt;br&gt;
On my laptop (Apple M4 Pro, 48 GB RAM) a story takes about 25 seconds from the first candle to the last word. &lt;code&gt;seance haunt&lt;/code&gt; on the whole 9,000-commit AngularJS history takes about a minute, most of it spent in &lt;code&gt;git blame&lt;/code&gt; dating old TODOs before the model even wakes up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The skeptic.&lt;/strong&gt; Ghosts lie, and so do language models. After the story arrives, git séance extracts every hash-like token and checks it against the real commit list. If the model cites a commit that doesn't exist, you get a warning instead of a confident fabrication.&lt;br&gt;
In my runs on the AngularJS repo it hasn't caught Gemma inventing a hash yet. Every citation matched a real commit. The check stays anyway, because the one time it matters is the time you didn't look.&lt;/p&gt;

&lt;p&gt;The project is about 400 lines of Python, its only dependency is the &lt;code&gt;ollama&lt;/code&gt; package, and the tests use a throwaway repo with backdated commits and a fake model, so they run anywhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For this tool, an open-weight model isn't a nice-to-have. It's the only way the idea works where it's needed most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most haunted code is the code you're not allowed to send anywhere.&lt;/strong&gt; Legacy systems live inside companies, government agencies and client projects, often under contracts that forbid sending source code to a third-party API. A model that runs on my own machine means the code, the commit history and the names of everyone who ever touched it never leave the laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works with the lights off.&lt;/strong&gt; Once the model is downloaded, git séance works fully offline: on a train, on an air-gapped network, or long after some hosted AI product has been retired. That felt right for a tool about things that outlive their creators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's inspectable all the way down.&lt;/strong&gt; The evidence-gathering is plain git you can run yourself (&lt;code&gt;--no-ai&lt;/code&gt; shows exactly what the model sees), the prompts are in the repo, and the model weights are public. Nothing about the séance is a black box, which matters when you're deciding whether to trust a story about why production code exists.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;The moment it landed for me was the &lt;code&gt;httpBackend.js&lt;/code&gt; run above. The TODO on line 58 says &lt;code&gt;fix the signature&lt;/code&gt;. Vojta Jina wrote it in August 2011, in the commit that created the file. Over the next five years the signature on line 59 gained four more parameters, from four separate feature commits, and nobody ever fixed it. The TODO is still there, 15 years later, in an archived repository.&lt;/p&gt;

&lt;p&gt;The ghost's "advice from beyond" was to fold the optional parameters into a single configuration object. That is exactly what the TODO was asking for in 2011. The model had no idea the TODO existed; it only saw the history of line 59. The code had been trying to say the same thing the whole time.&lt;/p&gt;

&lt;p&gt;The graveyard was quieter but just as telling. The oldest ghosts in AngularJS are three image files, untouched for 16 years, whose last commit message reads &lt;code&gt;inline all images into css&lt;/code&gt;. The commit that made them obsolete is the last one that ever touched them. The caretaker's epitaph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1] Here lies images/css/arrow_left.gif, untouched for 16 years, forgotten after the instruction to 'inline all images into css'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4 E4B runs locally through Ollama and is the core of the tool. It turns raw git history into a grounded first-person narrative and writes the graveyard's epitaphs.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
    </item>
  </channel>
</rss>
