<?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: Mohd. Ibrahim Iqbal</title>
    <description>The latest articles on DEV Community by Mohd. Ibrahim Iqbal (@ibrahimiqbal).</description>
    <link>https://dev.to/ibrahimiqbal</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%2F473979%2F63a2bd1a-2d0d-425a-b6bc-af4a1dd2618b.jpeg</url>
      <title>DEV Community: Mohd. Ibrahim Iqbal</title>
      <link>https://dev.to/ibrahimiqbal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ibrahimiqbal"/>
    <language>en</language>
    <item>
      <title>Exorcist: a ghost hunter for your package.json</title>
      <dc:creator>Mohd. Ibrahim Iqbal</dc:creator>
      <pubDate>Thu, 29 Oct 2026 07:08:46 +0000</pubDate>
      <link>https://dev.to/ibrahimiqbal/exorcist-a-ghost-hunter-for-your-packagejson-4oij</link>
      <guid>https://dev.to/ibrahimiqbal/exorcist-a-ghost-hunter-for-your-packagejson-4oij</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;&lt;strong&gt;Exorcist&lt;/strong&gt; is a dependency health scanner for &lt;code&gt;package.json&lt;/code&gt; — the checks&lt;br&gt;
&lt;code&gt;npm audit&lt;/code&gt; doesn't run. Drop in a file (or paste a GitHub repo URL) and it&lt;br&gt;
finds the slow rot every Node project accumulates: deprecated packages,&lt;br&gt;
functional duplicates nobody noticed they installed twice, packages a major&lt;br&gt;
version or more behind, oversized bundles, copyleft licenses hiding in a&lt;br&gt;
permissive project, and dependencies with no publish in two years. It rolls&lt;br&gt;
everything into a single 0–100 health score and tells you what to do about&lt;br&gt;
each finding.&lt;/p&gt;

&lt;p&gt;For the theme — &lt;em&gt;Ghost in the Machine&lt;/em&gt; — I leaned all the way in: a Victorian&lt;br&gt;
broadside-poster UI, a blood-drip ceiling, flickering candles, a lantern that&lt;br&gt;
follows your cursor, and a results screen presented as a morgue toe-tag.&lt;br&gt;
Findings are "spirits" (Dead, Dormant, Cursed, Ancient, Bloated, Possessed,&lt;br&gt;
and Resurrection for the alternatives it suggests). It's a bit much. That was&lt;br&gt;
the point.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;Click &lt;strong&gt;"Try a demo tome"&lt;/strong&gt; on the landing page to scan a deliberately haunted&lt;br&gt;
&lt;code&gt;package.json&lt;/code&gt; — it has lodash+underscore duplicates, deprecated &lt;code&gt;left-pad&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;request&lt;/code&gt;, and a few other dependency sins, so you can see every check&lt;br&gt;
fire without hunting down a real cursed project first.&lt;/p&gt;

&lt;p&gt;No login, no setup, no data leaves your browser — see Privacy below.&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%2Frl0hw92jikuqs9yujbbl.jpg" 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%2Frl0hw92jikuqs9yujbbl.jpg" alt="Landing page — the summoning circle awaits your package.json" width="800" height="600"&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%2Fxhai28ib7rd7wu9lwbqn.jpg" 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%2Fxhai28ib7rd7wu9lwbqn.jpg" alt="Results — the toe-tag verdict card with a health score and condition rating" width="800" height="600"&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%2Flc1r9c0l6z4yaxgm41b4.jpg" 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%2Flc1r9c0l6z4yaxgm41b4.jpg" alt="Findings breakdown — Dead Spirits, Cursed Licenses, Ancient Spirits, Bloated bundles, and more" width="640" height="480"&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%2Foxq2moec4qfltk23c6ei.jpg" 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%2Foxq2moec4qfltk23c6ei.jpg" alt="About page — FAQ, privacy policy, honest limits, and credits" width="640" height="480"&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/ibrahim-iqbal" rel="noopener noreferrer"&gt;
        ibrahim-iqbal
      &lt;/a&gt; / &lt;a href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4" rel="noopener noreferrer"&gt;
        exorcist-hf26-wk4
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Dependency health scanner powered by EmbeddingGemma — Hacktoberfest Week 4
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Exorcist&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Drop your package.json. See what lurks within.&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/./LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/abc4ed39bfbdc1f3a9e1faabfce76836f474e14ac8d6dba1e983b4802b9e71a7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d4532333633362e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/089b186637c64e8a1f8aa32003b51683c9a6237fd298eea2ce3515151309d4eb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4861636b746f626572666573742d5765656b2532303425323047686f7374253230696e2532307468652532304d616368696e652d4532333633362e737667" alt="Hacktoberfest 2026 — Week 4"&gt;&lt;/a&gt;
&lt;a href="https://huggingface.co/nickmuchi/EmbeddingGemma-300M-ONNX" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/31f705b31505157cab566592bcd7cf677c126833c73565c1822ec017b1268015/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706f776572656425323062792d456d62656464696e6747656d6d612532303330304d2d4532333633362e737667" alt="Powered by EmbeddingGemma"&gt;&lt;/a&gt;
&lt;a href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4#contributing" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a1aab7f84e690f6e553be6a7e610f4cf60bc57e4c9f4a279f2fa46c74907019a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5052732d77656c636f6d652d4532333633362e737667" alt="PRs welcome"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://exorcist-hf26-wk4.onrender.com" rel="nofollow noopener noreferrer"&gt;&lt;strong&gt;Live demo&lt;/strong&gt;&lt;/a&gt; · &lt;a href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/./docs/DEV_POST.md" rel="noopener noreferrer"&gt;&lt;strong&gt;DEV write-up&lt;/strong&gt;&lt;/a&gt; · &lt;a href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/./docs/DESIGN.md" rel="noopener noreferrer"&gt;&lt;strong&gt;Design spec&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/docs/screenshots/landing-dark.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fibrahim-iqbal%2Fexorcist-hf26-wk4%2FHEAD%2Fdocs%2Fscreenshots%2Flanding-dark.jpg" alt="Exorcist landing — dark theme" width="720"&gt;&lt;/a&gt;
&lt;/p&gt;


More screenshots
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dark results&lt;/th&gt;
&lt;th&gt;Light landing&lt;/th&gt;
&lt;th&gt;Light results&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/docs/screenshots/results-dark.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fibrahim-iqbal%2Fexorcist-hf26-wk4%2FHEAD%2Fdocs%2Fscreenshots%2Fresults-dark.jpg" alt="Results dark"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/docs/screenshots/landing-light.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fibrahim-iqbal%2Fexorcist-hf26-wk4%2FHEAD%2Fdocs%2Fscreenshots%2Flanding-light.jpg" alt="Landing light"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/docs/screenshots/results-light.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fibrahim-iqbal%2Fexorcist-hf26-wk4%2FHEAD%2Fdocs%2Fscreenshots%2Fresults-light.jpg" alt="Results light"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;





&lt;p&gt;Every Node.js project accumulates ghosts: deprecated packages nobody noticed, duplicates doing the same job, bloated bundles dragging down load times, licenses that conflict with yours, and ancient major versions three generations behind. These spirits hide in plain sight inside &lt;code&gt;package.json&lt;/code&gt;, haunting your build long after anyone remembers inviting them in.&lt;/p&gt;

&lt;p&gt;The invoicing-app fix is &lt;code&gt;npm audit&lt;/code&gt;, which covers CVEs but not the slow rot — the functional duplicate you installed because you forgot you already had one, the 200kB library you pulled in for a function that ships with Node, the package whose last commit was in 2022. &lt;strong&gt;Exorcist&lt;/strong&gt; catches those.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fully local.&lt;/strong&gt; Your &lt;code&gt;package.json&lt;/code&gt; never leaves your machine. The ML model downloads once and runs in the browser tab via WebAssembly. No…&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ibrahim-iqbal/exorcist-hf26-wk4.git
&lt;span class="nb"&gt;cd &lt;/span&gt;exorcist-hf26-wk4
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;MIT licensed. Architecture and the full scan pipeline are written up in&lt;br&gt;
&lt;a href="https://github.com/ibrahim-iqbal/exorcist-hf26-wk4/blob/main/docs/DESIGN.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/DESIGN.md&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
if you want the "how it actually works" version instead of the pitch.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The stack: React 19, TypeScript, Vite 8, Tailwind v4 for utility spots, and a&lt;br&gt;
hand-built CSS theming layer (custom properties, no CSS-in-JS) for the poster&lt;br&gt;
UI. Framer Motion and plain CSS keyframes handle the atmosphere — drips,&lt;br&gt;
embers, the cursor-following lantern.&lt;/p&gt;
&lt;h3&gt;
  
  
  The scan pipeline
&lt;/h3&gt;

&lt;p&gt;The scan runs entirely in the browser in seven stages:&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%2Fky4ee4bugbahzp3ja8j1.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%2Fky4ee4bugbahzp3ja8j1.png" alt="Scan pipeline architecture — parse through seven analyzers to health score" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each stage reports progress so the UI can animate the summoning circle in&lt;br&gt;
real time. If any stage fails (model can't load, registry unreachable), the&lt;br&gt;
pipeline degrades gracefully — the checks that needed that data are skipped,&lt;br&gt;
and the rest still run.&lt;/p&gt;
&lt;h3&gt;
  
  
  The open-source AI piece
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://huggingface.co/nickmuchi/EmbeddingGemma-300M-ONNX" rel="noopener noreferrer"&gt;EmbeddingGemma 300M&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
— Google's open-weight embedding model from the Gemma family, running&lt;br&gt;
entirely in-browser via &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt;&lt;br&gt;
on ONNX Runtime WebAssembly. No API key, no inference server, no per-request&lt;br&gt;
cost. It does two genuine jobs that keyword matching can't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate detection.&lt;/strong&gt; &lt;code&gt;lodash&lt;/code&gt; and &lt;code&gt;underscore&lt;/code&gt; share almost no words
in their descriptions, but they're functionally the same thing. Exorcist
embeds every package's npm description and flags pairs above an 82%
cosine-similarity threshold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternative suggestions.&lt;/strong&gt; When a package is dead or dormant, Exorcist
finds the closest healthy package by embedding distance instead of
consulting a hardcoded lookup table — if &lt;code&gt;request&lt;/code&gt; is deprecated, it can
surface &lt;code&gt;axios&lt;/code&gt; or &lt;code&gt;node-fetch&lt;/code&gt; based on what the package actually does.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the model can't load — offline, low memory, an older browser without&lt;br&gt;
WebAssembly SIMD — the scan degrades gracefully. Those two checks get skipped&lt;br&gt;
and the other five still run. No crash, no blank screen.&lt;/p&gt;

&lt;p&gt;The rest of the pipeline (npm registry lookups, Bundlephobia bundle-size&lt;br&gt;
checks, the scoring model) is plain TypeScript with no AI involved — the&lt;br&gt;
model earns its place for the two checks that genuinely need semantic&lt;br&gt;
understanding, not because "AI" sounded good on a feature list.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;package.json&lt;/code&gt; is basically a map of your stack, your exposure, and&lt;br&gt;
how far behind you are on patches — and I didn't want to ask anyone to upload&lt;br&gt;
that to a server to find out if their dependencies were rotten.&lt;/p&gt;

&lt;p&gt;An open-weight model running client-side means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by construction, not by policy.&lt;/strong&gt; There's no backend that &lt;em&gt;could&lt;/em&gt;
see your file even if I wanted it to. The claim "your data never leaves
your browser" is checkable in the network tab, not just written in a
privacy policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-scan cost.&lt;/strong&gt; A closed embedding API would do the same duplicate
detection, but every scan would mean a network call, a cost, and a
dependency on that endpoint staying up. EmbeddingGemma is a file, not a
service — run it on every project, every day, for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works offline once cached.&lt;/strong&gt; After the first load, the full scan
(minus the npm/Bundlephobia lookups, which are public and anonymous
anyway) works without internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspectable.&lt;/strong&gt; The 82% similarity threshold, the severity weights, the
category caps on the health score — all of it is in the source, and all of
it is a number someone can disagree with and change. That's a different
relationship to the tool than a black-box API returning a score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open-weight models made this flow possible without those tradeoffs. That's&lt;br&gt;
the whole pitch for "open innovation" here: the intelligence lives where the&lt;br&gt;
data already is, instead of the data having to travel to where the&lt;br&gt;
intelligence is.&lt;/p&gt;

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

&lt;p&gt;The moment Exorcist stopped feeling like a side project and started feeling&lt;br&gt;
like a real tool was the first time I ran it on one of my own production&lt;br&gt;
&lt;code&gt;package.json&lt;/code&gt; files — not the demo, not a test fixture, a project I&lt;br&gt;
actually ship.&lt;/p&gt;

&lt;p&gt;It found &lt;code&gt;request&lt;/code&gt;. Deprecated since 2020. Still in my dependencies. I knew&lt;br&gt;
&lt;code&gt;request&lt;/code&gt; was deprecated — I'd read the notice years ago — but it was still&lt;br&gt;
there, still being installed, still adding weight to every &lt;code&gt;npm install&lt;/code&gt;,&lt;br&gt;
because no scan I normally run flags "deprecated but functional." I'd walked&lt;br&gt;
past that ghost every day without seeing it.&lt;/p&gt;

&lt;p&gt;Then it found that &lt;code&gt;lodash&lt;/code&gt; and &lt;code&gt;underscore&lt;/code&gt; were both installed. Different&lt;br&gt;
parts of the codebase pulled them in at different times. They do essentially&lt;br&gt;
the same thing. Nobody noticed because the duplicate isn't visible in a&lt;br&gt;
lockfile diff or a bundle report — you only see it if you compare what the&lt;br&gt;
packages &lt;em&gt;do&lt;/em&gt;, not what they're &lt;em&gt;named&lt;/em&gt;. That's the check that needs&lt;br&gt;
embeddings, and that's why I built it.&lt;/p&gt;

&lt;p&gt;Exorcist is a tool for the kind of technical debt that hides in plain sight:&lt;br&gt;
not security vulnerabilities or breaking changes, but the slow accumulation&lt;br&gt;
of things that stopped being the right choice sometime between when you&lt;br&gt;
installed them and now. The ghosts in your dependency tree aren't dramatic.&lt;br&gt;
They're just quietly making everything a little heavier, a little slower, a&lt;br&gt;
little harder to maintain — and nobody exorcises them because nobody looks.&lt;/p&gt;

&lt;p&gt;Now something does.&lt;/p&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; — EmbeddingGemma 300M (from the Gemma family) runs
entirely in-browser via Transformers.js/ONNX for semantic duplicate
detection and alternative package suggestions. No API, no server, no cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt; — The live demo at
&lt;a href="https://exorcist-hf26-wk4.onrender.com" rel="noopener noreferrer"&gt;exorcist-hf26-wk4.onrender.com&lt;/a&gt;
is deployed as a Render static site from &lt;code&gt;render.yaml&lt;/code&gt;, building from
source on every push.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built for Hacktoberfest 2026, Week 4: Ghost in the Machine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>devchallenge</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>cubby: I made my wifi password searchable by meaning, not memory</title>
      <dc:creator>Mohd. Ibrahim Iqbal</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:25:00 +0000</pubDate>
      <link>https://dev.to/ibrahimiqbal/cubby-i-made-my-wifi-password-searchable-by-meaning-not-memory-3o7d</link>
      <guid>https://dev.to/ibrahimiqbal/cubby-i-made-my-wifi-password-searchable-by-meaning-not-memory-3o7d</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;I copy things all day and lose most of them: a wifi password, a &lt;code&gt;docker run&lt;/code&gt; line, a flight confirmation, an address. Every notes app says the same thing, organize it first. Give it a title, a tag, a folder. That's the step I always skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cubby&lt;/strong&gt; skips it. You paste something and press enter. Later you type what you remember, in your own words, and it finds the note. No title, no tag, no folder, no account, no server.&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%2Fxcr9swyc7xkkcmmrb7hc.jpg" 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%2Fxcr9swyc7xkkcmmrb7hc.jpg" alt="cubby answering " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That query found &lt;em&gt;"landlord's number: 555-0199, text don't call"&lt;/em&gt;. The two share no words. An open-weight model, EmbeddingGemma 300M, runs inside the browser tab and ranks your notes by meaning.&lt;/p&gt;

&lt;p&gt;On my 20-item test set it found the right note &lt;strong&gt;20 out of 20&lt;/strong&gt; times. Plain keyword search found &lt;strong&gt;14 out of 20&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://cubby-6mn8.onrender.com" rel="noopener noreferrer"&gt;https://cubby-6mn8.onrender.com&lt;/a&gt; (the first load downloads the model, about 300 MB, once)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpohyg72c71sgclybo31s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpohyg72c71sgclybo31s.gif" alt="typing a query and the right note rising to the top" width="600" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything above is a real capture of the live app with ten sample notes. It also fits a phone screen:&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%2Fdq507deljcwcy2s0i5ci.jpg" 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%2Fdq507deljcwcy2s0i5ci.jpg" alt="cubby on a 390px-wide screen" width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/ibrahim-iqbal/cubby-hf26-wk3" rel="noopener noreferrer"&gt;https://github.com/ibrahim-iqbal/cubby-hf26-wk3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. React 19 + Vite, a static site.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The model measures meaning. It never writes a word.
&lt;/h3&gt;

&lt;p&gt;Last week's &lt;a href="https://dev.to/ibrahimiqbal/hushquote-quotes-that-stay-yours-3ldl"&gt;HushQuote&lt;/a&gt; asked a 270M model to generate text, and most of that write-up is about how it hallucinated. So this time the model only does the one thing small models are reliable at: turning text into an embedding.&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%2Fi1durt8i0hhaesu26qqu.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%2Fi1durt8i0hhaesu26qqu.png" alt="Architecture: everything inside the box runs in your browser tab" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Saving never waits for the model. The note is stored first and its vector is filled in behind it. Asking blends two scores, so an exact keyword is never beaten by a weak semantic guess:&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%2Ff4tccwk0nal4g8jw088o.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%2Ff4tccwk0nal4g8jw088o.png" alt="Ask flow: semantic and lexical scores blended 70/30" width="560" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blend is a fixed &lt;code&gt;0.7 × semantic + 0.3 × lexical&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The test set
&lt;/h3&gt;

&lt;p&gt;20 fixed (note, query) pairs. Each query is ranked against all 20 notes, so a hit means the right one beat 19 others. Every row is in &lt;a href="https://github.com/ibrahim-iqbal/cubby-hf26-wk3/blob/main/test/queries.md" rel="noopener noreferrer"&gt;&lt;code&gt;test/queries.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scorer&lt;/th&gt;
&lt;th&gt;Right note in top 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;semantic only&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20/20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shipped hybrid&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20/20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plain keyword&lt;/td&gt;
&lt;td&gt;14/20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty is small and I wrote the paraphrases myself, so read this as "works on the case the app exists for", not "solves search". These are the six keyword misses:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;query&lt;/th&gt;
&lt;th&gt;saved note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;refund deadline for the apartment booking&lt;/td&gt;
&lt;td&gt;Airbnb Lisbon, free cancellation until Jan 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sherlock holmes address&lt;/td&gt;
&lt;td&gt;123 Baker Street, London NW1 6XE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;what flowers should I get for mother&lt;/td&gt;
&lt;td&gt;mom's birthday is March 14, she likes tulips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;how do I reach the property manager&lt;/td&gt;
&lt;td&gt;landlord's number: 555-0199, text don't call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;generate a self-signed TLS certificate&lt;/td&gt;
&lt;td&gt;openssl req -x509 -newkey rsa:4096 ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;what time is my teeth cleaning&lt;/td&gt;
&lt;td&gt;dentist appointment moved to 2:30pm Thursday&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What fought back
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;300 MB on first load.&lt;/strong&gt; A service worker caches the weights, so later visits are instant and work offline. The first visit shows a live log of each shard instead of a fake progress bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;256 dimensions, not 768.&lt;/strong&gt; I keep the first 256 of EmbeddingGemma's 768 and renormalize (Matryoshka truncation, which the model is trained for). Vectors are a third the size and recall on my set didn't change. The model also wants task prefixes, so a query gets &lt;code&gt;task: search result | query:&lt;/code&gt; and a saved note gets &lt;code&gt;title: none | text:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word matching was too strict.&lt;/strong&gt; The first version needed a whole word, so "doc" found nothing until I'd typed "docker". It now scores exact, prefix and substring matches per token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the model fails to load,&lt;/strong&gt; cubby falls back to keyword search and says so in the footer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Checking the privacy claim yourself
&lt;/h3&gt;

&lt;p&gt;"Nothing uploaded" is easy to claim, so I measured it. On the live build I loaded the page and let the model finish, then started logging network requests, saved ten notes, ran three searches and switched through all ten theme and mode combinations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests after load: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to know. The first visit downloads the model from &lt;code&gt;huggingface.co&lt;/code&gt;. And on later visits a small &lt;code&gt;generation_config.json&lt;/code&gt; request to the same host can still show up, which lets Hugging Face see your IP and browser like any web request. To repeat the test, open DevTools, go to Network, clear the log after load, then use the app. I didn't test the export button.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model is a file, not a service.&lt;/strong&gt; No API key, no per-call bill, nobody who can change the pricing. A hosted embedding API would mean sending every note you save to someone's server, which is the thing I wanted to avoid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can check the privacy claim.&lt;/strong&gt; Inference happens in your tab, so you can verify it in DevTools instead of taking my word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could test what was inside.&lt;/strong&gt; The truncation and the task prefixes are documented behaviour of the open model, and I checked both against my own test set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works offline.&lt;/strong&gt; The shell and the weights are cached, so it keeps working without signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it saves you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Saving costs one paste and one keypress.&lt;/li&gt;
&lt;li&gt;Finding costs one sentence. On my set, keyword search sent me hunting for 6 of 20 notes; EmbeddingGemma sent me hunting for none.&lt;/li&gt;
&lt;li&gt;Once warm, one run on the live site showed results about 170 ms after I entered a query. That's one measurement, not a benchmark, and it will grow with a bigger collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I haven't measured hours saved, so I'm not claiming any.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still missing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Text only: no images, no OCR.&lt;/li&gt;
&lt;li&gt;No sync between devices. JSON export and import is the only way to move data.&lt;/li&gt;
&lt;li&gt;Hybrid ranking narrows the cases where the two scores disagree but doesn't remove them.&lt;/li&gt;
&lt;li&gt;Share-to-cubby on Android is implemented through the PWA share target, but I haven't tested it on a real phone, so I'm not showing a screenshot of it. Safari on iPhone has weak support, so paste manually there.&lt;/li&gt;
&lt;/ul&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; EmbeddingGemma 300M is the whole retrieval engine. Every ranked result exists because that model embedded the note on-device. It never generates text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render.&lt;/strong&gt; A static site with no backend, deployed from the repo's &lt;code&gt;render.yaml&lt;/code&gt; blueprint and redeployed on every push.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live: &lt;a href="https://cubby-6mn8.onrender.com" rel="noopener noreferrer"&gt;https://cubby-6mn8.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/ibrahim-iqbal/cubby-hf26-wk3" rel="noopener noreferrer"&gt;https://github.com/ibrahim-iqbal/cubby-hf26-wk3&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Five themes in light and dark, each checked for WCAG AA contrast with &lt;code&gt;test/contrast.mjs&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&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%2Fo1el4c6kdvvo480h3qka.jpg" 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%2Fo1el4c6kdvvo480h3qka.jpg" alt="The five themes, dark on top and light below" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>ai</category>
    </item>
    <item>
      <title>HushQuote: quotes that stay yours</title>
      <dc:creator>Mohd. Ibrahim Iqbal</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:49:44 +0000</pubDate>
      <link>https://dev.to/ibrahimiqbal/hushquote-quotes-that-stay-yours-3ldl</link>
      <guid>https://dev.to/ibrahimiqbal/hushquote-quotes-that-stay-yours-3ldl</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/events/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;p&gt;&lt;strong&gt;What I Built:&lt;/strong&gt; HushQuote. Speak a job description, get an itemized, editable quote - then a PDF. A small open-weight model (Gemma 3 270M) runs entirely inside the browser tab, on WebGPU when it's there and WASM when it isn't. No account, no server, no install. The client's name and the day rate never leave the device they were typed on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tcghr9ift2t2ihslh0h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tcghr9ift2t2ihslh0h.gif" alt="Real job typed in, the model extracts it, one line confirmed, client named - all on the live deploy" width="760" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try your own phrasing, not just the demo's. If a line shows up that you never said, that's a known model quirk documented below, not a broken app - tap the × to drop it; totals recompute instantly either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every tradesperson I know who works for themselves does the same thing after a job: sits in the van, opens a notes app or a half-remembered Excel template, and tries to turn "forty square meters, two days, plus materials" into something a client will actually pay against. Then they either forget a line, round a number wrong, or just don't send the quote for three days because writing it feels like a second job.&lt;/p&gt;

&lt;p&gt;The invoicing SaaS market solved this with a $15/month subscription and an account you have to make, for software that's worse at the one job that actually matters: turning what you just said out loud into a document. That's the gap HushQuote is built for - it's cheap enough to be a real side-hustle tool, not a line item.&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%2Frpb7odi23rrr7eh00srj.jpg" 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%2Frpb7odi23rrr7eh00srj.jpg" alt="Cover page - model downloading, then the lock-open reveal" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The whole app is one living document, not a wizard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Margin&lt;/strong&gt; - a rail on the side holding your raw words in monospace, dictation-tape style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Sheet&lt;/strong&gt; - an always-visible, real quote document. Line items typeset directly onto it as the model reads your words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Command Bar&lt;/strong&gt; - mic or textarea, one submit button, nothing else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Say or type the job. The model reads it into structured line items - description, quantity, unit, rate - and they land on the sheet looking exactly like what you'd hand a client, except every model-sourced line is visually provisional: a dashed border and an "Estimated. Yours to set." label, until you tap to confirm it. Math is never done by the model; totals are always computed in plain JavaScript from whatever's actually on the sheet.&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%2Ffa9xcjmdkj8muem9osmb.jpg" 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%2Ffa9xcjmdkj8muem9osmb.jpg" alt="The Sheet mid-extraction - confirmed and still-estimated lines side by side" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Dashboard sits in front of the Sheet once you have more than one quote going: every saved quote in one list, with stat cards for quote count, total quoted value, and how many lines across all your drafts are still sitting on a model's estimate instead of your own number.&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%2F4abs0nabgwcvu1nv2u0m.jpg" 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%2F4abs0nabgwcvu1nv2u0m.jpg" alt="Dashboard - quote count, quoted value, and lines still needing a rate, with an illustrated empty state" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Stack, at a glance:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; React 19 + Vite, deployed as a static site on Render - no backend, no database, nothing running server-side at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Gemma 3 270M-instruct (Q8_0 GGUF), via &lt;code&gt;wllama&lt;/code&gt; (llama.cpp compiled to WebAssembly/WebGPU), downloaded once into the browser tab&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation:&lt;/strong&gt; zod schemas (&lt;code&gt;src/lib/schema.js&lt;/code&gt;) checking every model response before it reaches the sheet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; &lt;code&gt;localStorage&lt;/code&gt; only - quotes, business profile, and rate library never leave the device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF export:&lt;/strong&gt; the browser's native print dialog, no added dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The model only extracts. The app decides what's true.
&lt;/h3&gt;

&lt;p&gt;That's the one sentence that governs every design choice here. A 270-million-parameter model is small enough to ship as a file instead of a service, but it is also small enough to be wrong in specific, repeatable ways - so nothing it says is trusted until code checks it.&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%2Fhac37oudwnxc2618bccc.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%2Fhac37oudwnxc2618bccc.png" alt="Flow: mic or text input through Gemma 3 270M, JSON-schema validation, one repair retry, and either a confirmed sheet line or a fallback to manual mode" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structured output, not a hopeful prompt.&lt;/strong&gt; &lt;code&gt;wllama&lt;/code&gt;'s &lt;code&gt;createChatCompletion&lt;/code&gt; supports OAI-compatible &lt;code&gt;response_format: {type:'json_schema'}&lt;/code&gt; natively - the model is sampling against a grammar that only lets valid tokens through, not just being asked nicely for JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything still gets zod-validated.&lt;/strong&gt; A grammar guarantees the braces close; it says nothing about whether the content inside them is right. Every response goes through a real schema check before it touches the sheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One repair generation, then honest surrender.&lt;/strong&gt; If the first response fails validation, the model gets one more try with the exact error attached. If that fails too, the app does &lt;em&gt;not&lt;/em&gt; guess - it drops back to manual mode and the user types the line themselves. A bad extraction costs a few seconds, never a wrong number going out silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model never sees or touches the totals.&lt;/strong&gt; Subtotal, tax, and total are pure functions over whatever line items are actually on the sheet (&lt;code&gt;src/lib/totals.js&lt;/code&gt;) - they recompute instantly on every edit, independent of what the model ever said.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I got wrong, and how I know
&lt;/h3&gt;

&lt;p&gt;I ran all 20 of my own test transcripts (&lt;code&gt;test/transcripts.md&lt;/code&gt;) through the real pipeline rather than trusting the two I'd eyeballed early on, and the results were worse, and more specific, than I expected.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1, 3&lt;/td&gt;
&lt;td&gt;Bathroom tiling (clean / with correction)&lt;/td&gt;
&lt;td&gt;Correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6, 16&lt;/td&gt;
&lt;td&gt;Garden cleanup / fence staining with a price correction&lt;/td&gt;
&lt;td&gt;Mostly correct - 16 even picked up the corrected rate, not the original&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2, 7, 13&lt;/td&gt;
&lt;td&gt;Logo package / tap repair / photography&lt;/td&gt;
&lt;td&gt;Real job fragmented into 2-3 duplicate lines, flat fees landed in the wrong field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4, 8, 10, 11, 12, 18, 19&lt;/td&gt;
&lt;td&gt;Fence, deck rebuild, painting, electrical, Chen deck, gutter cleaning, drywall&lt;/td&gt;
&lt;td&gt;Right-ish first item, &lt;strong&gt;plus a fabricated "Callout" line that doesn't exist anywhere in the transcript&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5, 17&lt;/td&gt;
&lt;td&gt;Lawn+hedges / dog walking&lt;/td&gt;
&lt;td&gt;Right item count, duplicated 2-5x, wrong units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9, 14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Blog posts / callout+valve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Model ignored the real transcript and echoed an unrelated internal example almost word for word&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;"Just an inspection, no numbers"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Model invented a fully priced fake quote for a job that explicitly had no numbers in it&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That "Callout" line showing up in 10 of 18 fresh runs turned out to be my own few-shot example leaking into unrelated outputs - not a rate-library bug, confirmed by clearing the rate library mid-test and watching it persist. Transcript 20 is the one that actually matters: a model inventing prices for a job the user described as having none is exactly the failure "the LLM extracts, the app calculates" exists to make survivable, but a quote that reaches the pending-chip UI with a confidently wrong number on it is still a UX cost, even if it's a recoverable one.&lt;/p&gt;

&lt;p&gt;I tried the obvious fix - dropping the few-shot example entirely and relying on the JSON schema alone. It killed both of the worst cases, but introduced a new one (the model wrote the literal string &lt;code&gt;"${1}"&lt;/code&gt; as a description on every line). Not a clean win either way, so I shipped the known, documented behavior instead of an unproven swap days before the deadline. Full writeup, including a confirmed-separate truncation bug and its fix, is in &lt;code&gt;docs/RISKS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update, after publishing:&lt;/strong&gt; shipped the narrower fix instead. &lt;code&gt;src/lib/filterHallucinations.js&lt;/code&gt; drops a model-output line when its description is a bare "callout" and the transcript never said any form of that word - the single leak that hit 10 of the 18 transcripts above. It's deliberately narrow: "Labor" leaks the same way sometimes, but "Labor" is correct far more often than it's a leak (transcript 1's "two days labor" needs that line to survive), so the same bare-word check there would trade a rare false positive for a common false negative. It does not touch the other two failure rows (9/14's verbatim echo, 20's fully invented quote) - those need the model to recognize it has no grounding at all, which a regex over one field can't do.&lt;/p&gt;

&lt;p&gt;Here's the safety net those two unfixed rows still rely on, in case you hit either one: tap the × on a wrong line, the total recomputes immediately.&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%2Fjinvc791qudp1c7qko0v.jpg" 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%2Fjinvc791qudp1c7qko0v.jpg" alt="Before: three pending lines, including one that's off, total $300" width="800" height="600"&gt;&lt;/a&gt;&lt;br&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%2Fl5o1flbqe18k89k3d8x6.jpg" 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%2Fl5o1flbqe18k89k3d8x6.jpg" alt="After: the bad line removed with one tap, total recomputes to $0" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open innovation matters here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model is a file the app ships, not a service you rent.&lt;/strong&gt; No API key, no per-request bill, no vendor who can change pricing under you. The entire cost of running this for a thousand quotes is the same as running it for one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client names and day rates never leave the device.&lt;/strong&gt; A plumber's client list is exactly the kind of data a side-hustle tool shouldn't be phoning home with, and "trust us" isn't verifiable the way "look, no network tab activity" is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works with no signal.&lt;/strong&gt; A job site with no wifi and bad cell coverage is a normal Tuesday for a tradesperson. Closed APIs don't work there. This does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running it myself, honestly, instead of hiding the rough edges&lt;/strong&gt;, is also the open-source story: the 20-transcript table above exists because I could actually inspect why the model did what it did, and the fixes I made (schema validation, the repair step, the token-budget bump, and now the Callout filter) are fixes to &lt;em&gt;my own code&lt;/em&gt;, not a support ticket to a vendor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this is different from the other Side Hustle freelance tools
&lt;/h2&gt;

&lt;p&gt;A few other Week 2 submissions land in the same space - invoice and receipt sorters for freelancers. The closest ones (InvoicePilot AI, VaultLedger AI, FreelanceAI) are all built the same way under the hood: Ollama or a Python backend the user has to install and run alongside the app. That's "local" in the sense that your data doesn't leave your machine, but it's not "no install" - you're still standing up a server process first.&lt;/p&gt;

&lt;p&gt;The one submission actually in the same product category - turning a messy brief into a priced estimate, not sorting receipts - is &lt;a href="https://dev.to/harshalranjhani/scopecraft-a-clearer-scope-a-fairer-quote-and-a-model-you-can-own-2cla"&gt;ScopeCraft&lt;/a&gt;, a genuinely good build around Qwen3-0.6B. Even it runs a small Node HTTP server locally to serve inference. HushQuote has no server step at all, local or otherwise.&lt;/p&gt;

&lt;p&gt;HushQuote has no backend at all, not even a local one. It's a static site. The model downloads into the browser tab itself and runs there via WebAssembly/WebGPU. Close the tab, and there's nothing left running on your machine to manage. That's a stronger version of the same privacy pitch, and it's the actual reason this is a PWA instead of a desktop app.&lt;/p&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 3 270M-instruct is the only model HushQuote ships, and it's the entire extraction pipeline: every line item on the sheet exists because this model read the transcript, not a cloud call standing in for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt; - the live deploy (&lt;code&gt;hushquote.onrender.com&lt;/code&gt;) is a Render static site, auto-deploying on every push to &lt;code&gt;main&lt;/code&gt; with no build server of my own to maintain.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;The deterministic regex co-extraction pass flagged in &lt;code&gt;docs/RISKS.md&lt;/code&gt; as the next real lever on the two still-unfixed failure rows (verbatim echo, invented-quote), if there's time.&lt;/li&gt;
&lt;li&gt;Finding an actual tradesperson to hand this to and reporting back what they asked for next - the challenge's bonus round, not done yet at time of writing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://hushquote.onrender.com" rel="noopener noreferrer"&gt;https://hushquote.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/ibrahim-iqbal/hushquote" rel="noopener noreferrer"&gt;https://github.com/ibrahim-iqbal/hushquote&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full build log / decisions / bugs found:&lt;/strong&gt; &lt;code&gt;docs/PLAN.md&lt;/code&gt; and &lt;code&gt;docs/RISKS.md&lt;/code&gt; in the repo - the honest version of this post, day by day.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>webassembly</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Touch Grass: voice dares that get you off the screen, running on-device with Gemma 3 270M</title>
      <dc:creator>Mohd. Ibrahim Iqbal</dc:creator>
      <pubDate>Sat, 10 Oct 2026 18:20:41 +0000</pubDate>
      <link>https://dev.to/ibrahimiqbal/touch-grass-voice-dares-that-get-you-off-the-screen-running-on-device-with-gemma-3-270m-fi5</link>
      <guid>https://dev.to/ibrahimiqbal/touch-grass-voice-dares-that-get-you-off-the-screen-running-on-device-with-gemma-3-270m-fi5</guid>
      <description>&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%2F9vzt3xgqb1oyioa3663y.jpg" 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%2F9vzt3xgqb1oyioa3663y.jpg" alt="Touch Grass banner" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&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;&lt;strong&gt;Touch Grass&lt;/strong&gt; — say how much time and energy you've got, and it gives you one short, concrete dare to go do outside. Finish it, snap a photo as proof, and it counts toward a daily streak. No feed, no infinite scroll, no "just one more dare" loop — one dare, go outside, come back later.&lt;/p&gt;

&lt;p&gt;It's a PWA: install it, and after the first load it keeps working with no signal, because the shell and the model are both cached by a service worker.&lt;/p&gt;

&lt;p&gt;Who it's for: anyone who opens their phone meaning to do one quick thing and surfaces 40 minutes later having done none of them. Tell it you've got 10 minutes and low energy, it won't give you a half-marathon.&lt;/p&gt;

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

&lt;p&gt;🔗 &lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://touch-grass-bkii.onrender.com" rel="noopener noreferrer"&gt;https://touch-grass-bkii.onrender.com&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/ibrahim-iqbal/touch-grass" rel="noopener noreferrer"&gt;https://github.com/ibrahim-iqbal/touch-grass&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole app is a little outdoor world you stand in. The painted pixel-art scene fills the screen, your character stands in the grass, and the controls sit at the edges like a game HUD — nothing floats in a box over the middle.&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%2Flj86p57lk8exrbvq2428.jpg" 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%2Flj86p57lk8exrbvq2428.jpg" alt="Night scene — the HUD pins to the corners, the painted world fills the middle, and your character stands in the grass" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Flip the theme and the same world re-lights from a cozy night to a warm daytime — the backdrop art crossfades, the moon slides out and the sun comes up.&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%2Fr2l7ixoi0bybsuimsjl1.jpg" 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%2Fr2l7ixoi0bybsuimsjl1.jpg" alt="Day scene — same world, re-lit for daytime" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tap the mic, say your time and energy, and a dare slides up from the bottom in a dialogue ribbon — tagged with roughly how long it takes and how much it'll cost you — with a "prove it outside" button that opens the camera.&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%2Fg1m7m3ffn1yn4ientbrp.jpg" 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%2Fg1m7m3ffn1yn4ientbrp.jpg" alt="A generated dare in the quest ribbon, with time and energy tags and a prove-it-outside button" width="800" height="500"&gt;&lt;/a&gt;&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/ibrahim-iqbal" rel="noopener noreferrer"&gt;
        ibrahim-iqbal
      &lt;/a&gt; / &lt;a href="https://github.com/ibrahim-iqbal/touch-grass" rel="noopener noreferrer"&gt;
        touch-grass
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      voice dares that get you outside — local gemma, zero backend
    &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;touch grass&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;voice dares that get you outside. say how much time and energy you have
it gives you a short outdoor dare, you snap a photo as proof when you're done.&lt;/p&gt;
&lt;p&gt;built for the hacktoberfest open-source ai challenge, week 1: "touch grass."&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;why open&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;dare generation runs on-device with &lt;a href="https://huggingface.co/onnx-community/gemma-3-270m-it-ONNX" rel="nofollow noopener noreferrer"&gt;gemma 3 270m&lt;/a&gt; via transformers.js, not a hosted api&lt;/li&gt;
&lt;li&gt;works after the first load even with no signal — service worker caches the page and the model&lt;/li&gt;
&lt;li&gt;nothing you say or type leaves your browser; your hugging face token and elevenlabs key (both optional) are stored locally and used directly from your device&lt;/li&gt;
&lt;li&gt;swap the model, edit &lt;code&gt;dares.json&lt;/code&gt;, or rip out the generation step entirely — it's all plain js&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;running it&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;just a static site, no build step.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python3 -m http.server 8000&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;open &lt;code&gt;localhost:8000&lt;/code&gt;. voice input needs chrome or edge (web speech api support
is patchy elsewhere)…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ibrahim-iqbal/touch-grass" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Voice input needs Chrome or Edge (Web Speech API support is patchy elsewhere). Camera proof needs HTTPS or localhost — both are satisfied on the live link.&lt;/p&gt;

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

&lt;p&gt;Plain HTML/CSS/JS, no framework, no build step, no backend. A few deliberate choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default mode needs zero setup.&lt;/strong&gt; Say your time/energy/place, it matches you against a small curated dare bank (&lt;code&gt;dares.json&lt;/code&gt;) and speaks the result with the browser's own &lt;code&gt;speechSynthesis&lt;/code&gt;. This always works, offline, with nothing to configure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional smart layer: &lt;a href="https://huggingface.co/onnx-community/gemma-3-270m-it-ONNX" rel="noopener noreferrer"&gt;Gemma 3 270M&lt;/a&gt; via &lt;a href="https://huggingface.co/docs/transformers.js" rel="noopener noreferrer"&gt;transformers.js&lt;/a&gt;.&lt;/strong&gt; Paste a Hugging Face token into settings and dare generation switches to the model, running entirely in your browser via WASM — not a hosted API call. Nothing about your request ever leaves your device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional voice layer: ElevenLabs&lt;/strong&gt;, same pattern — your key, used directly from your browser, swappable back to the built-in voice at any time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI is a game HUD, not a form.&lt;/strong&gt; The painted pixel-art world fills the screen and your character stands in it; the level, stats and controls pin to the corners like an RTS, and each dare appears in a slide-up dialogue ribbon like an RPG. The backdrop art is day/night and crossfades when you toggle the theme, with a sliding sun/moon, drifting clouds and foliage that fills in as your streak grows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A service worker caches the app shell&lt;/strong&gt; so the whole thing — including the model, once downloaded — works with no connection after the first load. That's the actual point of the "touch grass" theme: the app should survive exactly the situation where you're outside with no signal, which is where it's most useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-origin isolation (COOP/COEP)&lt;/strong&gt; configured on the Render deploy so the WASM runtime gets the full, fast execution path rather than a degraded fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camera proof&lt;/strong&gt; uses &lt;code&gt;getUserMedia&lt;/code&gt; + canvas, with a defensive timeout — confirmed by hand that &lt;code&gt;getUserMedia&lt;/code&gt; can hang indefinitely on a device with no camera rather than reject, which otherwise traps the UI with no way out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployed on &lt;a href="https://render.com" rel="noopener noreferrer"&gt;Render&lt;/a&gt;&lt;/strong&gt; as a static site, auto-deploying from &lt;code&gt;main&lt;/code&gt; on every push.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Two reasons this specific shape only works because the model is open-weight:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It can genuinely run with nothing hosted by me.&lt;/strong&gt; There's no backend, no API key of mine in the loop, no bill that scales with usage. A user who wants the smarter dare generation brings their own free Hugging Face token and the model downloads and runs on &lt;em&gt;their&lt;/em&gt; device. I can't see their request, because it never reaches a server I control. A closed API would force a hosted proxy — which means a cost center, a privacy boundary I'd be asking users to trust, and a single point of failure the moment I stop paying the bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It made "fully optional" actually honest.&lt;/strong&gt; Because Gemma 3 270M is small enough to run client-side, the "smart" mode isn't a crippled free tier nudging you toward a paywall — it's a real optional upgrade with a real reliability floor underneath it (the curated dare bank) that works identically whether or not you ever touch the settings gear. That's a hard thing to build honestly on top of a closed, metered API; it's the default shape of an open-weight one.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt; — static site, deployed and auto-deploying from this repo, with COOP/COEP headers configured for the WASM runtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 3 270M running fully client-side via transformers.js for live dare generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — optional nicer voice output for the spoken dare, key used directly from the browser&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>OSS Buddy: a local Gemma that picks weekend-sized issues so my cousin can finally land his first PR</title>
      <dc:creator>Mohd. Ibrahim Iqbal</dc:creator>
      <pubDate>Fri, 02 Oct 2026 17:52:59 +0000</pubDate>
      <link>https://dev.to/ibrahimiqbal/oss-buddy-a-local-gemma-that-picks-weekend-sized-issues-so-my-cousin-can-finally-land-his-first-pr-277f</link>
      <guid>https://dev.to/ibrahimiqbal/oss-buddy-a-local-gemma-that-picks-weekend-sized-issues-so-my-cousin-can-finally-land-his-first-pr-277f</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I have a cousin. He has been telling me he wants to contribute to open source for about a year. Every month or two he sends me a screenshot of a repo and asks "is this a good one to start with?" I say yes. He opens it, scrolls for twenty minutes, closes the tab. The screenshot never turns into a PR.&lt;/p&gt;

&lt;p&gt;I used to tell him "just pick a good first issue." That advice is useless. Good first issues live in repos with four hundred files and eighty open issues each. You still have to pick. He had already been doing that for a year.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;OSS Buddy&lt;/strong&gt;. You point it at any GitHub repo and a local Gemma 3 model tells you three things in about twenty-three seconds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What the project actually does, in two plain sentences&lt;/li&gt;
&lt;li&gt;Up to three &lt;code&gt;good first issue&lt;/code&gt; tickets ranked easiest to hardest, with time estimates&lt;/li&gt;
&lt;li&gt;Which specific directory to start reading&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nothing leaves his laptop. No account, no API key, no credit card. He downloads Gemma once, forgets about it, the tool works forever.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fgithub.com%2Fibrahim-iqbal%2Foss-buddy%2Fraw%2Fmain%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fgithub.com%2Fibrahim-iqbal%2Foss-buddy%2Fraw%2Fmain%2Fdemo.gif" alt="demo" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One command, no cloud calls. Terminal recording against &lt;code&gt;kiwix/kiwix-android&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmarks on five real repos
&lt;/h3&gt;

&lt;p&gt;Measured end-to-end on an M-series Mac with Gemma 3 4B via Ollama. Each run is one command, no warmup, no cache.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Good-first-issues found&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;kiwix/kiwix-android&lt;/td&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;27.9s&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;simonoppowa/OpenNutriTracker&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;23.5s&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RetroMusicPlayer/RetroMusicPlayer&lt;/td&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;22.1s&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;neovim/neovim&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;22.0s&lt;/td&gt;
&lt;td&gt;0 (no &lt;code&gt;good first issue&lt;/code&gt; label)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fastapi/fastapi&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;22.0s&lt;/td&gt;
&lt;td&gt;0 (none open at test time)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Average: &lt;strong&gt;~23 seconds&lt;/strong&gt;, zero cloud bytes. The two zeros are honest limits — neovim doesn't use the &lt;code&gt;good first issue&lt;/code&gt; label at all, and fastapi had none open when I ran it. OSS Buddy says so plainly rather than hallucinating issues.&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/ibrahim-iqbal" rel="noopener noreferrer"&gt;
        ibrahim-iqbal
      &lt;/a&gt; / &lt;a href="https://github.com/ibrahim-iqbal/oss-buddy" rel="noopener noreferrer"&gt;
        oss-buddy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A local Gemma 3 reads any GitHub repo and picks weekend-sized good-first-issues for your friend. Nothing leaves your machine.
    &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;OSS Buddy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ibrahim-iqbal/oss-buddy/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%2Fibrahim-iqbal%2Foss-buddy%2FHEAD%2Fdemo.gif" alt="demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Point it at a GitHub repo. A local Gemma 3 model reads the README, the
top-level file tree, and the open &lt;code&gt;good first issue&lt;/code&gt; tickets, then
tells you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What the project does, in plain words&lt;/li&gt;
&lt;li&gt;Up to three issues that look weekend-sized, ranked easiest to hardest&lt;/li&gt;
&lt;li&gt;Where in the repo to start reading&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nothing leaves your machine. Your GitHub token stays local. The model
runs on your laptop via &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A friend of mine kept opening huge OSS repos, scrolling for twenty
minutes, closing the tab. He wanted to contribute. He did not know
where to start. Every guide said "pick a good first issue" and every
repo had dozens.&lt;/p&gt;
&lt;p&gt;This reads the repo for him and says "here, pick one of these three
start reading here."&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Needs Python 3.9+, the GitHub CLI, and Ollama.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; ollama (macOS)&lt;/span&gt;
brew install ollama
brew services start ollama&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/ibrahim-iqbal/oss-buddy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;~150 lines of stdlib Python. No &lt;code&gt;pip install&lt;/code&gt;. MIT.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Open-source AI used:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 3 (4B)&lt;/a&gt; — Google's open-weight model, running on my laptop&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; — open-source runtime for the model&lt;/li&gt;
&lt;li&gt;GitHub CLI (&lt;code&gt;gh&lt;/code&gt;) for repo data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pipeline (lazy on purpose):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    gh api  (readme + top-level tree + good-first-issues)
                        │
                        ▼
                 one tuned prompt
                        │
                        ▼
         POST localhost:11434 (ollama)
                        │
                        ▼
                   gemma 3 4b
                        │
                        ▼
         stream the answer to the terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three HTTP calls to GitHub, one HTTP call to a server running on my own laptop. That is the whole pipeline. The Python file is small enough to read in one sitting.&lt;/p&gt;

&lt;p&gt;The prompt went through three real iterations in the git history. The first version produced great summaries but ended every answer with "Would you like me to delve deeper?" — chatty, off-brand. The second version started paraphrasing issue titles instead of quoting them, which was worse than useless because then you couldn't &lt;code&gt;grep&lt;/code&gt; for the issue on GitHub. The third version pins Gemma: "Quote the issue number and the EXACT title from the data below. Do not paraphrase or invent." That one shipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Labels aren't consistent across repos
&lt;/h3&gt;

&lt;p&gt;Early version only looked for the literal &lt;code&gt;good first issue&lt;/code&gt; label. Lost half the repos. The fix was a cheap one — try the three common spellings in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;good first issue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;good-first-issue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;beginner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, but it is the difference between the tool working on one repo in two and almost all of them.&lt;/p&gt;

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

&lt;p&gt;He does not need to sign up for anything before he can start contributing to open source. He already has GitHub, git, and a dozen tabs open. A new contributor does not need another sign-up flow before the one he actually came for. Open-source AI removes that friction entirely — he downloads the model once, forgets about it, and the tool just works.&lt;/p&gt;

&lt;p&gt;The second thing matters more. His GitHub token and the repos he explores never leave his laptop. Some of those repos are private — half-built side projects, work stuff. A closed API would have meant sending every README and every issue title to a third party's servers. With Gemma running locally that whole class of problem does not exist.&lt;/p&gt;

&lt;p&gt;The third thing is practical. It runs on a plane. It runs in a cafe with slow wifi. It runs inside offices that block third-party API calls. It costs zero rupees forever. For someone taking their first step into open source every friction point is a chance to quit — the open stack removes a few of them.&lt;/p&gt;

&lt;p&gt;And the whole chain is readable. 150 lines of Python. An open-weight model with public weights. A runtime with public source. If my cousin wants to see what the LLM did with his repo, he can trace it from his terminal all the way down to the weights. That is the open-source story — not just the model, the whole stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hand-over
&lt;/h2&gt;

&lt;p&gt;I sent him the install snippet on WhatsApp — three commands, no sign-up, no API key.&lt;/p&gt;

&lt;p&gt;He hasn't run it yet as of this edit. I'd rather say that plainly than leave a placeholder sitting in a published post. The honest version of "built for a friend" includes the part where the friend hasn't gotten to it yet — that's a real, common outcome of building something for someone else's spare time, not a reason to dress it up. I'll update this section the moment he does, with whatever he actually says, not a tidied-up version of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;Skipping DevRelay sessions on this one. The story is in the git log instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;31b5e08&lt;/code&gt; initial scaffold: fetch readme + good-first-issues via gh&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;4a8dbb5&lt;/code&gt; wire up ollama + first prompt that asks for 3 ranked issues&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ede3d7f&lt;/code&gt; tighten prompt (no outro, quote exact issue titles)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;b430d72&lt;/code&gt; add demo gif + vhs tape&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bf1babc&lt;/code&gt; readme: link to the dev.to writeup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0d26567&lt;/code&gt; readme: proper case + benchmarks table + cover image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Six commits, a few hours of work spread across one evening and the next morning.&lt;/p&gt;

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

&lt;p&gt;Short list, in the order I'd attack them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deeper issue analysis.&lt;/strong&gt; Right now it reads the issue title and body. Next step: fetch linked files and the diff of the last PR to touch them, so "where to start" becomes line-level, not directory-level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember what my cousin has already tried.&lt;/strong&gt; A tiny local SQLite log of attempted issues so OSS Buddy stops suggesting the same ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A thin web wrapper for people without Ollama.&lt;/strong&gt; Deployed somewhere small (likely a free Render tier) that calls a hosted Gemma via Groq. The CLI stays local-first; the web version is a convenience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;--contributing&lt;/code&gt; flag&lt;/strong&gt; that reads the project's CONTRIBUTING.md and tells you what tests to run and how to open a PR, so the suggestion ends with "and here is exactly how this project wants it submitted."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of it changes the shape of the tool. It's all deeper reading of the same three sources.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 3 (4B) via Ollama is the only model used. The entire pipeline depends on it. No fallback, no closed-model path, no "bring your own API key." Just Gemma, local, free.&lt;/p&gt;

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