<?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: Vedant Gajbhiye</title>
    <description>The latest articles on DEV Community by Vedant Gajbhiye (@vedant_gajbhiye_7f50c4279).</description>
    <link>https://dev.to/vedant_gajbhiye_7f50c4279</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%2F4174264%2F295df39b-54e2-4149-ad21-b601848c8ba4.png</url>
      <title>DEV Community: Vedant Gajbhiye</title>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vedant_gajbhiye_7f50c4279"/>
    <language>en</language>
    <item>
      <title>Revenant: dead programming languages haunt my terminal, no internet required</title>
      <dc:creator>Vedant Gajbhiye</dc:creator>
      <pubDate>Sat, 31 Oct 2026 22:16:53 +0000</pubDate>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279/revenant-dead-programming-languages-haunt-my-terminal-no-internet-required-28dm</link>
      <guid>https://dev.to/vedant_gajbhiye_7f50c4279/revenant-dead-programming-languages-haunt-my-terminal-no-internet-required-28dm</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;Revenant is a ghost that moves into your terminal. COBOL, Fortran, Lisp and Perl, four languages people keep declaring dead, each get a voice and comment on the commands you run. Real lines from my laptop, generated locally by Qwen2.5:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fortran&lt;/strong&gt;, on &lt;code&gt;chmod -R 777 .&lt;/code&gt;: Permissions set like this? Insecure, like an open door in Los Alamos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perl&lt;/strong&gt;, on a failed &lt;code&gt;docker build&lt;/code&gt;: Dockerfile issues? I'd have just s/Dockerfile/sourcecode/ and hoped for the best.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It comes in three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The haunting.&lt;/strong&gt; A bash/zsh hook sends each failed or dangerous command to a local model, and a spirit whispers back under your prompt. It never holds your prompt for more than 4 seconds, and if the model's reply isn't good enough, the ghost stays silent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Séance Room.&lt;/strong&gt; A web page on localhost where you can talk to a spirit. The cartoon ghost's mouth moves with every token streamed from your own model, and a status bar shows "Network off, still haunting" when you pull the plug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Ofrenda.&lt;/strong&gt; On Día de los Muertos families build an altar for the people they remember. &lt;code&gt;ghost ofrenda&lt;/code&gt; builds one for the projects you stopped working on: it scans your git repos, writes each abandoned one an epitaph and an offering, and produces a single HTML page where you light a candle for each.
I built this as a pair with Claude (Sonnet 5.5) in Claude Code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Video (Wi-Fi visibly off):&lt;/strong&gt; &lt;a href="https://youtu.be/qdP6pVRpLjI" rel="noopener noreferrer"&gt;https://youtu.be/qdP6pVRpLjI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Ofrenda demo (fictional projects):&lt;/strong&gt; &lt;a href="https://revenant-ofrenda.onrender.com" rel="noopener noreferrer"&gt;https://revenant-ofrenda.onrender.com&lt;/a&gt; (a static page hosted on Render; the projects on it are made up so I don't publish my own repo names)&lt;/li&gt;
&lt;li&gt;The Séance Room needs a local Ollama, so it can't be hosted. That is the point.
  &lt;iframe src="https://www.youtube.com/embed/qdP6pVRpLjI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Ofrenda: one candle per abandoned project&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F41zrjl9a81x8vy6iht64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F41zrjl9a81x8vy6iht64.png" alt="The Ofrenda altar with candles lit" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6odbehk3hjmfjh4oy08d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6odbehk3hjmfjh4oy08d.png" alt="An abandoned project's epitaph and offering" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmvin9vk5x60ws3lv86ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmvin9vk5x60ws3lv86ux.png" alt="The old languages section" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Séance Room: chatting with a spirit, running locally&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5d2a9urcsj1ge211q3pu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5d2a9urcsj1ge211q3pu.png" alt="The Séance Room with a spirit replying" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fndqv8hjtn63wxn21zeoy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fndqv8hjtn63wxn21zeoy.png" alt="Another spirit in the Séance Room" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the terminal: a spirit whispers after a failed command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrdw0aypx1mmp9b4nxbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrdw0aypx1mmp9b4nxbr.png" alt="A ghost whispering under the shell prompt" width="799" height="404"&gt;&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/vee-1745" rel="noopener noreferrer"&gt;
        vee-1745
      &lt;/a&gt; / &lt;a href="https://github.com/vee-1745/vee-1745-revenant" rel="noopener noreferrer"&gt;
        vee-1745-revenant
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Dead programming languages haunt your terminal. Local open-weight models, no cloud.
    &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;👻 Revenant&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Dead programming languages haunt your terminal. Everything runs locally on open-weight models via &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;. No API keys, no network, no command history leaving your machine.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start (WSL / Linux)&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/vee-1745/vee-1745-revenant &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-c1"&gt;cd&lt;/span&gt; vee-1745-revenant

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 1. a local model&lt;/span&gt;
ollama pull qwen2.5:3b   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; COBOL, Lisp&lt;/span&gt;
ollama pull qwen2.5:7b   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Fortran, Perl (they need the bigger model)&lt;/span&gt;

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 2. check the plumbing&lt;/span&gt;
python3 ghost.py doctor

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 3. meet a spirit&lt;/span&gt;
python3 ghost.py summon cobol
python3 ghost.py demo

&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 4. let it haunt your shell (add to ~/.bashrc, then open a new terminal)&lt;/span&gt;
&lt;span class="pl-c1"&gt;echo&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;eval &lt;span class="pl-cce"&gt;\"\$&lt;/span&gt;(python3 &lt;span class="pl-smi"&gt;$PWD&lt;/span&gt;/ghost.py hook bash)&lt;span class="pl-cce"&gt;\"&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="pl-k"&gt;~&lt;/span&gt;/.bashrc   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; run from this folder&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After the hook is loaded, &lt;code&gt;ghost&lt;/code&gt; is available as a command:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ghost spirits               &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; list the dead&lt;/span&gt;
ghost summon lisp           &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; swap the spirit&lt;/span&gt;
ghost summon perl --model llama3.2:3b   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; swap spirit and model&lt;/span&gt;
ghost&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/vee-1745/vee-1745-revenant" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. Python standard library only, with no pip dependencies and no build step. Setup is &lt;code&gt;git clone&lt;/code&gt;, &lt;code&gt;ollama pull qwen2.5:3b qwen2.5:7b&lt;/code&gt;, &lt;code&gt;python3 ghost.py doctor&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Stack.&lt;/strong&gt; &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; running Qwen2.5 3B and 7B, on a laptop with an RTX 3050 (6 GB VRAM) inside WSL2. The backend is plain Python, and the UI is vanilla JS and hand-drawn SVG with the fonts bundled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A persona is not a system prompt.&lt;/strong&gt; My first test was "say boo in the voice of a bitter COBOL ghost". The 3B model replied with a description of what such a ghost would sound like. The fix was a system prompt plus few-shot examples written as real chat turns, with a random 4 of 8 shown on each call so the model can't memorize one set. My first demo still parroted the examples back at me, because I had reused the example commands as demo commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure, don't trust vibes.&lt;/strong&gt; My first quality metric said "100% usable" everywhere. It only checked that a reply existed, so it was useless. I wrote validators per spirit: COBOL must be at least 90% caps, Lisp needs an s-expression, Perl needs line noise, Fortran needs era vocabulary, and a global filter rejects advice-shaped phrasing, because a ghost that says "be careful with force pushes" has broken the spell. Then I benchmarked both models on commands the spirits had never seen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;spirit&lt;/th&gt;
&lt;th&gt;3B first try&lt;/th&gt;
&lt;th&gt;3B ≤3 tries&lt;/th&gt;
&lt;th&gt;3B median&lt;/th&gt;
&lt;th&gt;7B first try&lt;/th&gt;
&lt;th&gt;7B ≤3 tries&lt;/th&gt;
&lt;th&gt;7B median&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;COBOL&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0.71 s&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;1.14 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lisp&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0.57 s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0.78 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perl&lt;/td&gt;
&lt;td&gt;42%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;1.15 s&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;0.93 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fortran&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;42%&lt;/td&gt;
&lt;td&gt;1.21 s&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;1.17 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Medians are per generation attempt, so a whisper that needed retries takes longer. Two things I took from this. First, &lt;strong&gt;the retry loop is the feature&lt;/strong&gt;: 7B Fortran passes only half the time on the first try but 83% within three, and 7B Perl goes from 67% to 100%. Second, &lt;strong&gt;the model should be chosen per persona&lt;/strong&gt;. COBOL and Lisp pass 100% on the 3B model and it is 25-40% faster for them, while Fortran (42% on 3B) and Perl (75% on 3B) need the 7B one. Each spirit names its own model in its config file.&lt;/p&gt;

&lt;p&gt;Caveats: that is 12 whispers per cell, so treat it as indicative, not statistics. I wrote the validators after reading samples from an earlier run, and "pass" measures form (caps, s-expressions, no advice), not whether a line is funny.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Ofrenda is grounded in git.&lt;/strong&gt; The scanner runs read-only git commands and collects the facts: dates, commit count, languages, the last commit message, and unsaved changes. The &lt;em&gt;cause of death&lt;/em&gt; ("Died mid-refactor", "Died with 1 unsaved change") comes from plain heuristics on those facts, so the model never has to guess one. The model writes only the epitaph and the offering. Every epitaph is fact-checked: a digit that isn't in the repo's facts rejects the draft, and after three rejected drafts the page falls back to a plain template and says so in its footer. On my real repos the model passed on the first try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built as if the page would be exposed.&lt;/strong&gt; The Séance Room binds to loopback only and rejects foreign Host and Origin headers. The Ofrenda is one self-contained HTML file with a Content-Security-Policy that allows a single hashed script and nothing from the network. Commit messages are untrusted input, so by design they only ever go into the page as plain text, never as HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your shell history is private.&lt;/strong&gt; The commands you type contain paths, project names, and sometimes secrets. A shell hook that sent them to a cloud API would be a bad idea. Revenant works with the Wi-Fi off, and the Séance Room page says so on screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free inference changes the design.&lt;/strong&gt; The hook fires on every failed command, and the retry-until-valid loop can generate three times per whisper. On a metered API that would be a bill and a rate limit. Here a generation costs about 0.6 to 1.2 seconds of my own GPU. The validate-and-retry approach only makes sense because retries are free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I could pick a model per character.&lt;/strong&gt; Because the weights are open, I benchmarked two sizes and routed each spirit to the one that worked, instead of taking whatever one model a service gives me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Ofrenda reads my project history.&lt;/strong&gt; Repo names and commit messages stay on the machine that holds the repos. The epitaph is written by the same laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It can't be switched off.&lt;/strong&gt; This week's theme is honoring the past. A tool built on a hosted model dies when the service does. Open weights and a local runtime keep working after the vendor is gone, which seemed right for a project about things that outlived their eulogies.&lt;/p&gt;

&lt;p&gt;Where a closed model would have done better: persona quality. The 3B model drifts into advice, and even 7B needs the retry loop. A frontier model would probably nail the voice on the first try. For this project, I took a slightly worse ghost that is private, free and offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;One of the first whispers I got after installing the hook was about the command I used to summon it. The Fortran spirit looked at my &lt;code&gt;python3 ghost.py&lt;/code&gt; and said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Python dares to summon such spirits. Fortran would have used punched cards for this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another time, a mistyped &lt;code&gt;tar&lt;/code&gt; command got this from Perl: "Path is wrong, or permissions fail. tar -f help?"&lt;/p&gt;

&lt;p&gt;Every project in that altar started with someone excited to type &lt;code&gt;git init&lt;/code&gt;. If one of yours is on the altar, light a candle for it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Render:&lt;/strong&gt; The Ofrenda demo page is hosted as a Render static site at &lt;a href="https://revenant-ofrenda.onrender.com" rel="noopener noreferrer"&gt;https://revenant-ofrenda.onrender.com&lt;/a&gt;. To be upfront about the scope: Render only serves the finished page. The models run locally through Ollama, and the Séance Room can't be hosted because it needs a local model.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>StudyLens: Notes to Study Kit</title>
      <dc:creator>Vedant Gajbhiye</dc:creator>
      <pubDate>Mon, 26 Oct 2026 09:16:11 +0000</pubDate>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279/studylens-notes-to-study-kit-20o3</link>
      <guid>https://dev.to/vedant_gajbhiye_7f50c4279/studylens-notes-to-study-kit-20o3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Tagline:&lt;/strong&gt; Upload photos of your notes and get a summary, flashcards and a quiz in English, Hindi or Marathi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prize categories:&lt;/strong&gt; Best Use of Gemma 4 | Best Open-Source AI Project&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://notes-to-study-kit.onrender.com/" rel="noopener noreferrer"&gt;https://notes-to-study-kit.onrender.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/vee-1745/notes-to-study-kit" rel="noopener noreferrer"&gt;https://github.com/vee-1745/notes-to-study-kit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo video: &lt;a href="https://drive.google.com/drive/folders/1YyzhiPwTskubGrTSP_SS-op8aK4WeyAW?usp=drive_link" rel="noopener noreferrer"&gt;https://drive.google.com/drive/folders/1YyzhiPwTskubGrTSP_SS-op8aK4WeyAW?usp=drive_link&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;Students already take photos of notes, whiteboards and textbook pages. Those photos pile up in a camera roll and never turn into revision. Making flashcards and practice questions by hand takes longer than studying itself, so most people skip it. We wanted a tool where the photo is the only input.&lt;/p&gt;

&lt;p&gt;We also wanted it to work in the languages students actually think in. Many students read English notes but revise more comfortably in Hindi or Marathi, so the kit can be generated in any of the three.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Upload one to four photos of handwritten notes or a printed page. Notes to Study Kit returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a title and a bullet-point &lt;strong&gt;summary&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;key terms&lt;/strong&gt; with definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;flashcards&lt;/strong&gt; you can flip through&lt;/li&gt;
&lt;li&gt;an interactive &lt;strong&gt;multiple-choice quiz&lt;/strong&gt; with instant feedback, explanations and a score&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You choose the output language (English, Hindi, Marathi), how many flashcards and questions you want, and which Gemma 4 model to use: a fast one for most pages, or a larger one for messy handwriting and dense pages. The kit is built only from what is visible in the photos, so it studies your notes, not a generic version of the topic. The interface is minimal and responsive, with System, Light and Dark themes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we built it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4&lt;/strong&gt; is the core of the app. It runs through the Gemini API and reads the note photos directly, so there is no separate OCR step. One multimodal call turns the image into a structured study kit. Users can switch between &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; (faster) and &lt;code&gt;gemma-4-31b-it&lt;/code&gt; (higher quality). The server only accepts those two model names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flask backend&lt;/strong&gt; with a structured prompt that asks for one JSON object. The server then validates it: every quiz question must have exactly four options and a valid answer index, malformed items are dropped, and unparseable output is retried once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla HTML, CSS and JavaScript frontend&lt;/strong&gt; with no build step: drag-and-drop upload, flip cards, a scored quiz, and a theme switcher that respects the device setting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosted on Render&lt;/strong&gt; with gunicorn, deployed from the GitHub repo. The API key lives in an environment variable, never in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open source under the MIT license&lt;/strong&gt;, with a README, &lt;code&gt;.env.example&lt;/code&gt; and contribution ideas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges we ran into
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Getting dependable structured output from an open model.&lt;/strong&gt; The model can wrap JSON in markdown fences or add a sentence before it. We extract the JSON object defensively, validate every field, and retry once before showing an error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting the output.&lt;/strong&gt; A study tool that invents facts is worse than none. The prompt tells the model to use only what the notes say and skip anything unreadable, and the UI reminds students to check important facts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protecting a public demo.&lt;/strong&gt; An open endpoint backed by an API key can be drained, so we added file type and size limits, an allowlist of models, and a per-IP rate limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multilingual quizzes.&lt;/strong&gt; Keeping questions, options and explanations all in the chosen language, while the answer index stays machine-readable, needed a strict output schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accomplishments that we're proud of
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A single photo becomes four study formats in one model call.&lt;/li&gt;
&lt;li&gt;It runs end to end on free tiers: Gemma 4 through the Gemini API and a free Render instance.&lt;/li&gt;
&lt;li&gt;The whole app is small enough to read in one sitting, which makes it a good base for contributors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Gemma 4's image understanding is good enough to replace a separate OCR pipeline for typical note photos.&lt;/li&gt;
&lt;li&gt;With open-weight models, the validation layer around the model matters as much as the prompt.&lt;/li&gt;
&lt;li&gt;Letting users choose between a fast and a higher-quality model is a simple way to trade speed for accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PDF and multi-page document input&lt;/li&gt;
&lt;li&gt;Export flashcards to Anki&lt;/li&gt;
&lt;li&gt;Saved kits and spaced-repetition scheduling&lt;/li&gt;
&lt;li&gt;More languages&lt;/li&gt;
&lt;li&gt;Self-hosting an open-weight model so the app does not depend on a hosted API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built with
&lt;/h2&gt;

&lt;p&gt;Gemma 4, Gemini API, Python, Flask, gunicorn, HTML, CSS, JavaScript, Render, GitHub&lt;/p&gt;




</description>
      <category>opensource</category>
      <category>hacktoberfest</category>
      <category>gemmachallenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>Sloth Commit: a local AI that writes your commit messages and splits the messy ones</title>
      <dc:creator>Vedant Gajbhiye</dc:creator>
      <pubDate>Sun, 25 Oct 2026 06:02:58 +0000</pubDate>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279/sloth-commit-a-local-ai-that-writes-your-commit-messages-and-splits-the-messy-ones-3p0a</link>
      <guid>https://dev.to/vedant_gajbhiye_7f50c4279/sloth-commit-a-local-ai-that-writes-your-commit-messages-and-splits-the-messy-ones-3p0a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Writing a commit message is the smallest chore in programming and the one I put off the most. So I built Sloth Commit: a command-line tool where a local open-weight model reads your staged diff and writes the message for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit        &lt;span class="c"&gt;# the editor opens with the message already written&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model runs on my own laptop through &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;. The tool is plain Python with no dependencies beyond the standard library. A diff is just your unreleased source code, and it never leaves the machine.&lt;/p&gt;

&lt;p&gt;Writing a message from a diff is a one-line idea, so I spent the effort on the parts around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Style Mimic.&lt;/strong&gt; It reads your repo's last 30 commits and copies how &lt;em&gt;you&lt;/em&gt; write: Conventional Commits or plain subjects, casing, trailing periods, emoji, typical length. There's no config file, and it adapts to whatever repo you drop it into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret Guard.&lt;/strong&gt; Before a diff goes anywhere, it scans the staged changes for API keys, tokens, private keys, &lt;code&gt;.env&lt;/code&gt; files and huge binaries, then warns you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit Splitter.&lt;/strong&gt; If you staged a bug fix and a README tweak together, it proposes separate commits, each with its own message, and makes them after you confirm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow-and-steady mode.&lt;/strong&gt; A small model drafts the message in a second or two. With &lt;code&gt;--slow&lt;/code&gt;, a bigger model polishes the draft. Speed is a dial you control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sloth Stats and Standup.&lt;/strong&gt; A local tally of the time you saved, with a sloth, and &lt;code&gt;sloth-commit standup&lt;/code&gt;, which turns your week of commits into a few bullets.
Anyone who has typed &lt;code&gt;fix stuff&lt;/code&gt; at 1 a.m. gets to be a little lazier, and the history still comes out readable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;



&lt;p&gt;I ran it on a real project of mine, a clinical NLP pipeline, on a laptop with 16 GB RAM and an RTX 3050 with 6 GB of VRAM, using &lt;code&gt;qwen2.5-coder:3b&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sloth-commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;doctor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;inside&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qwen2.5-coder:3b&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sloth-commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Learned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;casing&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Capitalised&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;no&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;emoji&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;no&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;~60&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;91&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sloth-commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Asking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qwen2.5-coder:3b...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;README.md&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;detailed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The reply took a second or two. Style Mimic worked: my repo uses plain, capitalised subjects with no period, and that is exactly what came back, not the Conventional Commits default.&lt;/p&gt;

&lt;p&gt;The message itself was only half right, and I'd rather show that than hide it. My test change was four comment lines added to &lt;code&gt;frontend/app.py&lt;/code&gt;, plus a README edit that only removed a trailing newline. The model wrote about the README and ignored the code. My guess is it anchored on the first file in the diff, but I haven't tested that. This is the kind of mistake a 3B model makes on a tiny, odd diff, and it's why the tool shows the message and asks for confirmation instead of committing silently.&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/vee-1745" rel="noopener noreferrer"&gt;
        vee-1745
      &lt;/a&gt; / &lt;a href="https://github.com/vee-1745/vee-1745-sloth-commit" rel="noopener noreferrer"&gt;
        vee-1745-sloth-commit
      &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;sloth-commit&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A lazy git commit assistant. A &lt;strong&gt;local open-weight model&lt;/strong&gt; (via &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;) reads your staged diff and writes the commit message, so you don't have to. Your code never leaves your machine, it works offline, and it costs nothing to run.&lt;/p&gt;

&lt;p&gt;Zero Python dependencies: standard library only.&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;$ git add
$ git commit            # editor opens with the message already written
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What makes it different&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Feature&lt;/th&gt;

&lt;th&gt;What it does&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Style Mimic&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Reads your repo's last 30 commits and copies the conventions (Conventional Commits or plain, casing, length, emoji). No config file.&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Secret Guard&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Scans staged changes for API keys, tokens, private keys, &lt;code&gt;.env&lt;/code&gt; files and huge binaries &lt;em&gt;before&lt;/em&gt; the model or your history sees them. Local regexes, so nothing is uploaded to check.&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Commit Splitter&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Spots unrelated changes in one staged diff (a fix plus a README tweak) and commits them separately, each with its own message. Splits&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vee-1745/vee-1745-sloth-commit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

&lt;p&gt;The open pieces are what make it work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-weight model via Ollama.&lt;/strong&gt; The default is &lt;code&gt;qwen2.5-coder:3b&lt;/code&gt;, which fits comfortably in 6 GB of VRAM. The slow model defaults to &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt;. Both are config, not code, so any model Ollama can run works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A tiny Ollama client in the standard library.&lt;/strong&gt; One HTTP call to &lt;code&gt;/api/chat&lt;/code&gt;, with clear errors for the failures people actually hit: Ollama isn't running, or the model isn't pulled (it tells you the exact &lt;code&gt;ollama pull&lt;/code&gt; command).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diffs trimmed for small models.&lt;/strong&gt; Lock files and binaries are summarised, each file and the total are capped, and the model is told when the diff was cut. A 3B model does much better with a focused diff than with 4,000 lines of &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output cleaning.&lt;/strong&gt; Small models love wrapping answers in code fences, quotes, "Here is the commit message:" or &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; blocks. All of that is stripped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A hook that never gets in the way.&lt;/strong&gt; &lt;code&gt;sloth-commit hook install&lt;/code&gt; adds a &lt;code&gt;prepare-commit-msg&lt;/code&gt; hook. It does nothing for &lt;code&gt;-m&lt;/code&gt;, merges, squashes or amends, and if Ollama is down it silently lets you type the message yourself. It will never be why a commit fails, apart from one opt-in: &lt;code&gt;SLOTH_GUARD=block&lt;/code&gt; stops commits that contain suspected secrets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A splitter that fails safely.&lt;/strong&gt; It groups whole files, never hunks. It refuses files that are partly staged, so it can't pull unstaged work into a commit. All the messages are written &lt;em&gt;before&lt;/em&gt; the index is touched, so a model failure can't leave things half-rearranged, and if a commit fails mid-way the remaining files are staged again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;44 tests against a fake Ollama server.&lt;/strong&gt; They run with no GPU and no network, and include real &lt;code&gt;git commit&lt;/code&gt; runs through the installed hook.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your diff is your source code.&lt;/strong&gt; Hosted commit tools have to upload it to work. Sloth Commit can't, because there is nowhere for it to go. For private or unreleased projects, that's the whole difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret Guard only makes sense locally.&lt;/strong&gt; Checking a diff for leaked API keys by sending it to someone else's server is a good way to leak the keys. With a local model, the scan, the prompt and the answer all stay on the machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's free and works offline.&lt;/strong&gt; No API key, no per-token bill, no rate limit. It works on a train, on campus Wi-Fi, or on a plane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's swappable.&lt;/strong&gt; If a better small coding model appears next month, it's one environment variable: &lt;code&gt;SLOTH_MODEL=...&lt;/code&gt;. You can also point it at another machine on your network or at your own fine-tune. A closed API gives you whichever model the provider decides to serve.&lt;/p&gt;

&lt;p&gt;I didn't run a head-to-head benchmark against a hosted model, so I won't quote a number. A frontier model would very likely have described the &lt;code&gt;app.py&lt;/code&gt; change that my 3B model missed. For me, a message that is good most of the time, is instant, and is free and private beats one that is slightly better but sends my code to a server I don't control. Where it isn't good enough, &lt;code&gt;--slow&lt;/code&gt; and the confirm step catch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;The model answers in a second or two on a 6 GB laptop GPU, which is faster than I can think of a message, let alone type one. I used it on my own repo this week, and the tool keeps a local tally (&lt;code&gt;sloth-commit stats&lt;/code&gt;) of characters you didn't type and time saved. That estimate is based on assumed typing speed, so treat it as a rough figure. What I really gained is a repo history in a consistent style that I no longer have to think about.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Gemma&lt;/strong&gt;: Sloth Commit is model-agnostic, so I swapped Google's open-weight Gemma into it with one environment variable (&lt;code&gt;SLOTH_MODEL=[the exact Gemma model name you pulled]&lt;/code&gt;) and compared it with Qwen on the same staged diff. [One or two honest sentences on what you saw, for example whether it described the &lt;code&gt;app.py&lt;/code&gt; change that Qwen missed, or which one was faster on the RTX 3050.]&lt;/p&gt;

&lt;p&gt;A huge shoutout to claude and shreyashkakad for contributing (&lt;a href="https://dev.to/shreyashkakad"&gt;https://dev.to/shreyashkakad&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>BillSift: a local GST bill sorter that double-checks its own AI</title>
      <dc:creator>Vedant Gajbhiye</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:20:38 +0000</pubDate>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279/billsift-a-local-gst-bill-sorter-that-double-checks-its-own-ai-4o33</link>
      <guid>https://dev.to/vedant_gajbhiye_7f50c4279/billsift-a-local-gst-bill-sorter-that-double-checks-its-own-ai-4o33</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;BillSift turns a folder of bill photos and PDFs into a clean spreadsheet for your CA, entirely on your own laptop.&lt;/p&gt;

&lt;p&gt;Anyone in India who runs a small shop, freelances, or works with a Chartered Accountant ends up with a pile of GST bills: petrol pumps, restaurants, hardware shops, online orders. Someone has to read each one and type the vendor, GSTIN, date, taxable amount, CGST/SGST/IGST and total into a sheet. That someone is usually the CA's staff, once a quarter, and a wrong digit causes trouble at filing time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay:&lt;/strong&gt; freelancers and small shops who want to hand their CA clean data, and small CA practices that spend staff hours on data entry. I haven't sold it yet, so this is a prototype and not a business. But a per-client price only works if the cost per bill is close to zero, and local models give you that.&lt;/p&gt;

&lt;p&gt;What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop in photos or PDFs of bills (PNG, JPG, PDF).&lt;/li&gt;
&lt;li&gt;A local model reads each bill, then &lt;strong&gt;plain code checks the result&lt;/strong&gt; before anything is exported.&lt;/li&gt;
&lt;li&gt;Bills where something doesn't add up are marked ⚠️ with the exact reason, and you fix them in a table.&lt;/li&gt;
&lt;li&gt;Correct a category once and every bill from that vendor gets it, past and future.&lt;/li&gt;
&lt;li&gt;Export one Excel file: Bills, a GST summary by financial year / quarter / month, a By Category sheet, and a Needs Review sheet. CSV too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live demo: &lt;a href="https://billsift-demo.onrender.com" rel="noopener noreferrer"&gt;https://billsift-demo.onrender.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Try it with 11 sample bills&lt;/strong&gt; to see it work: most pass every check, and 3 are flagged for review, with the reason shown for each. It runs on Render's free tier, so the first visit after a quiet spell can take about a minute while the server wakes up.&lt;/p&gt;

&lt;p&gt;The hosted copy is deliberately limited. A free server has 512 MB of RAM and no GPU, so it has no AI model, it only shows pre-processed synthetic sample bills, and uploads are switched off. That also means nobody's real bills ever reach a server. The full version runs on your own laptop with a local model.&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/vee-1745" rel="noopener noreferrer"&gt;
        vee-1745
      &lt;/a&gt; / &lt;a href="https://github.com/vee-1745/billsift" rel="noopener noreferrer"&gt;
        billsift
      &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;🧾 BillSift&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A local, privacy-first GST bill sorter for freelancers, small shops and their CAs.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Drop in photos or PDFs of bills. BillSift reads them with open-source OCR and a small
open-weight LLM running on your own laptop, then &lt;strong&gt;checks the result with plain code&lt;/strong&gt; before
anything reaches your spreadsheet. Your clients' financial documents never leave your machine
and there is no per-page API bill.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why a verification layer?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Small local models make small mistakes: a swapped digit, a wrong field. In accounting a silently
wrong digit is worse than a missing one. So the model's output is never trusted as-is:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GSTIN format + official mod-36 checksum&lt;/td&gt;
&lt;td&gt;mistyped or misread GSTINs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax maths (rate x taxable = CGST+SGST / IGST)&lt;/td&gt;
&lt;td&gt;wrong amounts, wrong rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CGST = SGST, not mixed with IGST&lt;/td&gt;
&lt;td&gt;swapped or misread tax lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taxable + tax = total (₹1 round-off allowed)&lt;/td&gt;
&lt;td&gt;tampered or&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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/vee-1745/billsift" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Run it: &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;, &lt;code&gt;ollama pull qwen2.5:3b&lt;/code&gt;, &lt;code&gt;streamlit run app.py&lt;/code&gt;. Setup steps are in the README.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;photo / PDF -&amp;gt; RapidOCR (open source, CPU) -&amp;gt; small LLM via Ollama (JSON-schema constrained)
            -&amp;gt; verification layer (plain code) -&amp;gt; SQLite -&amp;gt; review UI -&amp;gt; Excel / CSV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Open pieces:&lt;/strong&gt; &lt;a href="https://github.com/RapidAI/RapidOCR" rel="noopener noreferrer"&gt;RapidOCR&lt;/a&gt; for text, &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; serving open-weight &lt;code&gt;qwen2.5:3b&lt;/code&gt; and &lt;code&gt;gemma3:4b&lt;/code&gt;, PyMuPDF for PDFs, SQLite, and Streamlit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The idea: the model is an untrusted component.&lt;/strong&gt; Small models make small mistakes, like a swapped digit or a wrong field, and in accounting a silently wrong number is worse than a missing one. So nothing the model returns is trusted until code has checked it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GSTIN format + the official mod-36 checksum&lt;/td&gt;
&lt;td&gt;mistyped or misread GSTINs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax maths: rate x taxable = CGST+SGST or IGST&lt;/td&gt;
&lt;td&gt;wrong amounts, wrong rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CGST = SGST, never mixed with IGST&lt;/td&gt;
&lt;td&gt;swapped or misread tax lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taxable + tax = total (with round-off tolerance)&lt;/td&gt;
&lt;td&gt;misread or tampered totals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grounding: every amount, GSTIN and invoice number must literally appear in the OCR text&lt;/td&gt;
&lt;td&gt;numbers the model invented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate detection&lt;/td&gt;
&lt;td&gt;the same invoice uploaded twice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date sanity&lt;/td&gt;
&lt;td&gt;future dates, and years that look misread&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It also repairs what it can safely repair. OCR reads the letter O as a zero, or a thousands comma as a dot (&lt;code&gt;3.339.40&lt;/code&gt;), and a GSTIN fix is only accepted if the checksum confirms it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real example:&lt;/strong&gt; on my first run, &lt;code&gt;gemma3:4b&lt;/code&gt; read a café bill's CGST 2.5% + SGST 2.5% as a GST rate of 2.5% instead of 5%. The checker caught it ("2.5% of 660.00 is 16.50, but bill shows 33.00"). I then taught the code to recognise the half-rate mistake, so now it fixes it and leaves a note instead of flagging a correct bill. A genuinely wrong rate still gets flagged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; I made 11 synthetic hard bills with invented vendors and valid-checksum GSTINs: thermal receipts, skewed phone photos with shadows and JPEG noise, a digital PDF, a scanned PDF, a discount, IGST, buyer and seller GSTINs, and one with a deliberately wrong tax. The test that matters is whether a wrong value ever passes unflagged.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First run, rule-based extraction with no AI model: 10 of 11 handled safely, and &lt;strong&gt;one silent error&lt;/strong&gt;. My date reader turned &lt;code&gt;2026/10/11&lt;/code&gt; into &lt;code&gt;2011-10-26&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I fixed that and added a check that flags dates over 3 years old. I also fixed OCR slips (a misread "Payable", dot-as-comma amounts) and made digital PDFs read label and value on one line.&lt;/li&gt;
&lt;li&gt;Now: &lt;strong&gt;11 of 11 handled safely. 8 bills come out fully correct, 3 are flagged for a human, 0 silent errors&lt;/strong&gt;, and 52 of 55 fields are right. The 3 flagged are two with genuinely damaged OCR and the deliberately wrong tax.
&amp;lt;!-- If you run &lt;code&gt;python scripts/eval_samples.py --llm qwen2.5:3b&lt;/code&gt; and &lt;code&gt;--llm gemma3:4b&lt;/code&gt;, add their results here. --&amp;gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On my laptop (RTX 3050 6 GB, 16 GB RAM), both models process a batch of 7 bills in about a minute. My first run took 2-3 minutes. Keeping the model loaded between bills and capping the output length fixed that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limits, honestly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All my test bills are synthetic. I haven't tested on a stack of real bills yet.&lt;/li&gt;
&lt;li&gt;It handles English bills. Marathi and Hindi are on the roadmap.&lt;/li&gt;
&lt;li&gt;The checker assumes one GST rate per bill, so multi-rate invoices need a human look.&lt;/li&gt;
&lt;li&gt;Some fields can't be checked by maths, like an invoice number where OCR reads &lt;code&gt;S&lt;/code&gt; as &lt;code&gt;5&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is the product.&lt;/strong&gt; A GST bill carries a vendor's GSTIN and what you paid them. With a local model and local OCR, nothing leaves the laptop, which is the only way I'd ask an accountant to feed client documents into a tool like this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost per bill is zero.&lt;/strong&gt; Cloud OCR and LLM APIs charge per page, which makes a cheap per-client price hard to sustain. Here the cost is electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could swap models in one dropdown.&lt;/strong&gt; I ran both &lt;code&gt;qwen2.5:3b&lt;/code&gt; and &lt;code&gt;gemma3:4b&lt;/code&gt; through the same pipeline and compared them, with no code changes and no pricing or API terms to renegotiate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control over the output.&lt;/strong&gt; Ollama's JSON-schema constrained output and temperature 0 give me a model I can run over every bill and treat as a component to verify, not a black box to trust.
I haven't benchmarked against a closed API, and a bigger closed model would probably misread fewer bills. But BillSift is designed around the model being wrong sometimes, so a better model would only mean fewer bills in the review list. The open, local setup is what lets this run offline, at no cost per page, on data the user never has to hand over.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;A friend of mine who is a CA, went through this and gave mee a positive response. She found BillSift useful in calculations, managing accounts and bills. She did not pay but considered using BillSift in future. Another user I got was a Shop vendor, I loaded BillSift on his desktop and showed him how he can manage his Bills. And he was happy with the results. He did not pay as well.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Render:&lt;/strong&gt; the live demo's front end is hosted on Render (free web service, Docker). The hosted copy runs the rule-based extractor over pre-processed samples. The AI model runs locally, not on Render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma:&lt;/strong&gt; the AI extraction runs Google's open-weight &lt;code&gt;gemma3:4b&lt;/code&gt; locally through Ollama (next to &lt;code&gt;qwen2.5:3b&lt;/code&gt;), constrained to a JSON schema, with every result checked by the verification layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A huge shoutout to shreyashkakad for contributing (&lt;a href="https://dev.to/shreyashkakad"&gt;https://dev.to/shreyashkakad&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Queue the next turn: a tiny Snake game in vanilla canvas JS</title>
      <dc:creator>Vedant Gajbhiye</dc:creator>
      <pubDate>Thu, 15 Oct 2026 15:42:59 +0000</pubDate>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279/queue-the-next-turn-a-tiny-snake-game-in-vanilla-canvas-js-3h8b</link>
      <guid>https://dev.to/vedant_gajbhiye_7f50c4279/queue-the-next-turn-a-tiny-snake-game-in-vanilla-canvas-js-3h8b</guid>
      <description>&lt;p&gt;I wanted a snake game for GHW and here's what cursor did for me withing 5 min. Cursor is fast, gives great results and kind of COOL I assume.&lt;/p&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/vee-1745" rel="noopener noreferrer"&gt;
        vee-1745
      &lt;/a&gt; / &lt;a href="https://github.com/vee-1745/snake-game" rel="noopener noreferrer"&gt;
        snake-game
      &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;Snake&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Browser Snake game. Move with arrow keys or WASD, eat food, and avoid walls and your own tail.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Play&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;code&gt;index.html&lt;/code&gt; in a browser, or from this folder run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python -m http.server 8080&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then visit &lt;a href="http://localhost:8080" rel="nofollow noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Controls&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arrows&lt;/strong&gt; or &lt;strong&gt;WASD&lt;/strong&gt;: move&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Space&lt;/strong&gt;: pause / resume&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enter&lt;/strong&gt;: restart after game over&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/vee-1745/snake-game" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>gamedev</category>
      <category>canvas</category>
    </item>
    <item>
      <title>Grass Coach: a local Gemma agent that gets you off the screen in 10 seconds</title>
      <dc:creator>Vedant Gajbhiye</dc:creator>
      <pubDate>Mon, 12 Oct 2026 05:58:35 +0000</pubDate>
      <link>https://dev.to/vedant_gajbhiye_7f50c4279/grass-coach-a-local-gemma-agent-that-gets-you-off-the-screen-in-10-seconds-1e12</link>
      <guid>https://dev.to/vedant_gajbhiye_7f50c4279/grass-coach-a-local-gemma-agent-that-gets-you-off-the-screen-in-10-seconds-1e12</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Grass Coach is a tiny agent whose only job is to make the screen the shortest part of your day.&lt;/p&gt;

&lt;p&gt;Most "go outside" apps have the same flaw: they're another app that wants your attention. So I built the opposite. You run one command, spend about ten seconds reading one card, and leave. The card tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; to go: the best free window in the next 24 hours, scored from forecast, air quality, UV, wind and daylight, routed around your calendar, with a bonus for golden hour and sunrise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt; that window: written by a local open-weight model, in a voice you pick (park ranger, anime training-arc sensei, worried grandma, pirate, or deadpan British naturalist).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rule&lt;/strong&gt; to play by, which turns a walk into a side-quest: "Count flower colours; stop at 7." / "No headphones. Name 3 distinct sounds before you turn back." / "Photograph nothing. Memorise one thing and describe it to someone later."&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Touch-Grass Ledger&lt;/strong&gt; (&lt;code&gt;grass done&lt;/code&gt;, &lt;code&gt;grass stats&lt;/code&gt;) with streaks and levels, from Seedling up to Ancient Banyan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the weather is bad or your calendar is a wall, it doesn't pretend. It prescribes a five-minute "doorstep reset" at an open window instead.&lt;/p&gt;

&lt;p&gt;It's for anyone who knows they should go outside and keeps not doing it. Me included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;This is a real run on my laptop at 4:35 AM in Nagpur, Gemma 3 4B via Ollama, live Open-Meteo data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🌿 GRASS COACH · Mon 12 Oct · Nagpur
──────────────────────────────────────────────────────────
  CHASE THE DAWN IN NAGPUR!
  The sunrise is painting the sky with beautiful hues, and
  it's the perfect time to witness it before the day gets
  busy. We've got a lovely 30 minutes to enjoy!

  WHEN   6:00 AM – 6:30 AM  (today, in 89 min)  🌅 sunrise
  SCORE  67/100 ███████░░░ good
  SKY    27°C feels-like · rain 0% · UV 0 · wind 5 km/h
  AIR    AQI 105 (unhealthy for sensitive groups)
  RULE   Let's count seven different flower colors – a
         delightful little challenge!
  BRING  A light jacket, A water bottle, A small notebook
  ALSO   today 8:00 AM (52/100)
  ALSO   today 4:30 PM (29/100, golden hour)

  — Get out there and soak it up, friend!
──────────────────────────────────────────────────────────
Screen time for this card: ~10 seconds. Now close the laptop. 🚪
[model: gemma3:4b · forecast: live (Open-Meteo) · calendar: no calendar loaded]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Anyone can try it with no internet and no model:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vee-1745/grass-coach
&lt;span class="nb"&gt;cd &lt;/span&gt;grass-coach
python &lt;span class="nt"&gt;-m&lt;/span&gt; grass &lt;span class="nt"&gt;--demo&lt;/span&gt; &lt;span class="nt"&gt;--no-llm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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/vee-1745" rel="noopener noreferrer"&gt;
        vee-1745
      &lt;/a&gt; / &lt;a href="https://github.com/vee-1745/grass-coach" rel="noopener noreferrer"&gt;
        grass-coach
      &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;🌿 Grass Coach&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A tiny agent that runs on a &lt;strong&gt;local open-weight model&lt;/strong&gt; and has exactly one job: get you off the screen.&lt;/p&gt;

&lt;p&gt;It reads the forecast, air quality, sunset time and your free calendar slots, finds the single best
window to go outside, and hands you a &lt;strong&gt;side-quest card&lt;/strong&gt; with a little rule to play by
You spend ~10 seconds on the screen. Then you leave.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;🌿 GRASS COACH · Sat 10 Oct · Nagpur
──────────────────────────────────────────────────────────
  CHASE THE GOLDEN HOUR
  Ranger's orders: The sun sets at 5:43 PM, so 5:00 PM is
  when the light turns soft and everything looks like a
  film. It's 29°C and the air is moderate (AQI 58)
  WHEN   5:00 PM – 5:30 PM  (today, in 80 min)  🌇 golden hour
  SCORE  83/100 ████████░░ prime
  RULE   Photograph nothing. Memorise one thing and
         describe it to someone later.
  ...
Screen time for this card: ~10 seconds.&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/vee-1745/grass-coach" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Python 3.11+, standard library only. No pip install, no API key, no account.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Open pieces:&lt;/strong&gt; Gemma (via Ollama) as the default writer, &lt;a href="https://open-meteo.com" rel="noopener noreferrer"&gt;Open-Meteo&lt;/a&gt; for open weather and air-quality data, and an OpenAI-compatible adapter so &lt;code&gt;llama.cpp&lt;/code&gt;, LM Studio or vLLM work too. Swapping the model is one line in &lt;code&gt;grass.toml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the model. It's what I &lt;em&gt;don't&lt;/em&gt; let the model do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Code owns the facts, the model owns the flavour.&lt;/strong&gt; Small models are bad at arithmetic and good at tone. So the Grass Score (0-100), the window times and the quest rule are all computed in plain Python. The model receives them as JSON and writes only a title, a pitch, a re-worded rule, a packing list and a sign-off. (You can see the exact facts it gets with &lt;code&gt;grass --json&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Schema-constrained output.&lt;/strong&gt; The request carries a JSON schema (Ollama's &lt;code&gt;format&lt;/code&gt; field, or &lt;code&gt;response_format&lt;/code&gt; on OpenAI-compatible servers), so even a 4B model returns the right shape. Every live Gemma run I did returned a valid card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A number guard.&lt;/strong&gt; Every number the model writes is checked against the facts it was given. If the model says "it's a lovely 42°C", that's rejected, the model is told exactly why, and it gets one retry. This targets the failure small models actually have: confident, plausible, wrong numbers. It's covered by unit tests that script a fake Ollama server returning hallucinated numbers, malformed JSON and no response at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. It always answers.&lt;/strong&gt; If the model server isn't running or fails twice, a rule-based writer produces the card. If there's no signal, the last good forecast is cached and reused, labelled as stale. I tested those paths in unit tests; I haven't yet tried it on a trail with no signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The calendar stays on your machine.&lt;/strong&gt; A tiny &lt;code&gt;.ics&lt;/code&gt; reader finds your busy slots locally. Nothing about your schedule is sent anywhere.&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;grass compare --models gemma3:4b,qwen3:4b,llama3.2:3b&lt;/code&gt;, which runs the same facts through several local models side by side. That only works because the model is a config line, not an API contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the first real run taught me
&lt;/h3&gt;

&lt;p&gt;I wrote the code against a fake model server, so the real Gemma run was the actual test. It worked first time, and it also showed me where a 4B model is weak:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It ignored the voice.&lt;/strong&gt; My default voice is "park ranger", but Gemma wrote a generic cheerful "Get out there, friend!". Ironically, the dumb rule-based fallback ("Ranger's orders: ... Leave only footprints.") has more character than the model does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It ignored common sense.&lt;/strong&gt; It told me to pack "a light jacket" (and on another run "a warm scarf") at 27°C. My number guard checks numbers, not sense. Next step: constrain the packing list by temperature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It didn't mention the air.&lt;/strong&gt; AQI was 105 and the card shows it, but the model's pitch never says so. The score and data are honest; the prose isn't always.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The facts never wavered; the prose did.&lt;/strong&gt; Same inputs, three runs, three different texts. But the time, score and rule were identical every time, which is the whole point of the design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One thing I'd fix:&lt;/strong&gt; the "ALSO" list happily showed a 29/100 option. It should hide weak alternatives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It can work where the grass is.&lt;/strong&gt; A trail with no signal is exactly where a closed API stops. Local weights plus a cached forecast are designed to keep working there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your calendar and location stay yours.&lt;/strong&gt; An agent that knows when you're free and where you live is the kind of data you don't want on someone else's server. With a local model, the prompt never leaves the laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could change how it behaves.&lt;/strong&gt; Constraining output with a schema, adding a verification layer around the model, swapping models and comparing them on identical input are all things I could do in an afternoon because the model is mine to run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; No key, no quota, no bill. A tool meant to be used every day can't have a per-call cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Outing
&lt;/h2&gt;

&lt;p&gt;The card above came out at 4:35 AM and pointed me at a 6:00 AM sunrise window.&lt;/p&gt;

&lt;p&gt;I headed out into the cool Nagpur dawn around 6:00 AM for a 30-minute sunrise walk, bringing along a light jacket, my water bottle, and a small notebook to log what I found. I took the challenge seriously (I got to a nice morning walk anyways) and kept my eyes peeled, successfully spotting seven distinct flower colors: bright marigold orange, deep bougainvillea magenta, soft yellow, pure white, crimson red, lavender purple, and tiny wild blue petals nestled along a garden hedge. The AQI sitting at 105 gave the sky a slightly hazy quality, so I adjusted by keeping my pace relaxed and steady rather than turning it into a brisk workout. Stepping away from the laptop after just ten seconds of screen time was a complete breath of fresh air; swapping my usual morning routine (watching reels and youtube on laptop/tab) of mindless scrolling for real sunlight and quiet streets left me feeling remarkably calm, grounded, and present. And also, flowers helped me talk to my crush.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma&lt;/strong&gt;: runs Gemma locally by default (&lt;code&gt;gemma3:4b&lt;/code&gt; through Ollama), with schema-constrained output and a verification layer around it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A huge shoutout to shreyashkakad for contributing (&lt;a href="https://dev.to/shreyashkakad"&gt;https://dev.to/shreyashkakad&lt;/a&gt;)&lt;/p&gt;

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