<?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: Hardik</title>
    <description>The latest articles on DEV Community by Hardik (@hardikgaikwad).</description>
    <link>https://dev.to/hardikgaikwad</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%2F4159619%2F7ac80ab0-6c51-4a42-b2f0-35a213d1faf3.jpg</url>
      <title>DEV Community: Hardik</title>
      <link>https://dev.to/hardikgaikwad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hardikgaikwad"/>
    <language>en</language>
    <item>
      <title>Sloth Sorter 🦥 — a local LLM tidies my Downloads folder, and every move can be undone</title>
      <dc:creator>Hardik</dc:creator>
      <pubDate>Fri, 23 Oct 2026 19:45:47 +0000</pubDate>
      <link>https://dev.to/hardikgaikwad/sloth-sorter-a-local-llm-tidies-my-downloads-folder-and-every-move-can-be-undone-3oid</link>
      <guid>https://dev.to/hardikgaikwad/sloth-sorter-a-local-llm-tidies-my-downloads-folder-and-every-move-can-be-undone-3oid</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;My Downloads folder is where files go to be forgotten. Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;invoice_final_v2.pdf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scan0003.pdf&lt;/code&gt;, which turns out to be a bank statement&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;document(7).pdf&lt;/code&gt;, which is actually lecture slides&lt;/li&gt;
&lt;li&gt;three copies of &lt;code&gt;setup.exe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a dozen &lt;code&gt;Screenshot 2026-…png&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sorting it by hand is exactly the kind of chore I keep putting off, so I built something that does it for me while I do nothing. Sloths would approve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sloth Sorter&lt;/strong&gt; is a small, offline-first file tidier with a friendly web UI. You point it at the folders you never clean, like Downloads and Desktop. It then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;sorts&lt;/strong&gt; files into category folders: Finance, Study, Personal &amp;amp; ID, Screenshots, Installers and so on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;renames&lt;/strong&gt; vague documents to something human, so &lt;code&gt;scan0003.pdf&lt;/code&gt; becomes &lt;code&gt;Supermarket Bank Statement Sep 2026.pdf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;archives&lt;/strong&gt; files you haven't touched in months into &lt;code&gt;_Archive/&amp;lt;year&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;runs on a schedule&lt;/strong&gt;, either applying changes automatically or preparing a plan for you to approve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thing I cared about most was trust. A tool that moves my files around has to be boringly safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It never deletes and never overwrites anything.&lt;/strong&gt; Name clashes become &lt;code&gt;report (1).pdf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every run starts as a plan.&lt;/strong&gt; You see a preview table, can untick rows or change a category or name, and only then press &lt;em&gt;Sort everything&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every move is written to a journal before it happens&lt;/strong&gt;, and &lt;strong&gt;every run has an Undo button.&lt;/strong&gt; Undo restores the original names and locations and cleans up the empty folders it created.&lt;/li&gt;
&lt;li&gt;It skips anything risky: hidden files, shortcuts, half-finished downloads (&lt;code&gt;.crdownload&lt;/code&gt;, &lt;code&gt;.part&lt;/code&gt;), files modified in the last 10 minutes, and system folders like &lt;code&gt;C:\Windows&lt;/code&gt; or AppData.&lt;/li&gt;
&lt;/ul&gt;

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


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/55451e53fd8846a4a50c1c7ade67df3d" width="100%" height="400"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;The UI is a little flat-2D "desktop" with rolling hills and drifting clouds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt; you can drag, minimise and bring to the front.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A taskbar&lt;/strong&gt; with a Start menu, a live clock and the active model's name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A dusk theme.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A sloth hanging from a branch&lt;/strong&gt;, which sleeps when idle, says &lt;em&gt;"sorting... slowly"&lt;/em&gt; while it works, and looks pleased when it's done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is drawn in inline SVG and CSS. The page loads nothing from the internet: no CDN, no web fonts.&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/hardikgaikwad" rel="noopener noreferrer"&gt;
        hardikgaikwad
      &lt;/a&gt; / &lt;a href="https://github.com/hardikgaikwad/Sloth-Sorter" rel="noopener noreferrer"&gt;
        Sloth-Sorter
      &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 Sorter 🦥&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Sloths move about one metre a minute, and they still get where they're going. Sloth Sorter
works at the same steady pace on your &lt;strong&gt;Downloads&lt;/strong&gt;, &lt;strong&gt;Desktop&lt;/strong&gt; and other cluttered folders
It sorts files into tidy category folders, gives vague documents sensible names, and archives
things you haven't touched in ages. It runs entirely on your own computer. A local language
model served by &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; helps only where simple rules can't decide.&lt;/p&gt;
&lt;p&gt;It never deletes anything and never overwrites anything, and every run can be undone.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;60-second quickstart&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;pip install -e &lt;span class="pl-c1"&gt;.&lt;/span&gt;            &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; from this folder (Python 3.10+)&lt;/span&gt;
ollama list                 &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; optional: check Ollama is running and has at least one model&lt;/span&gt;
sloth-sorter                &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or: python -m slothsorter&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Your browser opens &lt;strong&gt;&lt;a href="http://127.0.0.1:8484" rel="nofollow noopener noreferrer"&gt;http://127.0.0.1:8484&lt;/a&gt;&lt;/strong&gt;. Then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;My Folders&lt;/strong&gt;, add a folder such as &lt;code&gt;C:\Users\you\Downloads&lt;/code&gt;. Type the path or use
&lt;strong&gt;Browse…&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Preview plan&lt;/strong&gt;. Nothing…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hardikgaikwad/Sloth-Sorter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Try it on your own machine:&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/hardikgaikwad/Sloth-Sorter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Sloth-Sorter
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
python scripts/make_demo_clutter.py   &lt;span class="c"&gt;# optional: a safe folder full of fake clutter&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; slothsorter                 &lt;span class="c"&gt;# opens http://127.0.0.1:8484&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Python with FastAPI and uvicorn, SQLite, &lt;code&gt;pypdf&lt;/code&gt;, &lt;code&gt;python-docx&lt;/code&gt; and Pillow. The frontend is one static HTML/CSS/vanilla-JS page with no build step. The open-weight models are served locally by &lt;strong&gt;&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt;. I tested with &lt;strong&gt;Llama 3.2 (3B)&lt;/strong&gt; and &lt;strong&gt;Qwen 2.5 (7B)&lt;/strong&gt; on a laptop with a 4 GB GPU.&lt;/p&gt;

&lt;p&gt;The key design decision is that &lt;strong&gt;rules go first and the LLM only gets the hard cases.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rules, with no model call.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Extensions decide installers, archives, video, audio, code, spreadsheets and slides.&lt;/li&gt;
&lt;li&gt;Filename patterns catch screenshots ("Screenshot", "Screen Shot", "Snip").&lt;/li&gt;
&lt;li&gt;A screen-sized PNG with no camera data is probably a screenshot.&lt;/li&gt;
&lt;li&gt;A JPEG with camera EXIF data is a photo.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my test folder, this handled 18 of 29 files instantly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The LLM handles documents and unknown files.&lt;/strong&gt; For a PDF, DOCX, TXT, MD or CSV, I extract about 1,500 characters of text and send &lt;em&gt;the filename plus that excerpt&lt;/em&gt; to Ollama's &lt;code&gt;/api/chat&lt;/code&gt;. The request uses &lt;code&gt;temperature: 0&lt;/code&gt; and a &lt;strong&gt;JSON schema in &lt;code&gt;format&lt;/code&gt;&lt;/strong&gt;, so the model must pick a category from a fixed list and propose a short name:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;object&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;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;string&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;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggested_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;string&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;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;string&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;enum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;low&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;medium&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;high&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;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;type&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;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;category&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;suggested_name&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;confidence&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;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Code checks everything the model says.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model never produces paths.&lt;/strong&gt; A path-like answer such as &lt;code&gt;C:\Windows\…&lt;/code&gt; or &lt;code&gt;../../&lt;/code&gt; falls back to the original name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names are sanitised.&lt;/strong&gt; Illegal Windows characters and reserved names like &lt;code&gt;CON&lt;/code&gt; are removed, names are capped at 80 characters, and the original extension is always kept.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unknown categories are rejected.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-confidence answers&lt;/strong&gt; keep the original name and are flagged "needs review" in the plan.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Results are cached by content hash, model and prompt version, so re-running a plan is instant.&lt;/p&gt;

&lt;p&gt;Two things I learned along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small models love CamelCase.&lt;/strong&gt; Both models first returned names like &lt;code&gt;AcmeHostingInvoice4471&lt;/code&gt;. One concrete example in the system prompt fixed it: &lt;em&gt;"2–8 plain words separated by spaces, e.g. 'Acme Hosting Invoice Sep 2026'"&lt;/em&gt;. Now I get &lt;code&gt;Acme Hosting Invoice Sep 2026.pdf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;localhost&lt;/code&gt; cost me 4 seconds on Windows.&lt;/strong&gt; When Ollama was off, &lt;code&gt;localhost&lt;/code&gt; tried &lt;code&gt;::1&lt;/code&gt; first and Windows retried the refused connection before falling back. Ollama listens on &lt;code&gt;127.0.0.1&lt;/code&gt;, so I connect there directly. If Ollama dies in the middle of a run, Sloth Sorter stops calling it and sorts the remaining files by type, instead of waiting on each one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Ollama isn't running at all, nothing breaks. The app switches to &lt;strong&gt;rules-only mode&lt;/strong&gt; and shows a banner explaining &lt;code&gt;ollama serve&lt;/code&gt; and &lt;code&gt;ollama pull &amp;lt;model&amp;gt;&lt;/code&gt;. It never pulls a model itself. It reads your installed models from &lt;code&gt;/api/tags&lt;/code&gt;, auto-picks a small chat model, and lets you switch from a dropdown at any time.&lt;/p&gt;

&lt;p&gt;The scheduler is a background thread inside the server: no cron, no Celery.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runs never overlap.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch-up:&lt;/strong&gt; if the laptop was off when a run was due, it does &lt;em&gt;one&lt;/em&gt; catch-up pass after start-up, not one for every missed slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe test interface:&lt;/strong&gt; the whole web API listens on &lt;code&gt;127.0.0.1&lt;/code&gt; only, with Host/Origin checks and a per-session token, so other websites in your browser can't call it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 96 pytest tests. They cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the folder safety rules and the filename sanitiser&lt;/li&gt;
&lt;li&gt;name collisions&lt;/li&gt;
&lt;li&gt;the LLM path, using a mocked Ollama that returns bad JSON, unknown categories and path-like names&lt;/li&gt;
&lt;li&gt;apply, journal and undo, with byte-for-byte restore checks&lt;/li&gt;
&lt;li&gt;the scheduler, using a fake clock&lt;/li&gt;
&lt;li&gt;the API's CSRF protections&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Because the files in my Downloads folder are the most private data I have. That test folder alone held a bank statement, a passport detail, a CV and invoices. A file tidier only works if it can &lt;em&gt;read&lt;/em&gt; your files, and I'm not willing to upload my bank statements to a cloud API just to get them sorted.&lt;/p&gt;

&lt;p&gt;Open-weight models running locally through Ollama make this possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing leaves the machine.&lt;/strong&gt; The only network traffic is to &lt;code&gt;127.0.0.1:11434&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; It can sort thousands of files on a schedule, every day, forever, with no API bill and no rate limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It keeps working offline&lt;/strong&gt;, on a train, or after some company changes its pricing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You choose the model.&lt;/strong&gt; Use whatever you already have installed, and swap it from a dropdown. A 3B model is enough for "is this an invoice or lecture notes?"&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I tested on a folder of 33 realistic clutter files: invoices, a bank statement, lecture notes, a CV, installers, screenshots and photos. These are measured numbers from the end-to-end runs, not guesses.&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;Llama 3.2 3B&lt;/th&gt;
&lt;th&gt;Qwen 2.5 7B&lt;/th&gt;
&lt;th&gt;Rules only (Ollama off)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Files sent to the LLM&lt;/td&gt;
&lt;td&gt;11 of 29&lt;/td&gt;
&lt;td&gt;11 of 29&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct category&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26 / 29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;27 / 29&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;22 / 29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to build the plan&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.9 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;65.3 s&lt;/td&gt;
&lt;td&gt;0.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per LLM file&lt;/td&gt;
&lt;td&gt;~1.1 s&lt;/td&gt;
&lt;td&gt;~5.9 s&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undo restored everything byte-for-byte&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Doing this by hand means opening each mystery PDF, deciding where it goes, renaming it and dragging it. At roughly 20 seconds per file, the 28 files it moved represent about &lt;strong&gt;9 minutes of chores&lt;/strong&gt;, replaced by about 12 seconds of the sloth thinking and one click from me. The app keeps a running "estimated time saved" counter (files moved × seconds per file, adjustable in Settings and clearly labelled as an estimate). Set a daily schedule on Downloads and that counter keeps growing while you do nothing at all, which is the whole point. 🦥&lt;/p&gt;

&lt;p&gt;The misses were reasonable judgement calls rather than disasters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CSV of rent and grocery transactions went to Spreadsheets instead of Finance.&lt;/li&gt;
&lt;li&gt;A README about printer drivers was labelled Installers. The model flagged it as low confidence, so it kept its name and was marked for review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And thanks to Undo, a wrong guess costs one click.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>LocalAudit: an AI security auditor that never sees your secrets and never leaves your laptop</title>
      <dc:creator>Hardik</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:21:01 +0000</pubDate>
      <link>https://dev.to/hardikgaikwad/localaudit-an-ai-security-auditor-that-never-sees-your-secrets-and-never-leaves-your-laptop-2p72</link>
      <guid>https://dev.to/hardikgaikwad/localaudit-an-ai-security-auditor-that-never-sees-your-secrets-and-never-leaves-your-laptop-2p72</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;&lt;strong&gt;LocalAudit&lt;/strong&gt; is a command-line security auditor for source code that runs 100% on your own machine. Point it at a repo and a few seconds later you get a clean HTML report (plus JSON and Markdown) listing hardcoded secrets, injection risks, insecure configuration and unpinned dependencies. Each finding comes with the file and line, the code around it, a plain-English explanation and a concrete fix.&lt;/p&gt;

&lt;p&gt;The design rule I cared most about: &lt;strong&gt;the LLM never detects anything.&lt;/strong&gt; Deterministic rules find the issues: 24 code-pattern rules for Python/Django/Flask and JavaScript/Node, 10 built-in secret detectors (AWS, Stripe, Slack, GitHub, Google, JWTs, private keys, high-entropy strings...) and dependency checks. A local open-weight model then &lt;em&gt;triages&lt;/em&gt; each finding. It explains it, judges whether it's likely a false positive in context, and writes a fix. If the model is unavailable, you still get the full report.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who would pay for this?&lt;/strong&gt; People who need a security pass on code they aren't allowed to upload anywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Freelancers and small agencies&lt;/strong&gt; working on client code under NDA. A fixed-fee "pre-handoff security check" with a professional report as the deliverable is an easy add-on to sell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo founders&lt;/strong&gt; about to launch who can't justify an enterprise SAST subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consultants&lt;/strong&gt; who want a fast first pass before a manual review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all of them, the cloud AI code-review tools have the same problem: &lt;em&gt;your client's source code goes to someone else's server.&lt;/em&gt; LocalAudit's answer is that it doesn't go anywhere.&lt;/p&gt;

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


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/88c9a548370e42b5934df48c00ce654e" width="100%" height="400"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;The demo scans the bundled &lt;code&gt;examples/vulnerable_app&lt;/code&gt;, a deliberately insecure Django + Node app full of &lt;strong&gt;fake&lt;/strong&gt; credentials. The rules-only scan takes about a second and finds 43 issues (4 critical, 16 high), for a risk score of 100/100. A few things worth watching for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secrets are masked everywhere.&lt;/strong&gt; You'll only ever see &lt;code&gt;AKIA****&lt;/code&gt;. Even a private key's body is replaced with &lt;code&gt;****&lt;/code&gt; before any snippet is built, so raw secrets never reach the report, the console or the model's prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model catches a decoy.&lt;/strong&gt; The app contains &lt;code&gt;subprocess.check_output("df -h /", shell=True)&lt;/code&gt;. The rule correctly flags &lt;code&gt;shell=True&lt;/code&gt;, but the command is a constant with no user input. The local model marked it as a &lt;em&gt;likely false positive&lt;/em&gt;. LocalAudit doesn't delete it: it moves it to a separate "Likely false positives" section for a human to confirm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It falls back gracefully.&lt;/strong&gt; Stop Ollama and the tool prints the exact command to fix it (&lt;code&gt;ollama pull qwen2.5-coder:7b&lt;/code&gt;), then carries on with the rules-only report.&lt;/li&gt;
&lt;/ul&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/hardikgaikwad" rel="noopener noreferrer"&gt;
        hardikgaikwad
      &lt;/a&gt; / &lt;a href="https://github.com/hardikgaikwad/localaudit" rel="noopener noreferrer"&gt;
        localaudit
      &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;LocalAudit&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A command-line security audit tool for source code repositories that runs &lt;strong&gt;100% locally&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic rules find the issues&lt;/strong&gt;: built-in secret detection, about 25 code-pattern rules for Python/Django/Flask, JavaScript/Node and generic files, and dependency pinning checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A local LLM triages them&lt;/strong&gt; through &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;. For each finding it explains the issue in plain language, judges whether it is likely a false positive, and suggests a concrete fix.&lt;/li&gt;
&lt;li&gt;The LLM &lt;strong&gt;never detects&lt;/strong&gt; vulnerabilities, and it never deletes a finding. Anything it judges a likely false positive goes into a separate section for you to review.&lt;/li&gt;
&lt;li&gt;The report is complete and useful without the LLM (&lt;code&gt;--no-llm&lt;/code&gt;, or when Ollama isn't running).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Output: a self-contained &lt;code&gt;report.html&lt;/code&gt; (no CDN or external requests, and it prints cleanly to PDF), plus &lt;code&gt;report.json&lt;/code&gt; and &lt;code&gt;report.md&lt;/code&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;60-second quickstart&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;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 1. Get the model (one time, ~4.7 GB)&lt;/span&gt;
ollama pull qwen2.5-coder:7b
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; 2. Install&lt;/span&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/hardikgaikwad/localaudit" 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;ollama pull qwen2.5-coder:7b
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
localaudit scan examples/vulnerable_app
&lt;span class="c"&gt;# open localaudit-report/report.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Useful flags: &lt;code&gt;--no-llm&lt;/code&gt;, &lt;code&gt;--min-severity high&lt;/code&gt;, &lt;code&gt;--max-llm-findings 10&lt;/code&gt;, &lt;code&gt;--model &amp;lt;any Ollama model&amp;gt;&lt;/code&gt;, and &lt;code&gt;--fail-on high&lt;/code&gt; (exits with code 1, for CI).&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The open-source AI at the core:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt; for local inference over its HTTP API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen2.5-Coder 7B&lt;/strong&gt;, an open-weight coding model. It's the default, and you can swap it with &lt;code&gt;--model&lt;/code&gt; or &lt;code&gt;LOCALAUDIT_MODEL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How the pipeline works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan.&lt;/strong&gt; Walk the repo, skipping &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, venvs, build output, binaries, files over 1 MB and anything in &lt;code&gt;.localauditignore&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect and redact.&lt;/strong&gt; Run the secret detectors first and redact every detected value from the file text. Only then run the code rules and build snippets, which is how masking is guaranteed everywhere downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triage.&lt;/strong&gt; Send each finding, highest severity first, to the local model. The request includes the rule description, the file path and the &lt;em&gt;masked&lt;/em&gt; snippet. It runs at temperature 0, and Ollama's &lt;code&gt;format&lt;/code&gt; parameter enforces a JSON schema, so the model must return &lt;code&gt;{explanation, risk_in_context, likely_false_positive, confidence, suggested_fix, fixed_code_example}&lt;/code&gt;. Invalid JSON gets one retry, then the finding is marked "LLM triage unavailable".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache.&lt;/strong&gt; Results are cached on disk, keyed by model + rule + snippet, so reruns are instant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report.&lt;/strong&gt; Render a single self-contained HTML file with no CDNs or external requests, plus a print stylesheet so "Save as PDF" gives a client-ready document.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The stack is deliberately small: Python 3.10+, &lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;jinja2&lt;/code&gt; and pytest, with 110 tests. Those tests include a mocked Ollama covering invalid JSON, timeouts and the server going down mid-run.&lt;/p&gt;

&lt;p&gt;I built it with an AI coding agent (Claude Code) doing much of the implementation while I directed the design and reviewed the results. One moment I liked: testing caught a regex bug where a bare &lt;code&gt;password = "..."&lt;/code&gt; wasn't detected. Because the masker only masks what the detector finds, that password was showing up unmasked as a &lt;em&gt;context line&lt;/em&gt; in a neighbouring finding's snippet. It's fixed now, and there's a regression test for it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For this product, open weights aren't a nice-to-have. They're the reason it can exist.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is the product.&lt;/strong&gt; The customers I described &lt;em&gt;can't&lt;/em&gt; send client code to a third-party API. A local open-weight model is the only way to give them AI explanations without breaking that rule. Nothing leaves the laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The economics work.&lt;/strong&gt; There's no per-token bill, so you can rescan on every commit, or scan a whole agency's worth of repos, for zero marginal cost. A paid API would make a cheap fixed-fee audit service hard to sustain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; Switching models is one flag. When a better open coding model ships, users upgrade by running &lt;code&gt;ollama pull&lt;/code&gt;, not by renegotiating a contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust comes from transparency.&lt;/strong&gt; The rules are plain regexes in a readable list, the model is inspectable, and the report says clearly which parts are deterministic and which are advisory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Honest limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Detection is regex-based and line-by-line,&lt;/strong&gt; so it misses multi-line patterns and logic bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small model can misjudge a finding,&lt;/strong&gt; which is why "false positives" are never deleted and still count toward &lt;code&gt;--fail-on&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed depends on your hardware.&lt;/strong&gt; On my machine the 7B model is split between CPU and GPU and takes about a minute per finding. A GPU that fits the whole model is much faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It isn't a pentest.&lt;/strong&gt; It's a fast, private first pass.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;



&lt;p&gt;[AGENT_SESSION_PLACEHOLDER]&lt;/p&gt;

&lt;p&gt;Changes from the template, and why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"My First User" is removed. You don't have one yet, and the template says to delete the section in that case. It's where the bonus points are, though. If a friend or client runs it on their repo before the deadline, add the section back with what they said, even a sentence or two.&lt;/li&gt;
&lt;li&gt;"Prize Categories" is removed. None of the 17 partner categories apply: LocalAudit uses Qwen, not Gemma, and doesn't use GitHub Copilot or any other parclearly which parts are deterministic and which are advisory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Honest limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Detection is regex-based and line-by-line,&lt;/strong&gt; so it misses multi-line patterns and logic bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small model can misjudge a finding,&lt;/strong&gt; which is why "false positives" are never deleted and still count toward &lt;code&gt;--fail-on&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed depends on your hardware.&lt;/strong&gt; On my machine the 7B model is split between CPU and GPU and takes about a minute per finding. A GPU that fits the whole model is much faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It isn't a pentest.&lt;/strong&gt; It's a fast, private first pass.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Claude Code"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Building LocalAudit: a local-only AI security auditor (Claude Code session)&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Build "LocalAudit": a command-line security audit tool for source code repositories that runs 100% locally. Deterministic rules detect issues, and a local LLM served by Ollama triages and explains them. Build it in the CURRENT working directory (do not create a nested project folder). Keep the scope tight: a small, clean, working tool beats a large, fragile one.&lt;/p&gt;
&lt;h2 id="agent-session-916-0-core-principle"&gt;Core principle&lt;/h2&gt;

&lt;p&gt;The LLM NEVER detects vulnerabilities. Rules find candidate findings; the LLM only (a) explains each finding in plain language, (b) judges whether it is likely a false positive given the surrounding code, and (c) suggests a concrete fix. The tool must still produce a complete, useful report with --no-llm or when Ollama is unreachable.&lt;/p&gt;
&lt;h2 id="agent-session-916-0-stack-keep-it-minimal"&gt;Stack (keep it minimal)&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+, standard library plus &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;jinja2&lt;/code&gt; only. Use argparse for the CLI. Use pytest for tests.&lt;/li&gt;
&lt;li&gt;Ollama HTTP API at &lt;a href="http://localhost:11434" rel="noopener noreferrer"&gt;http://localhost:11434&lt;/a&gt; (/api/chat, stream=false). Default model &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt;, overridable with --model and the env var LOCALAUDIT_MODEL.&lt;/li&gt;
&lt;li&gt;Package layout: &lt;code&gt;localaudit/&lt;/code&gt; (cli.py, scanner.py, rules.py, secrets.py, llm.py, report.py, models.py, templates/report.html.j2), &lt;code&gt;tests/&lt;/code&gt;, &lt;code&gt;examples/vulnerable_app/&lt;/code&gt;, &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt; (installable with a &lt;code&gt;localaudit&lt;/code&gt; console script).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="agent-session-916-0-features"&gt;Features&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Scanner&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Walk the target path; skip .git, node_modules, venv/.venv, &lt;strong&gt;pycache&lt;/strong&gt;, dist, build, binaries, and files over 1 MB. Respect a simple .localauditignore (one glob per line).&lt;/li&gt;
&lt;li&gt;Each Finding has: id, rule_id, title, severity (critical/high/medium/low/info), category, file, line, snippet (the line plus 3 lines of context either side), and the matched text.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Secret detection (secrets.py), built in with no external tools&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regexes for: AWS access key IDs, private key blocks, GitHub tokens, Slack tokens, Stripe live keys, Google API keys, JWTs, and generic &lt;code&gt;password/secret/api_key/token = "&amp;lt;literal&amp;gt;"&lt;/code&gt; assignments.&lt;/li&gt;
&lt;li&gt;A Shannon-entropy check for high-entropy string literals assigned to suspicious variable names.&lt;/li&gt;
&lt;li&gt;Ignore obvious placeholders (changeme, your_key_here, example, xxx, &amp;lt;...&amp;gt;, ${...}, os.environ lookups).&lt;/li&gt;
&lt;li&gt;ALWAYS mask secret values in all output (show the first 4 characters then ****). Real secret values must never appear in the report, the console, or the LLM prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Code-pattern rules (rules.py), a data-driven list of regex rules with language filters, severity, CWE id, and a short description. Include about 15-20 rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python/Django/Flask: DEBUG = True, ALLOWED_HOSTS = ['*'], hardcoded SECRET_KEY, &lt;code&gt;eval&lt;/code&gt;/&lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;pickle.loads&lt;/code&gt;, &lt;code&gt;yaml.load&lt;/code&gt; without SafeLoader, &lt;code&gt;subprocess&lt;/code&gt; with shell=True, &lt;code&gt;os.system&lt;/code&gt;, string-formatted SQL passed to execute()/raw(), &lt;code&gt;verify=False&lt;/code&gt; in requests, weak hashes (md5/sha1) for passwords, CORS_ALLOW_ALL_ORIGINS = True, csrf_exempt, &lt;code&gt;mark_safe&lt;/code&gt;, Flask &lt;code&gt;app.run(debug=True)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;JS/Node: &lt;code&gt;eval(&lt;/code&gt;, &lt;code&gt;child_process.exec&lt;/code&gt; with template strings, &lt;code&gt;innerHTML =&lt;/code&gt;, &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;, &lt;code&gt;rejectUnauthorized: false&lt;/code&gt;, Math.random used for tokens, wildcard CORS.&lt;/li&gt;
&lt;li&gt;Generic: private IPs/internal URLs hardcoded, TODO/FIXME mentioning security or auth, &lt;code&gt;chmod 777&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dependency check (optional, graceful)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse requirements.txt and package.json. Flag unpinned Python dependencies (low severity).&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;pip-audit&lt;/code&gt; is installed, run it on requirements.txt and import its results as findings. If not installed, print a one-line notice and continue. Never fail because a tool is missing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LLM triage (llm.py)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For each finding (highest severity first, capped by --max-llm-findings, default 25), send the rule description, masked snippet, and file path to Ollama.&lt;/li&gt;
&lt;li&gt;Require structured JSON output using Ollama's &lt;code&gt;format&lt;/code&gt; parameter with a JSON schema: {explanation, risk_in_context, likely_false_positive (bool), confidence (low/medium/high), suggested_fix, fixed_code_example}. Temperature 0, num_ctx 4096, request timeout 120s, one retry on invalid JSON, then fall back to marking the finding "LLM triage unavailable".&lt;/li&gt;
&lt;li&gt;Cache results on disk in .localaudit_cache/ keyed by a hash of (model + rule_id + snippet) so reruns are fast.&lt;/li&gt;
&lt;li&gt;Check Ollama availability up front (GET /api/tags). If Ollama is down or the model is not pulled, print the exact command to fix it (&lt;code&gt;ollama pull &amp;lt;model&amp;gt;&lt;/code&gt;) and continue in --no-llm mode instead of crashing.&lt;/li&gt;
&lt;li&gt;The LLM may downgrade a finding as a likely false positive but must never silently delete it. Show it in a separate "Likely false positives" section.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reporting (report.py)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Output a single self-contained HTML report (inline CSS, no external requests, no CDN) with: title and scan metadata (path, date, model used, duration, files scanned), a severity summary with counts, a risk score out of 100, findings grouped by severity (each with file:line, code snippet, explanation, fix, and fix example), a collapsed "Likely false positives" section, and a print stylesheet so Save as PDF from the browser looks clean.&lt;/li&gt;
&lt;li&gt;Also write report.json (machine-readable) and report.md.&lt;/li&gt;
&lt;li&gt;CLI flags: &lt;code&gt;localaudit scan PATH [--model M] [--out DIR] [--no-llm] [--max-llm-findings N] [--min-severity LEVEL] [--format html,json,md] [--fail-on LEVEL]&lt;/code&gt;. &lt;code&gt;--fail-on&lt;/code&gt; returns exit code 1 if any finding is at or above that severity (for CI use). Show a concise coloured console summary and a progress indicator during LLM triage.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Demo and tests&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;examples/vulnerable_app/&lt;/code&gt;: a small intentionally vulnerable Django-style app and a small Node file, with fake credentials, that triggers most of the rules. Add a prominent comment that it is intentionally insecure.&lt;/li&gt;
&lt;li&gt;pytest tests: each rule fires on a known-bad snippet and does not fire on a safe one; secrets are masked; placeholders are ignored; the scanner finds the expected findings in the example app; the LLM module is tested with a mocked HTTP response (including invalid JSON and Ollama-down cases); --no-llm end-to-end run produces all three report f
[... truncated 1197 chars]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Plan: models → rules/secrets → scanner → report (with --no-llm) → tests → LLM module → README and example app → run tests, run the scan, check whether Ollama is available.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "ls -la &amp;amp;&amp;amp; python --version &amp;amp;&amp;amp; pip --version &amp;amp;&amp;amp; python -c \"impor...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "ls -la &amp;amp;&amp;amp; python --version &amp;amp;&amp;amp; pip --version &amp;amp;&amp;amp; python -c \"import requests, jinja2, pytest; print('ok')\"; which pip-audit ollama; curl - [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;total 0
drwxr-xr-x 1 &amp;lt;user&amp;gt; 197609 0 Oct 16 13:04 .
drwxr-xr-x 1 &amp;lt;user&amp;gt; 197609 0 Nov  3  2025 ..
Python 3.14.7
pip 26.2 [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Empty directory; Python 3.14; jinja2 is missing; Ollama is installed but not answering on :11434 right now. Writing the package now.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "mkdir -p localaudit/templates tests examples/vulnerable_app &amp;amp;&amp;amp; c...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "mkdir -p localaudit/templates tests examples/vulnerable_app &amp;amp;&amp;amp; cat &amp;gt; requirements.txt &amp;lt;&amp;lt;'EOF'\nrequests&amp;gt;=2.28\njinja2&amp;gt;=3.1\nEOF\ncat &amp;gt; py [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;(Bash completed with no output)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\models.py", "content": "\"\"...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\models.py", "content": "\"\"\"Core data types shared across LocalAudit.\"\"\"\n\nfrom __future__ impo [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\models.py (file state is current in your context — no need  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\secrets.py", "content": "\"\...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\secrets.py", "content": "\"\"\"Built-in secret detection: provider-specific regexes, generic assignme [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\secrets.py (file state is current in your context — no need [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Fixing one thing: provider-specific keys (e.g. a live Stripe key used as an &lt;code&gt;os.environ.get&lt;/code&gt; default) should still be caught; only the generic/entropy checks should skip env lookups.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/secrets.py'\ns=open(p).read()\ns...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/secrets.py'\ns=open(p).read()\ns=s.replace(\"\"\"    for idx, line in enumerate(lines, start=1):\n        [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;(Bash completed with no output)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the code-pattern rules.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\rules.py", "content": "\"\"\...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\rules.py", "content": "\"\"\"Data-driven code-pattern rules.\n\nTo add a rule, append a `Rule(...)` t [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\rules.py (file state is current in your context — no need t [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Adding &lt;code&gt;snippet_start&lt;/code&gt; to Finding so reports can number snippet lines, then the scanner (including dependency checks).&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/models.py'\ns=open(p).read()\ns=...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/models.py'\ns=open(p).read()\ns=s.replace(\"\"\"    snippet: str\n    match: str\n    description\"\"\",\ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;(Bash completed with no output)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanner.py", "content": "\"\...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanner.py", "content": "\"\"\"Walk a target path and run secret, code-pattern and dependency checks. [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\scanner.py (file state is current in your context — no need [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Making dependency checks also work on redacted text (a requirements line could carry a tokenized URL):&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/scanner.py'\ns=open(p).read()\no...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/scanner.py'\ns=open(p).read()\nold=\"\"\"        name = path.name.lower()\n        if name.startswith(\"r [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;(Bash completed with no output)&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;That package.json line is needlessly convoluted; simplifying it.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanne...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanner.py", "old_string": "                result.findings.extend(check_packag [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;The file &amp;lt;projects&amp;gt;\LocalSec\localaudit\scanner.py has been updated successfully.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the reporting module and HTML template.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\report.py", "content": "\"\"...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\report.py", "content": "\"\"\"Render scan results as self-contained HTML, JSON and Markdown.\"\"\"\n\ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\report.py (file state is current in your context — no need  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\templates\\report.html.j2", ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\templates\\report.html.j2", "content": "&amp;lt;!doctype html&amp;gt;\n&amp;lt;html lang=\"en\"&amp;gt;\n&amp;lt;head&amp;gt;\n&amp;lt;meta charset=\" [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\templates\report.html.j2 (file state is current in your con [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the LLM triage module.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\llm.py", "content": "\"\"\"L...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\llm.py", "content": "\"\"\"Local LLM triage through the Ollama HTTP API.\n\nThe model never detects i [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\llm.py (file state is current in your context — no need to  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Progress should show &lt;em&gt;before&lt;/em&gt; each (possibly slow) request, not after. Adjusting:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/llm.py'\ns=open(p).read()\ns=s.r...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/llm.py'\ns=open(p).read()\ns=s.replace(\"progress: Optional[Callable[[int, int, Finding, bool], None]] =  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;207:                    progress: Optional[Callable[[int, int, Finding], None]] = None) -&amp;gt; dict:
219:        if progress [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the CLI.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\cli.py", "content": "\"\"\"C...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\cli.py", "content": "\"\"\"Command-line interface: `localaudit scan PATH ...`.\"\"\"\n\nfrom __future [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\localaudit\cli.py (file state is current in your context — no need to  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt; localaudit/__main__.py &amp;lt;&amp;lt;'EOF'\nimport sys\n\nfrom .cli im...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt; localaudit/__main__.py &amp;lt;&amp;lt;'EOF'\nimport sys\n\nfrom .cli import main\n\nsys.exit(main())\nEOF\nmkdir -p examples/vulnerable_app/shop [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;done&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Install and do a first rules-only scan:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "pip install -q -e \".[dev]\" 2&amp;gt;&amp;amp;1 | tail -3; localaudit scan exa...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "pip install -q -e \".[dev]\" 2&amp;gt;&amp;amp;1 | tail -3; localaudit scan examples/vulnerable_app --no-llm --out \"$TEMP/la-out\" 2&amp;gt;&amp;amp;1", "description" [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Exit code 127
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script- [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The console script installed to a user Scripts dir that isn't on PATH; I'll call it by full path for now (and mention it at the end).&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"; \"$LA\" scan examples/vulnerable_app --no-llm --out \"$TEMP/la-out\"; e [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Exit code 1
Traceback (most recent call last):
  File "&amp;lt;frozen runpy&amp;gt;", line 203, in _run_module_as_main
  File "&amp;lt;froz [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The heredoc turned &lt;code&gt;\\n&lt;/code&gt; into a real newline. Fixing:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanner.py", "offset": 264, ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;projects&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;LocalSec&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;localaudit&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;scanner.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;264          name = path.name.lower()
265         is_req = name.startswith("requirements") and name.endswith(".txt")
266  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanne...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\localaudit\\scanner.py", "old_string": "            safe_text = \"\n\".join(", "new_string" [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;The file &amp;lt;projects&amp;gt;\LocalSec\localaudit\scanner.py has been updated successfully.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"; \"$LA\" scan examples/vulnerable_app --no-llm --out \"$TEMP/la-out\"; e [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;LocalAudit 0.1.0  scanning &amp;lt;projects&amp;gt;\LocalSec\examples\vulnerable_app
Scanned 10 files (0 skipped), 42 findings at or  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;42 findings, secrets masked. One miss: &lt;code&gt;"PASSWORD": "Sup3****"&lt;/code&gt; in the settings dict wasn't flagged. Debugging:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python -c \"\nfrom localaudit import secrets as s\nline='       ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python -c \"\nfrom localaudit import secrets as s\nline='        \\\"PASSWORD\\\": \\\"Sup3****\\\",'\nprint(s.GENERIC_ASSIGNMENT.search( [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;None
False None

&amp;gt; pip-audit not installed; skipping known-vulnerability check (pip install pip-audit to enable).

##  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python -c \"\nfrom localaudit import secrets as s\nimport re\npr...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python -c \"\nfrom localaudit import secrets as s\nimport re\nprint(repr(s.GENERIC_ASSIGNMENT.pattern))\nfor l in ['PASSWORD: \\\"Sup3*** [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;'(?ix)\n    ["\']?(?P&amp;lt;name&amp;gt;\\b[A-Za-z_][A-Za-z0-9_]*?(?:password|passwd|pwd|secret|api_?key|token)[A-Za-z0-9_]*)\\b["\'] [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Bug: &lt;code&gt;\b[A-Za-z_]&lt;/code&gt; must consume one char before the keyword, so a bare &lt;code&gt;password&lt;/code&gt;/&lt;code&gt;token&lt;/code&gt;/&lt;code&gt;secret&lt;/code&gt; name never matched. Fixing both regexes:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "sed -i 's/(?P&amp;lt;name&amp;gt;\\\\b\\[A-Za-z_\\]\\[A-Za-z0-9_\\]\\*?/(?P&amp;lt;na...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "sed -i 's/(?P&amp;lt;name&amp;gt;\\\\b\\[A-Za-z_\\]\\[A-Za-z0-9_\\]\\*?/(?P&amp;lt;name&amp;gt;\\\\b[A-Za-z0-9_]*?/' localaudit/secrets.py &amp;amp;&amp;amp; grep -n \"P&amp;lt;name&amp;gt;\" loc [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;80:    ["']?(?P&amp;lt;name&amp;gt;\b[A-Za-z_][A-Za-z0-9_]*?(?:password|passwd|pwd|secret|api_?key|token)[A-Za-z0-9_]*)\b["']?
89:     [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Sed didn't match; using a Python replace instead.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/secrets.py'\ns=open(p).read()\no...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'EOF'\np='localaudit/secrets.py'\ns=open(p).read()\nold=r'(?P&amp;lt;name&amp;gt;\\b[A-Za-z_][A-Za-z0-9_]*?'\nassert s.count(old)==2\ns=s.re [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;PASSWORD: "Sup3****" [('secret-generic-assignment', 'Sup3****')]
password = "Sup3****" [('secret-generic-assignment', ' [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now the tests.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_rules.py", "content": "impor...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_rules.py", "content": "import pytest\n\nfrom localaudit.rules import RULES, get_rule, rules_for\n\n#  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\tests\test_rules.py (file state is current in your context — no need t [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_secrets.py", "content": "imp...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_secrets.py", "content": "import pytest\n\nfrom localaudit import secrets\nfrom localaudit.scanner imp [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\tests\test_secrets.py (file state is current in your context — no need [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_scanner.py", "content": "fro...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_scanner.py", "content": "from pathlib import Path\n\nfrom localaudit import scanner\nfrom localaudit. [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\tests\test_scanner.py (file state is current in your context — no need [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_llm.py", "content": "import ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_llm.py", "content": "import json\n\nimport pytest\nimport requests\n\nfrom localaudit import llm, rep [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\tests\test_llm.py (file state is current in your context — no need to  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_cli.py", "content": "import ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\tests\\test_cli.py", "content": "import json\nimport re\nfrom pathlib import Path\n\nimport pytest\n\nfrom locala [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;File created successfully at: &amp;lt;projects&amp;gt;\LocalSec\tests\test_cli.py (file state is current in your context — no need to  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python -m pytest -q 2&amp;gt;&amp;amp;1 | tail -40", "description": "Run the te...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python -m pytest -q 2&amp;gt;&amp;amp;1 | tail -40", "description": "Run the test suite"}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;.............F.......................................................... [ 65%]
......................................  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The three test findings share a snippet, so the cache answered them (correct behavior — the test was wrong). Giving each a distinct snippet:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'EOF'\np='tests/test_llm.py'\ns=open(p).read()\nold='...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'EOF'\np='tests/test_llm.py'\ns=open(p).read()\nold='''    findings = [make_finding(severity=\"low\", line=1), make_finding(se [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;........................................................................ [ 65%]
......................................  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;110 passing. Now I want to actually look at the HTML report. Checking for a headless browser to screenshot it:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"; \"$LA\" scan examples/vulnerable_app --no-llm --out \"$TEMP/la-out\" &amp;gt;/ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe
/c/Program Files/Google/Chrome/Application/chrome.exe&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "SP=\"~/AppData/Local/Temp/claude/&amp;lt;project-id&amp;gt;/5982a5be-821b-4a50...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "SP=\"~/AppData/Local/Temp/claude/&amp;lt;project-id&amp;gt;/5982a5be-821b-4a50-8648-c43f4c26c75d/scratchpad\"; mkdir -p \"$SP\"; CH=\"/c/Program Files/ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;total 452
drwxr-xr-x 1 &amp;lt;user&amp;gt; 197609      0 Oct 16 13:20 .
drwxr-xr-x 1 &amp;lt;user&amp;gt; 197609      0 Oct 16 13:07 ..
-rw-r--r--  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "~\\AppData\\Local\\Temp\\claude\\&amp;lt;project-id&amp;gt;\\5982a5be-821b-4...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;AppData&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Local&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Temp&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;claude&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;project-id&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;5982a5be-821b-4a50-8648-c43f4c26c75d&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;scratchpad&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;shot.png"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;[image]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "~\\AppData\\Local\\Temp\\claude\\&amp;lt;project-id&amp;gt;\\5982a5be-821b-4...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;AppData&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Local&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Temp&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;claude&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;project-id&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;5982a5be-821b-4a50-8648-c43f4c26c75d&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;scratchpad&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;report.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1-2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;pdftoppm is not installed. Install poppler-utils (e.g. `brew install poppler` or `apt-get install poppler-utils`) to ena [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "curl -s -m 3 http://localhost:11434/api/tags; echo \"rc=$?\"; ls...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "curl -s -m 3 http://localhost:11434/api/tags; echo \"rc=$?\"; ls ~/.ollama/models/manifests/registry.ollama.ai/library/ 2&amp;gt;/dev/null; ls ~ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Exit code 2
{"models":[{"name":"qwen2.5-coder:3b","model":"qwen2.5-coder:3b","modified_at":"2026-10-16T13:15:26+05:30"," [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Ollama is up now and &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; is pulled, so I'm running a real LLM scan (default cap: 25 findings):&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "LA=\"~/AppData/Roaming/Python/Python314/Scripts/localaudit.exe\"; time \"$LA\" scan examples/vulnerable_app --out \"$TEMP/la-llm\" 2&amp;gt;&amp;amp;1 | [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Command did not complete within its 600s timeout and was moved to the background (ID: bcv7500is). Output is being writte [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;It's slow (&amp;gt;10 min so far). While it runs, I'll check its progress and write the README.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "ls .localaudit_cache | wc -l; tail -c 600 \"~/AppData/Local/Temp...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "ls .localaudit_cache | wc -l; tail -c 600 \"~/AppData/Local/Temp/claude/&amp;lt;project-id&amp;gt;/f0d03b42-0e55-44bb-ae6d-75f69b9bc920/tasks/bcv7500is [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;11

NAME                ID              SIZE      PROCESSOR          CONTEXT    UNTIL              
qwen2.5-coder:7b     [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;About 55 s per finding: the 7B model is split 55/45 CPU/GPU on this machine, so 11 of the 25 findings are done so far. Writing the README in the meantime.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;Write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "content": "# LocalAudit\n\n...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "content": "# LocalAudit\n\nA command-line security audit tool for source code repositories that runs [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;The server-side auto mode classifier gave no verdict (it skipped this action), so auto mode cannot determine the safety  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "ls .localaudit_cache | wc -l", "description": "Count cached tria...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "ls .localaudit_cache | wc -l", "description": "Count cached triage results"}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;12&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-toolsearch"&gt;ToolSearch&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"query": "select:Monitor", "max_results": 1}&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"select:Monitor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;[image]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-monitor"&gt;Monitor&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "until [ -f \"$TEMP/la-llm/report.json\" ]; do sleep 15; done; ec...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"command": "until [ -f \"$TEMP/la-llm/report.json\" ]; do sleep 15; done; echo \"LLM scan finished\"; ls &amp;lt;projects&amp;gt;/LocalSec/.localaudit_cache | wc - [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Monitor started (task b8oml6ciw, expires in 30m unless the source ends first; you get one notice at expiry — re-arm if y [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The monitor is armed and I'll be notified when the report lands. Meanwhile I'm checking that the &lt;code&gt;python -m localaudit&lt;/code&gt; fallback works, since the console script isn't on PATH here:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python -m localaudit scan examples/vulnerable_app --no-llm --for...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python -m localaudit scan examples/vulnerable_app --no-llm --format md --out \"$TEMP/la-m\" --fail-on high | tail -3; echo \"exit=$?\"; p [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;wrote ~\AppData\Local\Temp\la-m\report.md
FAIL: 20 finding(s) at or above 'high'.
exit=0
localaudit 0.1.0&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;(&lt;code&gt;exit=0&lt;/code&gt; there is from &lt;code&gt;tail&lt;/code&gt;, which ends the pipe; the exit code 1 itself is covered by &lt;code&gt;test_fail_on&lt;/code&gt;.) Waiting for the LLM scan to finish.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;write the README with bash instead&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll write the README with a bash heredoc, as you asked. I also see that you stopped the LLM scan and its monitor.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt; README.md &amp;lt;&amp;lt;'EOF'\n# LocalAudit\n\nA command-line security...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt; README.md &amp;lt;&amp;lt;'EOF'\n# LocalAudit\n\nA command-line security audit tool for source code repositories that runs **100% locally**.\n\n- [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Exit code 2
/usr/bin/bash: -c: line 102: unexpected EOF while looking for matching `''&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The heredoc didn't parse, probably because of the apostrophes in the body. Checking with a minimal test:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt; \"$TEMP/t.txt\" &amp;lt;&amp;lt;'EOF'\nit's `x` and \"y\" ${z} \\b\nEOF\...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt; \"$TEMP/t.txt\" &amp;lt;&amp;lt;'EOF'\nit's `x` and \"y\" ${z} \\b\nEOF\ncat \"$TEMP/t.txt\"", "description": "Test heredoc with apostrophes and  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;it's `x` and "y" ${z} \b&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;A small heredoc works, so something in the body is breaking it. Writing the README in parts to find the culprit:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n# LocalAudit\n\nA command-line s...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n# LocalAudit\n\nA command-line security audit tool for source code repositories that runs **100% locally* [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;57 README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt;&amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n## What it checks\n\n**Secrets*...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt;&amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n## What it checks\n\n**Secrets** (`localaudit/secrets.py`, no external tools): AWS access key IDs, priva [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;81 README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt;&amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n## LLM triage\n\nFor each findi...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt;&amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n## LLM triage\n\nFor each finding, highest severity first up to `--max-llm-findings`, LocalAudit sends t [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;102 README.md&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt;&amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n## Adding a rule\n\nAppend a `R...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt;&amp;gt; README.md &amp;lt;&amp;lt;'README_END'\n## Adding a rule\n\nAppend a `Rule` to `RULES` in `localaudit/rules.py`:\n\n```python\nRule(\"py-tempfil [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;136 README.md
9
23&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python -c \"\nimport json,glob\nfor p in glob.glob('.localaudit_...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python -c \"\nimport json,glob\nfor p in glob.glob('.localaudit_cache/*.json'):\n    t=json.load(open(p))\n    print(('FP ' if t['likely_ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;hig | The code contains a hardcoded GitHub personal access token, which is a critical security risk.  || FIX: Remove the [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;LocalAudit is built in the current directory and the README is now written. All 110 tests pass, and the rules-only scan of the demo app produces all three reports with every secret masked. A full scan with the LLM never finished, because you stopped it after 23 of the 25 findings had been triaged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules-only scan of &lt;code&gt;examples/vulnerable_app&lt;/code&gt;:&lt;/strong&gt; 43 findings (4 critical, 16 high, 15 medium, 8 low) and a risk score of 100/100. I viewed the HTML report in headless Chrome and it renders cleanly. I also checked every output (HTML, JSON, Markdown, console) for each fake secret, and none of the real values appear anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM run (&lt;code&gt;qwen2.5-coder:7b&lt;/code&gt;):&lt;/strong&gt; about 55 seconds per finding, because the model is split 55% CPU / 45% GPU on your machine. All 23 results I read from the cache were valid JSON and made sense:&lt;br&gt;
- The SQL injection, pickle, eval and secret findings were explained correctly, with the right fixes (parameterised queries, &lt;code&gt;yaml.safe_load&lt;/code&gt;, &lt;code&gt;execFile&lt;/code&gt;, environment variables).&lt;br&gt;
- It correctly marked the deliberate decoy, &lt;code&gt;subprocess.check_output("df -h /", shell=True)&lt;/code&gt; with no user input, as a likely false positive.&lt;br&gt;
- The run never wrote a report, so I haven't seen the "Likely false positives" section filled with real model output. That path is covered by a mocked test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to run it:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;&lt;br&gt;
pip install -e ".[dev]"&lt;br&gt;
python -m localaudit scan examples/vulnerable_app --no-llm&lt;br&gt;
python -m localaudit scan examples/vulnerable_app --max-llm-findings 5   # with the LLM&lt;br&gt;
pytest&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
On your machine pip put &lt;code&gt;localaudit.exe&lt;/code&gt; in &lt;code&gt;~\AppData\Roaming\Python\Python314\Scripts&lt;/code&gt;, which isn't on PATH. Add that folder to PATH to use the &lt;code&gt;localaudit&lt;/code&gt; command directly, or use &lt;code&gt;python -m localaudit&lt;/code&gt; as above. A rerun of the LLM scan reuses the 23 cached results, so only the remaining 2 hit the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things I fixed or decided along the way:&lt;/strong&gt;&lt;br&gt;
- I found and fixed a bug where bare names like &lt;code&gt;password = "..."&lt;/code&gt; weren't detected as secrets. Because of it, an unmasked password was showing up as a context line in another finding's snippet. There's now a regression test for this.&lt;br&gt;
- Live provider keys (Stripe, AWS and so on) are still flagged when used as an &lt;code&gt;os.environ.get&lt;/code&gt; default. Only the generic checks skip environment lookups.&lt;br&gt;
- &lt;code&gt;--fail-on&lt;/code&gt; also counts findings the LLM marked as likely false positives, so the model can't turn a CI gate green on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not verified:&lt;/strong&gt;&lt;br&gt;
- pip-audit isn't installed. The "not installed" notice is checked on a real scan, but importing pip-audit results is only tested with a mocked output.&lt;br&gt;
- I didn't look at the PDF output of the print stylesheet (no PDF renderer here). Chrome produced the PDF without errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small deviations from the spec:&lt;/strong&gt;&lt;br&gt;
- The Django &lt;code&gt;SECRET_KEY&lt;/code&gt; check sits in &lt;code&gt;secrets.py&lt;/code&gt; rather than &lt;code&gt;rules.py&lt;/code&gt;, so its value gets masked like any other secret.&lt;br&gt;
- The dependency checks live in &lt;code&gt;scanner.py&lt;/code&gt;.&lt;br&gt;
- I added &lt;code&gt;localaudit/__main__.py&lt;/code&gt; so &lt;code&gt;python -m localaudit&lt;/code&gt; works.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Is this a command line based tool?&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Yes, it's purely a command-line tool. There's no GUI, web server or interactive mode. You run one command, it scans and writes its reports, then exits:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;python -m localaudit scan PATH [--model M] [--out DIR] [--no-llm] [--max-llm-findings N]
                               [--min-severity LEVEL] [--format html,json,md] [--fail-on LEVEL]
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terminal output:&lt;/strong&gt; a coloured summary with counts by severity, the risk score and the top findings, plus a progress line while the LLM triages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files:&lt;/strong&gt; &lt;code&gt;report.html&lt;/code&gt;, &lt;code&gt;report.json&lt;/code&gt; and &lt;code&gt;report.md&lt;/code&gt; in the output folder (&lt;code&gt;./localaudit-report&lt;/code&gt; by default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit code:&lt;/strong&gt; &lt;code&gt;0&lt;/code&gt; normally, &lt;code&gt;1&lt;/code&gt; when &lt;code&gt;--fail-on&lt;/code&gt; is triggered, &lt;code&gt;2&lt;/code&gt; for bad arguments. That lets it run in CI scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The HTML report is the only visual part. It's a static file you open in a browser after the scan, not a running app.&lt;/p&gt;

&lt;p&gt;Once the Scripts folder is on your PATH (see my last message), you can type &lt;code&gt;localaudit scan ...&lt;/code&gt; instead of &lt;code&gt;python -m localaudit scan ...&lt;/code&gt;. Both run the same entry point.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;what codebase did you test it with?&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;I only tested it on code I wrote myself during this session. I haven't run it on any real-world codebase. Specifically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;examples/vulnerable_app/&lt;/code&gt;&lt;/strong&gt;, the demo app I wrote for this project: about 10 files, made up of a Django-style &lt;code&gt;settings.py&lt;/code&gt;, &lt;code&gt;views.py&lt;/code&gt; and &lt;code&gt;integrations.py&lt;/code&gt;, plus a Node &lt;code&gt;server.js&lt;/code&gt;, a React &lt;code&gt;widget.jsx&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;deploy.sh&lt;/code&gt;. It's deliberately vulnerable, with fake credentials. All the scan results I reported (the 43 findings and the LLM triage) come from this app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-line snippets in the unit tests&lt;/strong&gt;, a known-bad and a safe example for each rule and secret pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small temporary folders created by the tests&lt;/strong&gt;, to check the skipped folders, binary and oversized files, and &lt;code&gt;.localauditignore&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a real limitation. I wrote the rules and the demo app together, so the app naturally triggers them. The run tells you the tool works end to end. It doesn't tell you how it performs on code it wasn't designed around: how many false positives it produces, what it misses, how fast it runs on thousands of files, or how it handles unusual encodings and minified files.&lt;/p&gt;

&lt;p&gt;To get a meaningful answer, the next step is a rules-only scan of a real project. It's quick and makes no LLM calls. A few options:&lt;br&gt;
- &lt;strong&gt;One of your own repositories.&lt;/strong&gt; This is the most useful, because you can judge which findings are real.&lt;br&gt;
- &lt;strong&gt;A well-known open-source Django or Express project.&lt;/strong&gt; I'd need to clone it, which means a network download.&lt;br&gt;
- &lt;strong&gt;LocalAudit's own source.&lt;/strong&gt; This is the quickest. It should be mostly clean, but the test files are full of deliberately bad snippets, so it's a good false-positive check.&lt;/p&gt;

&lt;p&gt;Do you want me to run one of these? If you have a repository in mind, give me the path.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I have a project that i need to be audited, How can I test that? The codebase is locally saved&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Point the tool at the project folder. Start with a quick rules-only scan, then add the LLM for the most serious findings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Rules-only scan (seconds, no Ollama needed)&lt;/strong&gt;&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;cd &amp;lt;projects&amp;gt;\LocalSec
python -m localaudit scan "D:\path\to\your\project" --no-llm --out "D:\audit\myproject"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use your project's real path, and put quotes around it if it contains spaces. Writing the reports outside the project keeps them out of your repository. When it finishes, open &lt;code&gt;D:\audit\myproject\report.html&lt;/code&gt; in a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reduce the noise if needed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a lot of findings come from folders that aren't your code (vendored libraries, generated files, test fixtures), create a file called &lt;code&gt;.localauditignore&lt;/code&gt; in the project's root folder:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;vendor/
static/libs/
*.min.js
tests/fixtures/*
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then run the scan again. &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;venv&lt;/code&gt;, &lt;code&gt;dist&lt;/code&gt; and &lt;code&gt;build&lt;/code&gt; are already skipped automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add LLM triage for the worst findings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure Ollama is running, then:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;python -m localaudit scan "D:\path\to\your\project" --min-severity high --max-llm-findings 10 --out "D:\audit\myproject"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On your machine the 7B model takes about a minute per finding, so 10 findings is roughly 10 minutes. For quicker but rougher triage, add &lt;code&gt;--model qwen2.5-coder:3b&lt;/code&gt;. Results are cached, so running the same scan again is fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to keep in mind&lt;/strong&gt;&lt;br&gt;
- &lt;strong&gt;Nothing leaves your machine.&lt;/strong&gt; The scan and the model both run locally, and secrets are masked in the reports.&lt;br&gt;
- &lt;strong&gt;The one exception is pip-audit.&lt;/strong&gt; If you install it, it looks up your dependency names and versions online. It isn't installed right now, so nothing goes out.&lt;br&gt;
- &lt;strong&gt;This is the tool's first run on real code.&lt;/strong&gt; Expect some false positives, and some real issues it can't catch, such as multi-line patterns, logic flaws and broken access control.&lt;br&gt;
- &lt;strong&gt;It's a first pass, not a full audit.&lt;/strong&gt; Treat the findings as a checklist to review, not as a verdict. If the project is going to production or handles sensitive data, it still needs a proper review.&lt;/p&gt;

&lt;p&gt;If you tell me the project's path, I can run the rules-only scan, go through the findings with you, and tune the ignore file or rules wherever they're noisy.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;PS ~&amp;gt; python -m localaudit scan "&amp;lt;projects&amp;gt;\getpyqjec" --min-severity high --max-findings 10 --out "&amp;lt;projects&amp;gt;"&lt;br&gt;
usage: localaudit [-h] [--version] {scan} ...&lt;br&gt;
localaudit: error: unrecognized arguments: --max-findings 10&lt;br&gt;
PS ~&amp;gt; localaudit scan "&amp;lt;projects&amp;gt;\getpyqjec" --min-severity high --max-findings 10 --out "&amp;lt;projects&amp;gt;"&lt;br&gt;
localaudit : The term 'localaudit' is not recognized&lt;br&gt;
as the name of a cmdlet, function, script file, or&lt;br&gt;
operable program. Check the spelling of the name, or&lt;br&gt;
if a path was included, verify that the path is&lt;br&gt;
correct and try again.&lt;br&gt;
At line:1 char:1&lt;br&gt;
+ localaudit scan "&amp;lt;projects&amp;gt;\getpyqjec"&lt;br&gt;
--min-severity high  ...&lt;br&gt;
+ ~~~~~~~~~~&lt;br&gt;
    + CategoryInfo          : ObjectNotFound: (localau&lt;br&gt;
   dit:String) [], CommandNotFoundException&lt;br&gt;
    + FullyQualifiedErrorId : CommandNotFoundException&lt;/p&gt;

&lt;p&gt;PS ~&amp;gt;&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;There were two separate problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The option name was wrong (my fault).&lt;/strong&gt; I mixed up the name in my message at one point. The correct option is &lt;code&gt;--max-llm-findings&lt;/code&gt;, not &lt;code&gt;--max-findings&lt;/code&gt;. The &lt;code&gt;python -m localaudit&lt;/code&gt; form did work: it started and only rejected that one option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;localaudit&lt;/code&gt; isn't recognized&lt;/strong&gt; because its install folder isn't on your PATH. Use &lt;code&gt;python -m localaudit&lt;/code&gt; for now.&lt;/p&gt;

&lt;p&gt;Run this:&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localaudit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;projects&amp;gt;\getpyqjec"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--min-severity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--max-llm-findings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;projects&amp;gt;\getpyqjec-audit"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I also changed &lt;code&gt;--out&lt;/code&gt; to a new subfolder. With &lt;code&gt;--out "&amp;lt;projects&amp;gt;"&lt;/code&gt;, the three report files would go straight into your Project folder alongside your other projects. When it finishes, open &lt;code&gt;&amp;lt;projects&amp;gt;\getpyqjec-audit\report.html&lt;/code&gt;. Make sure Ollama is running first, and expect up to about 10 minutes for the LLM step.&lt;/p&gt;

&lt;p&gt;If you want plain &lt;code&gt;localaudit ...&lt;/code&gt; to work, run this once in PowerShell, then open a new terminal:&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;SetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;;~\AppData\Roaming\Python\Python314\Scripts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you'd rather I run the scan here and go through the findings with you, just say so.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;What is the command to run the application on examples you created, because i would not like to record my project findings and post it on dev&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Run these from the LocalSec folder, since the demo app path is relative to it:&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;\LocalSec&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Rules only (finishes in about a second)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localaudit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples\vulnerable_app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--no-llm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;demo-report&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# With LLM triage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localaudit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples\vulnerable_app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--max-llm-findings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;demo-report&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then open &lt;code&gt;demo-report\report.html&lt;/code&gt; in a browser.&lt;/p&gt;

&lt;p&gt;For a recording:&lt;br&gt;
- &lt;strong&gt;Everything in the demo is safe to show.&lt;/strong&gt; The credentials are fake and masked in the output.&lt;br&gt;
- &lt;strong&gt;The LLM run will look instant.&lt;/strong&gt; 23 results from the earlier run are cached, so &lt;code&gt;--max-llm-findings 10&lt;/code&gt; answers straight from the cache.&lt;br&gt;
- &lt;strong&gt;To show the model working live,&lt;/strong&gt; first run &lt;code&gt;Remove-Item -Recurse .localaudit_cache&lt;/code&gt;. Each finding then takes about a minute on your machine, so a small cap like &lt;code&gt;--max-llm-findings 3&lt;/code&gt; keeps the video short.&lt;br&gt;
- &lt;strong&gt;Good spots to point out in the report:&lt;/strong&gt; the &lt;code&gt;subprocess ... "df -h /"&lt;/code&gt; finding is the decoy the model marked as a likely false positive, and the private key block shows its body replaced with &lt;code&gt;****&lt;/code&gt;.&lt;br&gt;
- &lt;strong&gt;The report header shows your local path&lt;/strong&gt; (&lt;code&gt;&amp;lt;projects&amp;gt;\LocalSec\...&lt;/code&gt;). If that matters to you, crop it or run from a neutral folder.&lt;/p&gt;

&lt;p&gt;I can also draft the DEV post text for you to review.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;give me steps to connect this to a fresh github repo and push everything with commit statement&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Here are the steps. Run everything in PowerShell from the project folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create an empty repository on GitHub&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to github.com/new and name it (for example &lt;code&gt;localaudit&lt;/code&gt;). Leave &lt;strong&gt;all&lt;/strong&gt; of the "Add README / .gitignore / license" boxes unticked, because the project already has those files and an extra initial commit on GitHub would make the first push fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Commit and push&lt;/strong&gt;&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;\LocalSec&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Keep demo report output out of the repo (build artefacts and the LLM cache are already ignored)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gitignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo-report/"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c"&gt;# check the list: no demo-report, .localaudit_cache, __pycache__ or *.egg-info&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Initial release of LocalAudit: local-only security audit CLI"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deterministic rules detect secrets, insecure code patterns and unpinned dependencies; a local Ollama model only explains findings, flags likely false positives and suggests fixes. Outputs self-contained HTML, JSON and Markdown reports. Includes an intentionally vulnerable demo app and a pytest suite."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/YOUR-USERNAME/localaudit.git&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Replace &lt;code&gt;YOUR-USERNAME&lt;/code&gt; with your GitHub username. If &lt;code&gt;git commit&lt;/code&gt; complains that it doesn't know who you are, run these once and commit again:&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user.name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user.email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;3. GitHub may block the push at first&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The demo app and tests contain fake keys in real formats, such as &lt;code&gt;ghp_...&lt;/code&gt;, &lt;code&gt;sk_live_...&lt;/code&gt;, &lt;code&gt;xoxb-...&lt;/code&gt; and &lt;code&gt;AIza...&lt;/code&gt;. GitHub's secret-scanning push protection may reject the push with a message like "Push cannot contain secrets". That is expected, because the demo is designed to trip exactly these detectors. To get past it:&lt;br&gt;
- The error lists each detected value with a link. Open each link and choose &lt;strong&gt;"It's used in tests"&lt;/strong&gt; to allow it.&lt;br&gt;
- Run &lt;code&gt;git push -u origin main&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;All of these values are made up. Still, read the list in the error before allowing anything, to confirm each flagged file is under &lt;code&gt;examples/vulnerable_app/&lt;/code&gt; or &lt;code&gt;tests/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have the GitHub CLI (&lt;code&gt;gh&lt;/code&gt;) installed and logged in, you can skip creating the repo on the website. After the &lt;code&gt;git commit&lt;/code&gt; line, run &lt;code&gt;gh repo create localaudit --public --source . --push&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I can also run the git steps here for you. You would still create the repo and approve the push yourself.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;git push -u origin main&lt;br&gt;
Enumerating objects: 38, done.&lt;br&gt;
Counting objects: 100% (38/38), done.&lt;br&gt;
Delta compression using up to 20 threads&lt;br&gt;
Compressing objects: 100% (35/35), done.&lt;br&gt;
Writing objects: 100% (38/38), 40.87 KiB | 5.11 MiB/s, done.&lt;br&gt;
Total 38 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)&lt;br&gt;
remote: error: GH013: Repository rule violations found for refs/heads/main.&lt;br&gt;
remote: &lt;br&gt;
remote: - GITHUB PUSH PROTECTION&lt;br&gt;
remote:   —————————————————————————————————————————&lt;br&gt;
remote:     Resolve the following violations before pushing again&lt;br&gt;
remote:&lt;br&gt;
remote:     - Push cannot contain secrets&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:      (?) Learn how to resolve a blocked push&lt;br&gt;
remote:      &lt;a href="https://docs.github.com/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line#resolving-a-blocked-push" rel="noopener noreferrer"&gt;https://docs.github.com/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line#resolving-a-blocked-push&lt;/a&gt; &lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:       —— Slack API Token ———————————————————————————————————&lt;br&gt;
remote:        locations:&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: examples/vulnerable_app/shop/integrations.py:6&lt;br&gt;
remote:&lt;br&gt;
remote:        (?) To push, remove secret from commit(s) or follow this URL to allow the secret &lt;br&gt;
remote:        &amp;lt;push-protection-bypass-link&amp;gt;&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:       —— Stripe API Key ————————————————————————————————————&lt;br&gt;
remote:        locations:&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: examples/vulnerable_app/shop/integrations.py:7&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_scanner.py:11&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_secrets.py:11&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_secrets.py:11&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_secrets.py:59&lt;br&gt;
remote:&lt;br&gt;
remote:        (?) To push, remove secret from commit(s) or follow this URL to allow the secret &lt;br&gt;
remote:        &amp;lt;push-protection-bypass-link&amp;gt;&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:       —— Slack API Token ———————————————————————————————————&lt;br&gt;
remote:        locations:&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_secrets.py:10&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_secrets.py:10&lt;br&gt;
remote:&lt;br&gt;
remote:        (?) To push, remove secret from commit(s) or follow this URL to allow the secret &lt;br&gt;
remote:        &amp;lt;push-protection-bypass-link&amp;gt;&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:       —— Amazon AWS Access Key ID ——————————————————————————&lt;br&gt;
remote:        locations:&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: examples/vulnerable_app/shop/settings.py:18&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_scanner.py:10&lt;br&gt;
remote:&lt;br&gt;
remote:        (?) To push, remove secret from commit(s) or follow this URL to allow the secret &lt;br&gt;
remote:        &amp;lt;push-protection-bypass-link&amp;gt;&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:       —— Amazon AWS Secret Access Key ——————————————————————&lt;br&gt;
remote:        locations:&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: examples/vulnerable_app/shop/settings.py:19&lt;br&gt;
remote:          - commit: &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;&lt;br&gt;
remote:            path: tests/test_scanner.py:10&lt;br&gt;
remote:&lt;br&gt;
remote:        (?) To push, remove secret from commit(s) or follow this URL to allow the secret &lt;br&gt;
remote:        &amp;lt;push-protection-bypass-link&amp;gt;&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
remote:&lt;br&gt;
To &lt;a href="https://github.com/hardikgaikwad/localaudit.git" rel="noopener noreferrer"&gt;https://github.com/hardikgaikwad/localaudit.git&lt;/a&gt;&lt;br&gt;
 ! [remote rejected] main -&amp;gt; main (push declined due to repository rule violations)&lt;br&gt;
error: failed to push some refs to '&lt;a href="https://github.com/hardikgaikwad/localaudit.git" rel="noopener noreferrer"&gt;https://github.com/hardikgaikwad/localaudit.git&lt;/a&gt;'&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;That's the expected block. GitHub flagged five fake values, and every location is either in &lt;code&gt;examples/vulnerable_app/&lt;/code&gt; or in &lt;code&gt;tests/&lt;/code&gt;. They are the made-up keys I wrote so the scanner has something to find, not real credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To push:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open each of the 5 links from the error while signed in as &lt;code&gt;hardikgaikwad&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;Slack token (integrations.py): &lt;code&gt;&amp;lt;push-protection-bypass-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Stripe key: &lt;code&gt;&amp;lt;push-protection-bypass-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slack token (tests): &lt;code&gt;&amp;lt;push-protection-bypass-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS access key ID: &lt;code&gt;&amp;lt;push-protection-bypass-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AWS secret access key: &lt;code&gt;&amp;lt;push-protection-bypass-link&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;On each page, choose &lt;strong&gt;"It's used in tests"&lt;/strong&gt; and click &lt;strong&gt;Allow secret&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Push again within a couple of hours, while the allowances are still valid. You don't need to change the commit:
&lt;code&gt;powershell
git push -u origin main
&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What to expect afterwards:&lt;/strong&gt;&lt;br&gt;
- If the repo is public, GitHub may still open secret-scanning alerts for these values under the repo's Security tab. Close them as "Used in tests".&lt;br&gt;
- GitHub may also pass the AWS, Slack and Stripe values to those providers to check. They're fake, so nothing happens.&lt;br&gt;
- The fake GitHub token, Google key and JWT weren't flagged. GitHub's checks for those formats are stricter, and my made-up values don't pass them. That's fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you'd rather not bypass push protection:&lt;/strong&gt; I can change the tests to build the fake keys at runtime (for example &lt;code&gt;"sk_" + "live_" + ...&lt;/code&gt;), so the literal strings never appear in them. The demo app has to keep literal keys in its files, though, or the scanner has nothing to find. That would cut the flags down to the demo files, but you'd still need to allow those, so the five-click route above is simpler.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Make sure the README mentions that there are fake API keys in the example application&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-grep"&gt;Grep&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"pattern": "Demo app|vulnerable demo app|You can try it without", "path": "&amp;lt;...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Demo app|vulnerable demo app|You can try it without"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;projects&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;LocalSec&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;21:# 3. Scan the intentionally vulnerable demo app
26:You can try it without Ollama: `localaudit scan examples/vulnerabl [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "offset": 24, "limit": 4}&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;projects&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;LocalSec&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;24   ```
25  
26  You can try it without Ollama: `localaudit scan examples/vulnerable_app --no-llm`.
27&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-read"&gt;Read&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "offset": 132}&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;projects&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;LocalSec&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;README.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;132&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;132  - **This is not a replacement for a professional penetration test** or a full SAST/SCA product.
133 
134 ## Demo app [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "old_s...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "old_string": "You can try it without Ollama: `localaudit scan examples/vulnera [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;The server-side auto mode classifier gave no verdict (it skipped this action), so auto mode cannot determine the safety  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;Edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "old_s...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"replace_all": false, "file_path": "&amp;lt;projects&amp;gt;\\LocalSec\\README.md", "old_string": "`examples/vulnerable_app/` is **intentionally insecure**, and al [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;The server-side auto mode classifier gave no verdict (it skipped this action), so auto mode cannot determine the safety  [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;I couldn't make this change. Auto mode's safety check returned no verdict for both README edits, the same thing that happened with the first README write. I haven't routed around it this time. You approved using Bash for that earlier write, but I won't assume that approval carries over to this edit.&lt;/p&gt;

&lt;p&gt;You have two options:&lt;br&gt;
- &lt;strong&gt;Tell me to use Bash again&lt;/strong&gt; and I'll apply both changes that way.&lt;br&gt;
- &lt;strong&gt;Paste them in yourself.&lt;/strong&gt; Here is the exact text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. In the quickstart,&lt;/strong&gt; directly after the line &lt;code&gt;You can try it without Ollama: `localaudit scan examples/vulnerable_app --no-llm`.&lt;/code&gt; add:&lt;/p&gt;

&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gt"&gt;&amp;gt; **Heads-up: the demo app contains fake API keys.** `examples/vulnerable_app/` and the tests include made-up credentials in real formats (AWS, Stripe, Slack, GitHub, Google, a JWT, a private key block) so the secret rules have something to find. None of them are real or valid. See [Demo app](#demo-app).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;2. Replace everything under &lt;code&gt;## Demo app&lt;/code&gt;&lt;/strong&gt; (the last section) with:&lt;/p&gt;

&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="sb"&gt;`examples/vulnerable_app/`&lt;/span&gt; is &lt;span class="gs"&gt;**intentionally insecure**&lt;/span&gt;. It exists only to show the rules firing. Don't deploy or copy it.

&lt;span class="gs"&gt;**It contains fake API keys and credentials.**&lt;/span&gt; To exercise the secret detector, the demo app and the tests (&lt;span class="sb"&gt;`tests/test_secrets.py`&lt;/span&gt;, &lt;span class="sb"&gt;`tests/test_scanner.py`&lt;/span&gt;) hold made-up values in the exact formats of real ones:

| Kind | Where |
|---|---|
| AWS access key ID and secret access key | &lt;span class="sb"&gt;`shop/settings.py`&lt;/span&gt; |
| Database password, Django &lt;span class="sb"&gt;`SECRET_KEY`&lt;/span&gt; | &lt;span class="sb"&gt;`shop/settings.py`&lt;/span&gt; |
| GitHub token, Slack bot token, Stripe live key, Google API key, JWT, signing salt | &lt;span class="sb"&gt;`shop/integrations.py`&lt;/span&gt; |
| RSA private key block | &lt;span class="sb"&gt;`shop/integrations.py`&lt;/span&gt; |

These values are &lt;span class="gs"&gt;**not real, have never been valid, and grant access to nothing**&lt;/span&gt;. They were invented for this project. Some contain &lt;span class="sb"&gt;`FAKE`&lt;/span&gt; or &lt;span class="sb"&gt;`NOTAREALKEY`&lt;/span&gt;, but not all of them do, because real formats don't always leave room for it.

Because they match real formats, other secret scanners will flag them too. GitHub's push protection blocks the first push until you allow each one as "used in tests". Repository secret-scanning alerts can be closed as "used in tests". If you fork this project, expect the same.

Never replace them with real credentials, even for testing.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After the README is updated, commit it and push. You still need to allow the five flagged values first, using the links from the push error, because the blocked first commit is part of this push.&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&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="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Document fake API keys in demo app and tests"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;use bash to update README&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cat &amp;gt; \"$TEMP/la_quick.md\" &amp;lt;&amp;lt;'README_END'\n\n&amp;gt; **Heads-up: the ...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cat &amp;gt; \"$TEMP/la_quick.md\" &amp;lt;&amp;lt;'README_END'\n\n&amp;gt; **Heads-up: the demo app contains fake API keys.** `examples/vulnerable_app/` and the tes [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;ok&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "python - &amp;lt;&amp;lt;'PY'\nimport os\nt = os.environ[\"TEMP\"]\nquick = op...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "python - &amp;lt;&amp;lt;'PY'\nimport os\nt = os.environ[\"TEMP\"]\nquick = open(os.path.join(t, \"la_quick.md\"), encoding=\"utf-8\").read().rstrip(\" [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;28:&amp;gt; **Heads-up: the demo app contains fake API keys.** `examples/vulnerable_app/` and the tests include made-up credent [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;I updated the README using Bash, and it now says clearly that the demo app contains fake API keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quickstart (line 28):&lt;/strong&gt; a short notice right under the demo command saying the demo app and tests contain made-up credentials in real formats. It links to the Demo app section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo app section:&lt;/strong&gt; rewritten with a table of every fake credential and the file it's in. It says the values are invented and grant access to nothing, warns that GitHub push protection and other secret scanners will flag them, and says never to replace them with real credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To publish it, first allow the five flagged values using the links from the push error, since the blocked commit is still part of this push. Then run:&lt;/p&gt;

&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&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="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Document fake API keys in demo app and tests"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &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 id="agent-session-916-22-what-i-built"&gt;What I Built&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- What does it do, and who would pay for it?  What problem does it solve for them? --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-demo"&gt;Demo&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Share a deployed link or a video demo. --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-code"&gt;Code&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Show us the code!  You can embed a GitHub repo directly into your post. --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-how-i-built-it"&gt;How I Built It&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Which open-source AI did you use (open-weight models, agent harnesses, frameworks, local inference), and how is your project built around it? --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-why-does-open-innovation-matter"&gt;Why Does Open Innovation Matter?&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Why does open innovation matter for what you built?  What did it make possible that a closed API wouldn't? --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-my-agent-session"&gt;My Agent Session&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Optional, but judges love it.  Save your session with DevRelay and embed it with the agent_session tag (see the challenge page), or link to it. --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-my-first-user"&gt;My First User&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Optional, but this is where bonus points live.  Did someone use it?  Who were they, what did they say, and did they pay?  If not, remove this section. --&amp;gt;&lt;/p&gt;
&lt;h2 id="agent-session-916-22-prize-categories"&gt;Prize Categories&lt;/h2&gt;

&lt;p&gt;&amp;lt;!-- Which partner categories are you entering?  List every one that applies, or remove this section. --&amp;gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;!-- Team Submissions: Please pick one member to publish the submission and credit teammates by listing their DEV usernames directly in the body of the post. --&amp;gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;!-- Thanks for participating! --&amp;gt;&lt;/p&gt;

&lt;p&gt;Use this as the template and rewrite this for the devpost, leave placeholders for demo and github embeds. Tell me separately how to add devrelay session in the submission&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll look up the challenge details and the session-embed instructions first, so the prize categories and embed syntax are accurate rather than guessed.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-skill"&gt;Skill&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"skill": "devrelay-sessions"}&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skill"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"devrelay-sessions"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Launching skill: devrelay-sessions&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-toolsearch"&gt;ToolSearch&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"query": "select:mcp__devrelay-gateway__get_challenges,mcp__devrelay-gateway...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"select:mcp__devrelay-gateway__get_challenges,mcp__devrelay-gateway__get_challenge_details"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;[image]
[image]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-mcpdevrelaygatewaygetchallenges"&gt;mcp__devrelay-gateway__get_challenges&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{}&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;{"Ok":[{"id":82,"title":"Hacktoberfest Open-Source AI Challenge: Week 4","slug":"hacktoberfest-week4-2026-10-26","descri [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Here's the post, ready to paste into DEV. I checked the details against the live challenge page:&lt;br&gt;
- &lt;strong&gt;Theme:&lt;/strong&gt; Side Hustle.&lt;br&gt;
- &lt;strong&gt;Deadline:&lt;/strong&gt; &lt;strong&gt;October 18, 11:59 PM PDT&lt;/strong&gt;, which is two days from now.&lt;br&gt;
- &lt;strong&gt;Required tag:&lt;/strong&gt; &lt;code&gt;#hf26challenge&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title: LocalAudit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;an AI security auditor that never sees your secrets and never leaves your laptop&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hf26challenge, security, opensource, ai&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="ge"&gt;*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle](https://dev.to/challenges/hacktoberfest-week2-2026-10-12)*&lt;/span&gt;

&lt;span class="gu"&gt;## What I Built&lt;/span&gt;

&lt;span class="gs"&gt;**LocalAudit**&lt;/span&gt; is a command-line security auditor for source code that runs 100% on your own machine. Point it at a repo and a few seconds later you get a clean HTML report (plus JSON and Markdown) listing hardcoded secrets, injection risks, insecure configuration and unpinned dependencies. Each finding comes with the file and line, the code around it, a plain-English explanation and a concrete fix.

The design rule I cared most about: &lt;span class="gs"&gt;**the LLM never detects anything.**&lt;/span&gt; Deterministic rules find the issues: 24 code-pattern rules for Python/Django/Flask and JavaScript/Node, 10 built-in secret detectors (AWS, Stripe, Slack, GitHub, Google, JWTs, private keys, high-entropy strings...) and dependency checks. A local open-weight model then &lt;span class="ge"&gt;*triages*&lt;/span&gt; each finding. It explains it, judges whether it's likely a false positive in context, and writes a fix. If the model is unavailable, you still get the full report.

&lt;span class="gs"&gt;**Who would pay for this?**&lt;/span&gt; People who need a security pass on code they aren't allowed to upload anywhere:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Freelancers and small agencies**&lt;/span&gt; working on client code under NDA. A fixed-fee "pre-handoff security check" with a professional report as the deliverable is an easy add-on to sell.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Solo founders**&lt;/span&gt; about to launch who can't justify an enterprise SAST subscription.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Consultants**&lt;/span&gt; who want a fast first pass before a manual review.

For all of them, the cloud AI code-review tools have the same problem: &lt;span class="ge"&gt;*your client's source code goes to someone else's server.*&lt;/span&gt; LocalAudit's answer is that it doesn't go anywhere.

&lt;span class="gu"&gt;## Demo&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- DEMO VIDEO: paste your video link below, e.g. {% embed https://www.youtube.com/watch?v=... %} --&amp;gt;&lt;/span&gt;
[DEMO_VIDEO_PLACEHOLDER]

The demo scans the bundled &lt;span class="sb"&gt;`examples/vulnerable_app`&lt;/span&gt;, a deliberately insecure Django + Node app full of &lt;span class="gs"&gt;**fake**&lt;/span&gt; credentials. The rules-only scan takes about a second and finds 43 issues (4 critical, 16 high), for a risk score of 100/100. A few things worth watching for:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Secrets are masked everywhere.**&lt;/span&gt; You'll only ever see &lt;span class="sb"&gt;`AKIA****`&lt;/span&gt;. Even a private key's body is replaced with &lt;span class="sb"&gt;`****`&lt;/span&gt; before any snippet is built, so raw secrets never reach the report, the console or the model's prompt.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**The model catches a decoy.**&lt;/span&gt; The app contains &lt;span class="sb"&gt;`subprocess.check_output("df -h /", shell=True)`&lt;/span&gt;. The rule correctly flags &lt;span class="sb"&gt;`shell=True`&lt;/span&gt;, but the command is a constant with no user input. The local model marked it as a &lt;span class="ge"&gt;*likely false positive*&lt;/span&gt;. LocalAudit doesn't delete it: it moves it to a separate "Likely false positives" section for a human to confirm.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**It falls back gracefully.**&lt;/span&gt; Stop Ollama and the tool prints the exact command to fix it (&lt;span class="sb"&gt;`ollama pull qwen2.5-coder:7b`&lt;/span&gt;), then carries on with the rules-only report.

&lt;span class="gu"&gt;## Code&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- GITHUB EMBED: e.g. {% github hardikgaikwad/localaudit %} --&amp;gt;&lt;/span&gt;
[GITHUB_EMBED_PLACEHOLDER]

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;ollama pull qwen2.5-coder:7b
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
localaudit scan examples/vulnerable_app
&lt;span class="c"&gt;# open localaudit-report/report.html&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

Useful flags: &lt;span class="sb"&gt;`--no-llm`&lt;/span&gt;, &lt;span class="sb"&gt;`--min-severity high`&lt;/span&gt;, &lt;span class="sb"&gt;`--max-llm-findings 10`&lt;/span&gt;, &lt;span class="sb"&gt;`--model &amp;lt;any Ollama model&amp;gt;`&lt;/span&gt;, and &lt;span class="sb"&gt;`--fail-on high`&lt;/span&gt; (exits with code 1, for CI).

&lt;span class="gu"&gt;## How I Built It&lt;/span&gt;

&lt;span class="gs"&gt;**The open-source AI at the core:**&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**[Ollama](https://ollama.com)**&lt;/span&gt; for local inference over its HTTP API.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Qwen2.5-Coder 7B**&lt;/span&gt;, an open-weight coding model. It's the default, and you can swap it with &lt;span class="sb"&gt;`--model`&lt;/span&gt; or &lt;span class="sb"&gt;`LOCALAUDIT_MODEL`&lt;/span&gt;.

&lt;span class="gs"&gt;**How the pipeline works:**&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Scan.**&lt;/span&gt; Walk the repo, skipping &lt;span class="sb"&gt;`.git`&lt;/span&gt;, &lt;span class="sb"&gt;`node_modules`&lt;/span&gt;, venvs, build output, binaries, files over 1 MB and anything in &lt;span class="sb"&gt;`.localauditignore`&lt;/span&gt;.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Detect and redact.**&lt;/span&gt; Run the secret detectors first and redact every detected value from the file text. Only then run the code rules and build snippets, which is how masking is guaranteed everywhere downstream.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Triage.**&lt;/span&gt; Send each finding, highest severity first, to the local model. The request includes the rule description, the file path and the &lt;span class="ge"&gt;*masked*&lt;/span&gt; snippet. It runs at temperature 0, and Ollama's &lt;span class="sb"&gt;`format`&lt;/span&gt; parameter enforces a JSON schema, so the model must return &lt;span class="sb"&gt;`{explanation, risk_in_context, likely_false_positive, confidence, suggested_fix, fixed_code_example}`&lt;/span&gt;. Invalid JSON gets one retry, then the finding is marked "LLM triage unavailable".
&lt;span class="p"&gt;4.&lt;/span&gt; &lt;span class="gs"&gt;**Cache.**&lt;/span&gt; Results are cached on disk, keyed by model + rule + snippet, so reruns are instant.
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Report.**&lt;/span&gt; Render a single self-contained HTML file with no CDNs or external requests, plus a print stylesheet so "Save as PDF" gives a client-ready document.

The stack is deliberately small: Python 3.10+, &lt;span class="sb"&gt;`requests`&lt;/span&gt;, &lt;span class="sb"&gt;`jinja2`&lt;/span&gt; and pytest, with 110 tests. Those tests include a mocked Ollama covering invalid JSON, timeouts and the server going down mid-run.

I built it with an AI coding agent (Claude Code) doing much of the implementation while I directed the design and reviewed the results. One moment I liked: testing caught a regex bug where a bare &lt;span class="sb"&gt;`password = "..."`&lt;/span&gt; wasn't detected. Because the masker only masks what the detector finds, that password was showing up unmasked as a &lt;span class="ge"&gt;*context line*&lt;/span&gt; in a neighbouring finding's snippet. It's fixed now, and there's a regression test for it.

&lt;span class="gu"&gt;## Why Does Open Innovation Matter?&lt;/span&gt;

For this product, open weights aren't a nice-to-have. They're the reason it can exist.
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Privacy is the product.**&lt;/span&gt; The customers I described &lt;span class="ge"&gt;*can't*&lt;/span&gt; send client code to a third-party API. A local open-weight model is the only way to give them AI explanation
[... truncated 3409 chars]
&lt;/code&gt;&lt;/pre&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Sure save the devrelay session&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll start by finding this session's transcript on disk.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "ls -la ~/.claude/projects/&amp;lt;project-id&amp;gt;/ 2&amp;gt;&amp;amp;1 | head -20", "descr...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "ls -la ~/.claude/projects/&amp;lt;project-id&amp;gt;/ 2&amp;gt;&amp;amp;1 | head -20", "description": "List Claude Code transcripts for this project"}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;total 3952
drwxr-xr-x 1 &amp;lt;user&amp;gt; 197609       0 Oct 16 13:21 .
drwxr-xr-x 1 &amp;lt;user&amp;gt; 197609       0 Oct 16 13:07 ..
-rw-r--r [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;There are two transcript files, so the session probably continued from one file into the other. Checking how each is structured:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cd ~/.claude/projects/&amp;lt;project-id&amp;gt;/ &amp;amp;&amp;amp; python - &amp;lt;&amp;lt;'PY'\nimport j...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cd ~/.claude/projects/&amp;lt;project-id&amp;gt;/ &amp;amp;&amp;amp; python - &amp;lt;&amp;lt;'PY'\nimport json\nfor fn in [\"5982a5be-821b-4a50-8648-c43f4c26c75d.jsonl\",\"f0d03b42 [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;5982a5be {'mode': 17, 'permission-mode': 17, 'atis-latch': 17, 'file-history-snapshot': 1, 'user': 38, 'attachment': 69, [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "mkdir -p \"$TEMP/la-session\" &amp;amp;&amp;amp; cat &amp;gt; \"$TEMP/la-session/build....&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "mkdir -p \"$TEMP/la-session\" &amp;amp;&amp;amp; cat &amp;gt; \"$TEMP/la-session/build.py\" &amp;lt;&amp;lt;'PY'\nimport json, re, sys, os\nsys.path.insert(0, r\"&amp;lt;projects&amp;gt;\\ [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;messages: 26  bytes: 204597
     70  fake keys (explicit list)
     26  ~
     13  &amp;lt;/?pasted_content[^&amp;gt;]*&amp;gt;
     10   [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The transcript is built: 26 messages, about 200 KB. Only one project path was replaced, though, which seems too low. Before going further I'll grep the output for anything that still leaks:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cd \"$TEMP/la-session\" &amp;amp;&amp;amp; for p in GetItDone &amp;lt;user&amp;gt; gmail 'sk_l...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cd \"$TEMP/la-session\" &amp;amp;&amp;amp; for p in GetItDone &amp;lt;user&amp;gt; gmail 'sk_live_[A-Za-z0-9]{6}' 'ghp_[A-Za-z0-9]{6}' 'xox[bp]-[0-9]' 'AKIA[A-Z0-9]{6} [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;GetItDone                        54
&amp;lt;user&amp;gt;                            38
gmail                            0
sk_live_[A-Z [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The scrub is incomplete: 54 project paths and some home-directory paths are still in there. Debugging the path regex:&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-bash"&gt;Bash&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;{"command": "cd \"$TEMP/la-session\" &amp;amp;&amp;amp; grep -oE '.{30}&amp;lt;user&amp;gt;.{20}' transcrip...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ {"command": "cd \"$TEMP/la-session\" &amp;amp;&amp;amp; grep -oE '.{30}&amp;lt;user&amp;gt;.{20}' transcript.json | sort | uniq -c | sort -rn | head -8; python -c \"\nimport re\nrx [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;2 file_path\": \"~\\\\AppData\\\\Local
      1 ut": "total 452\ndrwxr-xr-x 1 &amp;lt;user&amp;gt; 197609      0 Oct 1
      1 undExcep [... trimmed]&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        26 of 26 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>A Game That Replaces Doomscrolling, Powered by Local AI (Ollama)</title>
      <dc:creator>Hardik</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:56:45 +0000</pubDate>
      <link>https://dev.to/hardikgaikwad/a-game-that-replaces-doomscrolling-powered-by-local-ai-ollama-2hic</link>
      <guid>https://dev.to/hardikgaikwad/a-game-that-replaces-doomscrolling-powered-by-local-ai-ollama-2hic</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;&lt;strong&gt;Field Dex&lt;/strong&gt; is a pixel-art creature-collecting game where the creatures are real. You photograph a bird, a tree, a beetle or a flower on your normal iPhone camera, and when you get home you open the day's photos like a pack of trading cards.&lt;/p&gt;

&lt;p&gt;My laptop identifies every photo with a local vision model and rolls a rarity, from Common to Legendary. It writes the card's flavour text, pays out XP, and dresses your pixel avatar in gear you unlock. Each day, finding something new also unlocks the next chapter of a story starring you, which ends on a cliffhanger. The only way to read what happens next is to go back outside.&lt;/p&gt;

&lt;p&gt;It's for anyone who has caught themselves scrolling when they meant to go for a walk, and for people who like collecting things. That includes me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a game?
&lt;/h3&gt;

&lt;p&gt;I didn't want to build something that politely suggests going outside. Doomscrolling works because of three things: you don't know what the next swipe will show, there is always one more to see, and nothing ever finishes. So I tried to copy the mechanics but not the screen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Doomscrolling&lt;/th&gt;
&lt;th&gt;Field Dex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unpredictable next item&lt;/td&gt;
&lt;td&gt;A rarity roll when you flip each card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Endless feed&lt;/td&gt;
&lt;td&gt;A Dex with &lt;code&gt;???&lt;/code&gt; slots for every creature you haven't found yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reward is on the screen&lt;/td&gt;
&lt;td&gt;Reward is only earned outside, then opened at home&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No "done" state&lt;/td&gt;
&lt;td&gt;A new Wanted poster every day and a story that never quite ends&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The screen is the shortest part of the day. The photo walk is the game, and the pack opening is the reward at the end of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The game, in detail
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Wanted poster.&lt;/strong&gt; Every day the game posts a bounty such as "a bee on a yellow flower". It's described by what you can &lt;em&gt;see&lt;/em&gt;, never by species name, so the vision model can actually check whether your photo matches. A match is worth +50 XP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rarity is a mix of luck and effort.&lt;/strong&gt; A harder subject (birds beat plants), a first-ever species, a sharp photo and a shot taken within about 75 minutes of sunrise or sunset all raise your odds. The final roll is seeded from the photo's hash, so re-uploading can't reroll it. From my simulation of 10,000 rolls, a good photo of a new bird comes out Rare 57% of the time, Epic 31% and Legendary 12%. A repeat plant is Common about three times out of four, so a Legendary actually feels like an event.&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="n"&gt;score&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KIND_BASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                       &lt;span class="c1"&gt;# birds are harder than plants
&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;NEW_SPECIES_BONUS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_new&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# first time you've seen it
&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;                   &lt;span class="c1"&gt;# sharp photo beats a blurry one
&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;golden_hour&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;             &lt;span class="c1"&gt;# dawn and dusk are luckier
&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt;             &lt;span class="c1"&gt;# the surprise, skewed low
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;It can't be gamed easily.&lt;/strong&gt; It rejects photos of screens, photos with no living thing, photos more than a week old, and anything you've uploaded before. The rarity and XP rules are plain Python code, not model output, so the AI can't be flattered or prompted into handing out a Legendary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing is spoiled early.&lt;/strong&gt; Your XP bar and Dex count only change when you flip a card, not when the laptop finishes processing it. The surprise is always saved for the flip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pixel world.&lt;/strong&gt; Your avatar is drawn from text grids on a canvas, with no image files. It gains a camera, binoculars, a leaf crown, a bug net or a cape as you hit milestones. Each card's artwork is your own photo, shrunk and colour-reduced into pixel art, and tapping it shows the original. Sound effects are synthesised live with WebAudio, so there are no audio files either, and nothing loads from the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soft pressure on purpose.&lt;/strong&gt; There are no streaks to lose and nothing is ever taken away. I wanted to pull people outside, not punish them for staying in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/Wd2y8V4rQmA" width="315" height="560"&gt;
    &lt;/iframe&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/hardikgaikwad" rel="noopener noreferrer"&gt;
        hardikgaikwad
      &lt;/a&gt; / &lt;a href="https://github.com/hardikgaikwad/FieldDex" rel="noopener noreferrer"&gt;
        FieldDex
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A local 'touch grass' gamified application to get you off your screen.
    &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;Field Dex&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A pixel-art creature collecting game played in the real world. You photograph living things outside, then come home and &lt;strong&gt;open them like a card pack&lt;/strong&gt;. A local open-weight vision model identifies each find. A rarity roll, XP, levels, gear for your pixel avatar, and a story that continues one chapter at a time are what bring you back the next day.&lt;/p&gt;

&lt;p&gt;Everything runs on your own laptop through &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;. No accounts, no cloud, no API keys. Your photos never leave your devices.&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/hardikgaikwad/FieldDex/docs/today.png"&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%2Fhardikgaikwad%2FFieldDex%2FHEAD%2Fdocs%2Ftoday.png" alt="Today screen: the pixel avatar, a Wanted poster and the button to open today's pack" width="24%"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/hardikgaikwad/FieldDex/docs/reveal.png"&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%2Fhardikgaikwad%2FFieldDex%2FHEAD%2Fdocs%2Freveal.png" alt="Revealing an Epic Tiger Butterfly card with pixel-art made from the photo" width="24%"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/hardikgaikwad/FieldDex/docs/dex.png"&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%2Fhardikgaikwad%2FFieldDex%2FHEAD%2Fdocs%2Fdex.png" alt="The Dex: collected cards with rarity frames and ??? slots for creatures not yet found" width="24%"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/hardikgaikwad/FieldDex/docs/story.png"&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%2Fhardikgaikwad%2FFieldDex%2FHEAD%2Fdocs%2Fstory.png" alt="Story chapter written from today's finds, ending on a cliffhanger" width="24%"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Today · Reveal · Dex · Story. Screenshots use sample photos from Wikimedia Commons.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How a day works&lt;/h2&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Outside:&lt;/strong&gt; use the normal iPhone Camera. No app, no laptop, no signal needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home:&lt;/strong&gt; the laptop is on your iPhone's Personal Hotspot. Open Field Dex in Safari, tap &lt;strong&gt;Open today's pack&lt;/strong&gt;, and pick today's photos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On the laptop:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;qwen2.5vl:3b&lt;/code&gt; identifies each photo (about 6 s…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hardikgaikwad/FieldDex" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The README has setup steps, a map of the code and the screenshots. Everything runs with &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; and two &lt;code&gt;ollama pull&lt;/code&gt; commands.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The constraint that shaped everything
&lt;/h3&gt;

&lt;p&gt;My first idea was a "side quest generator": a local agent reads your profile and free time and hands you a short outdoor mission. I built it and it worked. Then I noticed the obvious problem: &lt;strong&gt;my laptop can't come outside with me&lt;/strong&gt;, and it gets its internet from my iPhone's hotspot. A quest app that only works at a desk is useless.&lt;/p&gt;

&lt;p&gt;So I flipped the design instead of fighting it. &lt;strong&gt;The phone collects outside; the laptop thinks at home.&lt;/strong&gt; The iPhone camera needs no app and no signal. Back home, Safari talks to the laptop over the hotspot. That delay between taking the photo and seeing the card turned out to be the best part of the design, because it's exactly the gap between pulling the lever and seeing what you won.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; OUTSIDE                         HOME (same hotspot)
┌──────────────┐   upload   ┌───────────────────────────────┐
│ iPhone camera│ ─────────▶ │ FastAPI + SQLite              │
│ (no app, no  │            │   ├─ Pillow: HEIC → JPEG, pixel art
│  signal)     │ ◀───────── │   ├─ qwen2.5vl:3b  → what is it?
└──────────────┘   cards    │   ├─ game.py       → rarity, XP, unlocks
   Safari UI                │   └─ qwen2.5:3b    → card text, story
                            └───────────────────────────────┘
                                   all via Ollama, on the laptop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  The open models
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;qwen2.5vl:3b&lt;/code&gt;&lt;/strong&gt; (open-weight vision-language model) looks at each photo and returns structured JSON: what the subject is, its visible features, a category, a confidence, whether it's a photo of a screen, and a photo-quality score.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;qwen2.5:3b&lt;/code&gt;&lt;/strong&gt; (open-weight text model) writes each card's flavour text and stats, the daily Wanted poster, and the story chapters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt; runs both, locally. Its structured-output mode takes a JSON schema, which forces the answer into a shape my code can trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing runs on a laptop with an &lt;strong&gt;RTX 3050 with 4 GB of VRAM&lt;/strong&gt;. Identifying a photo takes roughly 6–9 seconds, and a batch of six photos, with their cards and the story chapter, finished in about 90 seconds. Setting the context window to 4,096 tokens kept the text model entirely on the GPU. Ollama's default context pushed a third of it onto the CPU.&lt;/p&gt;
&lt;h3&gt;
  
  
  The idea that made small models usable
&lt;/h3&gt;

&lt;p&gt;Three-billion-parameter models are good at some things and bad at others, so the pipeline gives each part to whoever is good at it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The model&lt;/strong&gt; describes and names things, and writes. It's good at that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; decides rarity, XP, levels, unlocks and rejections. These are deterministic, testable and can't be talked into cheating.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two real examples of that split paying off:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The model names well and categorises badly.&lt;/strong&gt; On my test photos it called the peacock "Indian Peafowl" correctly, then filed it under "animal". A butterfly became a "plant". So the name now decides the category through a small vocabulary map, and the model's own category is only a fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field order matters.&lt;/strong&gt; I originally asked "is this a living thing?" first, and the model confidently said a tree beside a building was not. Reordering the schema so it &lt;em&gt;describes first, then classifies, then judges&lt;/em&gt; fixed that case. It still labelled a hibiscus flower as "none" until the name-based category rule above caught it. Both fixes together got all six of my test photos right, including rejecting a street scene with no living thing in it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  A pipeline detail that matters on 4 GB
&lt;/h3&gt;

&lt;p&gt;Ollama can only keep one model loaded on a card this small. Processing photo-by-photo would swap vision and text models back and forth every time. So a batch runs &lt;strong&gt;all vision calls first, then all text calls&lt;/strong&gt;, and the models swap once per batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other things worth saying
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One background worker&lt;/strong&gt; processes the queue while the page polls for progress, so uploading returns instantly and the "developing film" screen shows live progress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real bug I hit:&lt;/strong&gt; uploading a duplicate photo left a SQLite transaction open and locked the worker out of the database. I fixed it with autocommit and wrote a regression test. The project has 11 tests covering the game rules, the vision-category logic and that bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI has no build step.&lt;/strong&gt; It's plain HTML, CSS and JavaScript, with bundled pixel fonts (Press Start 2P and Pixelify Sans, both under the SIL Open Font License).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where the small models struggle (honestly)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Flavour text sometimes invents facts. One card claimed a peafowl is "a master of disguise".&lt;/li&gt;
&lt;li&gt;Names are sometimes vague. A photo of a big tree came back as just "Tree".&lt;/li&gt;
&lt;li&gt;The stats on each card (charm, stealth, grit) are made up by the model for fun, not real biology.&lt;/li&gt;
&lt;li&gt;Story chapters stay in the right world and use the day's finds, but the prose can be clumsy. One chapter said "Mynah, the Mynah, sang a song".&lt;/li&gt;
&lt;li&gt;Telling lookalike species apart is beyond a 3B model, so I treat the names as a fun guess, not a field guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Taking It Outside
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[TODO before publishing: replace this block with your real photo walk.]&lt;/strong&gt;&lt;br&gt;
Where did you go? How many photos? What was your best and worst find? Did a Wanted poster make you walk somewhere you wouldn't have? How did it feel to open the pack afterwards compared with scrolling? Add 2–3 real screenshots or photos.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;I could have built this around a hosted vision API in an afternoon. I'm glad I didn't, for four reasons that are specific to this app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;My photos are my life.&lt;/strong&gt; A folder of "what I saw on my walks" shows where I go and when, which is exactly what you shouldn't hand to a server you don't control. Field Dex never sends a photo anywhere. Nothing in the app calls out to the internet: no CDN, no analytics, no fonts fetched from a server, and inference is a call to &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost is zero, which changes the design.&lt;/strong&gt; A game that rewards you for taking &lt;em&gt;more&lt;/em&gt; photos only works if each one is free. With a per-image API price, I'd have been quietly discouraging the very behaviour the game exists to encourage. I also ran dozens of prompt and pipeline experiments in one evening without watching a bill or a rate limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open weights let me constrain the model.&lt;/strong&gt; Because I run the stack myself, I could force structured JSON output, control the context window to fit a 4 GB card, run the vision and text models in a deliberate order, and keep the rules in code rather than trusting a prompt. That's how a 3B model became reliable enough to ship.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I can change it.&lt;/strong&gt; The models are two lines in &lt;code&gt;profile.yaml&lt;/code&gt;. Any vision model Ollama can run can be dropped in, and when a better small model ships next month, the game gets better without me touching the code. (I only tested &lt;code&gt;qwen2.5vl:3b&lt;/code&gt; and &lt;code&gt;qwen2.5:3b&lt;/code&gt; end to end, so I can't promise how others compare.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest trade-off: a hosted frontier model would identify species more accurately and write better prose. For a game about the &lt;em&gt;act of going outside&lt;/em&gt;, I think "mostly right, private, free and mine" beats "perfectly right and rented".&lt;/p&gt;

&lt;p&gt;Built on open-weight models (Qwen2.5-VL and Qwen2.5) running through Ollama, with the session saved through DevRelay.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>No More TeX Errors: Meet CVstart, a 100% Private AI Resume Builder</title>
      <dc:creator>Hardik</dc:creator>
      <pubDate>Sun, 04 Oct 2026 11:20:14 +0000</pubDate>
      <link>https://dev.to/hardikgaikwad/no-more-tex-errors-meet-cvstart-a-100-private-ai-resume-builder-4fbe</link>
      <guid>https://dev.to/hardikgaikwad/no-more-tex-errors-meet-cvstart-a-100-private-ai-resume-builder-4fbe</guid>
      <description>&lt;p&gt;What I Built&lt;br&gt;
I built CVstart—a local-first, AI-powered agentic LaTeX resume builder designed for anyone who loves the typographical beauty of LaTeX but dreads fighting syntax errors, brittle formatting, and margin spills.&lt;/p&gt;

&lt;p&gt;Demo&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/5Zeit4GWklw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code&lt;br&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/hardikgaikwad" rel="noopener noreferrer"&gt;
        hardikgaikwad
      &lt;/a&gt; / &lt;a href="https://github.com/hardikgaikwad/CVStart" rel="noopener noreferrer"&gt;
        CVStart
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local LLM AI Resume Builder in Latex
    &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;CVstart&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A Local-First, AI-Powered Agentic LaTeX Resume Builder&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/hardikgaikwad/CVStart/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://www.python.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1498a580110c107910a7e0d7e7a667d43d06947b1db0d8f183a03c00e5ec3352/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507974686f6e2d332e31302532422d627269676874677265656e2e737667" alt="Python: 3.10+"&gt;&lt;/a&gt;
&lt;a href="https://react.dev/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ba3c159a0fd14b09005b75937b7f95f72b310fad55b0af7027dddb9cf95bfbc0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f52656163742d31392d6379616e2e737667" alt="React: 19"&gt;&lt;/a&gt;
&lt;a href="https://tectonic-typesetting.github.io/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f043d3f4da10937f61fee331d65dac04ea512d244dc893834a198b487da86f8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c615465582d546563746f6e69632d707572706c652e737667" alt="LaTeX: Tectonic"&gt;&lt;/a&gt;
&lt;a href="https://ollama.ai/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a1f6a1cfc72f2f83e0c51e6b607b4595402cbe346da483ca0a4ce3383c20d81b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f496e666572656e63652d4f6c6c616d61253230284c6f63616c292d6f72616e67652e737667" alt="Inference: Ollama"&gt;&lt;/a&gt;
&lt;a href="https://github.com/hardikgaikwad/CVStart/tests/" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/404575de2c4d9a0760395888144fbb5e694306efcc84842774145a73ae84373a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54657374732d31392532305061737365642d737563636573732e737667" alt="Tests: 19 Passed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;CVstart&lt;/strong&gt; empowers software engineers, students, and professionals to create, edit, format, compile, inspect, and export stunning LaTeX resumes using natural language—without needing to write or debug complex LaTeX syntax.&lt;/p&gt;
&lt;p&gt;Unlike conventional AI resume tools that merely generate raw text and dump it into an unverified document, CVstart features a &lt;strong&gt;true agentic compile-inspect-correct loop&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The AI translates user facts or instructions into a structured change plan.&lt;/li&gt;
&lt;li&gt;The LaTeX engine compiles the source in a secure, sandboxed local environment.&lt;/li&gt;
&lt;li&gt;The geometry inspection engine analyzes the resulting PDF text bounding boxes, density, margins, and page spills using PyMuPDF.&lt;/li&gt;
&lt;li&gt;If an overflow or layout flaw is detected, the agent autonomously executes targeted corrections (up to 3 bounded iterations) to produce a flawless document.&lt;/li&gt;
&lt;li&gt;All operations run &lt;strong&gt;100% locally&lt;/strong&gt; on your machine using Ollama and local compilation—&lt;strong&gt;zero cloud calls, zero telemetry, absolute privacy&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hardikgaikwad/CVStart" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Who I Built It For&lt;br&gt;
I built this for my close friend Abhinav, a final year B. preparing for backend and development roles.&lt;/p&gt;

&lt;p&gt;Abhinav faced two frustrating roadblocks:&lt;/p&gt;

&lt;p&gt;The "LaTeX Tax": Every time he tailored his resume for a job, he spent 80% of his time debugging cryptic TeX compilation errors (! LaTeX Error: Missing $ inserted, bad table line breaks, or bullet points overflowing onto page 2 by three lonely lines).&lt;br&gt;
The Cloud Privacy Dilemma: He refused to paste his unredacted career history, private internal team metrics, home address, and compensation notes into closed cloud LLM websites. A resume contains your most sensitive personal and career data, and the idea of feeding it into closed AI APIs that log prompts and train on user inputs felt unacceptable.&lt;br&gt;
The Solution&lt;br&gt;
CVstart solves both problems:&lt;/p&gt;

&lt;p&gt;100% Local-First &amp;amp; Air-Gapped: Powered entirely by open-weight models (llama3.2, qwen2.5) running locally via Ollama, with an embedded local LaTeX compiler. Zero cloud calls, zero telemetry, absolute data sovereignty.&lt;br&gt;
Agentic Compile-Inspect-Correct Loop: Instead of generating raw markdown and hoping for the best, CVstart compiles the document in a sandboxed local environment, analyzes the PDF page geometry with PyMuPDF, detects vertical density and page spills, and autonomously adjusts vertical spacing and padding to fit exactly on a single page.&lt;br&gt;
Editorial Document Editor Experience: Built with a minimal, light-mode, distraction-free aesthetic with square geometry, clean monochrome typography, and no AI chat-bubble clutter.&lt;br&gt;
Demo&lt;br&gt;
CVstart runs as a desktop application with a single CLI command (CVstart). It features a real-time split workspace: structured section editing on the left, an authentic rendered document sheet on the right, and an integrated AI utility panel.&lt;/p&gt;

&lt;p&gt;Workflow Highlights&lt;br&gt;
Single-Command Launch: Typing CVstart in the terminal runs system readiness checks (Python, Tectonic compiler, Ollama models), migrations, and opens the local interface at &lt;a href="http://127.0.0.1:9999/" rel="noopener noreferrer"&gt;http://127.0.0.1:9999/&lt;/a&gt;.&lt;br&gt;
Natural Language Assistant: Ask the local model to "Strengthen action verbs under my latest backend role and emphasize distributed systems metrics." The assistant returns a structured Change Plan with a color-coded diff review card (Accept / Discard).&lt;br&gt;
Live PDF Rendering: Every accepted change re-renders the LaTeX PDF in sub-second time, displaying the high-resolution document directly on the neutral canvas with zoom and pan controls.&lt;br&gt;
One-Click Export: Download publication-ready PDFs or export the raw .tex source files.&lt;br&gt;
Code&lt;br&gt;
CVstart is open-source under the MIT License:&lt;/p&gt;

&lt;p&gt;Repository URL: &lt;a href="https://github.com/hardikgaikwad/CVstart" rel="noopener noreferrer"&gt;https://github.com/hardikgaikwad/CVstart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tech Stack&lt;br&gt;
Backend: Python 3.10+, Django REST Framework, SQLite&lt;br&gt;
Frontend: React 19, TypeScript, Tailwind CSS, Lucide Icons&lt;br&gt;
Local AI Inference: Ollama HTTP Client (llama3.2:3b, qwen2.5:7b, llama3.2-vision)&lt;br&gt;
Compilation Engine: Sandboxed Tectonic LaTeX compiler (zero shell escapes)&lt;br&gt;
Geometry Inspection: PyMuPDF (fitz) for printable bounding box measurement and layout density analysis&lt;br&gt;
How I Built It&lt;br&gt;
CVstart is architected around open-source AI and local tooling:&lt;/p&gt;

&lt;p&gt;Local AI Inference with Ollama: We integrated local open-weight models using Ollama's HTTP API. llama3.2:latest (3.2B) is used as an ultra-fast model for rapid structured JSON planning, summary rewrites, and skill categorizations, while qwen2.5:latest (7.6B) handles advanced technical reasoning to ensure strict schema validation.&lt;/p&gt;

&lt;p&gt;Guardrailed Agentic Change Planner: When a user asks for edits, CVstart outputs a strictly structured JSON change plan (summary, affected_sections, proposed_structured_data). An anti-hallucination guardrail verifies that the AI didn't invent non-existent company names or degrees that weren't present in the user's ground-truth profile. The user retains complete control with an auditable visual diff review card.&lt;/p&gt;

&lt;p&gt;PyMuPDF Layout Inspection Loop: One of the hardest parts of resume formatting is preventing an accidental 2-line spill onto page 2. We built a closed-loop inspector using PyMuPDF (fitz). It computes vertical density, printable margins, and total word count. If an overflow is detected, the agent autonomously invokes a micro-refinement pass, adjusting LaTeX font size and list separations until the document fits cleanly on exactly 1 page.&lt;/p&gt;

&lt;p&gt;Why Does Open Innovation Matter?&lt;br&gt;
Open innovation made CVstart possible in ways closed commercial APIs simply could not:&lt;/p&gt;

&lt;p&gt;True Privacy &amp;amp; Data Sovereignty: Closed proprietary AI APIs require transmitting sensitive personal information—employment history, contact info, patents, and compensation metrics—to third-party cloud servers. With open-weight models running on Ollama, all data stays strictly on the user's machine. Zero data leakage, zero tracking.&lt;br&gt;
No Paywalls or Subscriptions: Job hunting is stressful enough without having to pay a $25/month SaaS fee to export your own resume. Open-source models mean free, unlimited compilations and edits forever, even when you're completely offline.&lt;br&gt;
Deterministic, Introspectable Tooling: Closed chatbots trap users in conversational black boxes. Open weights enabled us to build a transparent, introspectable loop: translating natural language into validated schemas, compiling with a local TeX engine, and measuring physical bounding boxes with PyMuPDF.&lt;/p&gt;

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