<?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: Pouria Zandakbari</title>
    <description>The latest articles on DEV Community by Pouria Zandakbari (@pouria_zand).</description>
    <link>https://dev.to/pouria_zand</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3943899%2F32b221bd-bfcd-4161-b55e-bd6fba86ab60.jpeg</url>
      <title>DEV Community: Pouria Zandakbari</title>
      <link>https://dev.to/pouria_zand</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pouria_zand"/>
    <language>en</language>
    <item>
      <title>I Built a Private AI Assistant That Queries My Git History and Project Management Data — Using Only Local LLMs</title>
      <dc:creator>Pouria Zandakbari</dc:creator>
      <pubDate>Thu, 21 May 2026 22:14:40 +0000</pubDate>
      <link>https://dev.to/pouria_zand/i-built-a-private-ai-assistant-that-queries-my-git-history-and-project-management-data-using-only-39mn</link>
      <guid>https://dev.to/pouria_zand/i-built-a-private-ai-assistant-that-queries-my-git-history-and-project-management-data-using-only-39mn</guid>
      <description>&lt;p&gt;&lt;strong&gt;No API keys. No cloud. All data stays on my machine.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;As a web developer, I constantly need to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Who committed the most to our main repo this month?"&lt;/li&gt;
&lt;li&gt;"What files were changed for the last campaign launch?"&lt;/li&gt;
&lt;li&gt;"What project tasks are still in progress for the web team?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These answers exist — scattered across &lt;code&gt;git log&lt;/code&gt;, project management boards, and my own memory. I was tired of digging through terminal output and clicking through boards manually.&lt;/p&gt;

&lt;p&gt;So I built a &lt;strong&gt;natural language interface&lt;/strong&gt; that lets me ask these questions in plain English and get instant answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: Text-to-SQL, Not Vector RAG
&lt;/h2&gt;

&lt;p&gt;Here's the key insight that shaped the entire project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My data is structured, not unstructured.&lt;/strong&gt; Commits have authors, dates, and repos. Project tasks have statuses, deadlines, and assignees. This isn't a pile of PDFs — it's relational data that fits naturally into a SQLite database.&lt;/p&gt;

&lt;p&gt;Traditional RAG (vector embeddings + similarity search) is built for unstructured documents. For structured data, there's a better approach: &lt;strong&gt;Text-to-SQL&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Question
    ↓
Local LLM (generates SQL)
    ↓
SQLite Database (executes query)
    ↓
Local LLM (summarizes results)
    ↓
Human-readable Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM doesn't store or memorize my data. It just translates my question into SQL, runs it, and explains the results.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Data Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Collect everything into SQLite
&lt;/h3&gt;

&lt;p&gt;I wrote two Python collectors that populate a single SQLite database:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git history collector&lt;/strong&gt; (&lt;code&gt;collect.py&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs &lt;code&gt;git log&lt;/code&gt; across multiple repositories&lt;/li&gt;
&lt;li&gt;Stores commits, file changes, branches, and tags&lt;/li&gt;
&lt;li&gt;Captures author, date, message, and insertions/deletions per file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Project management collector&lt;/strong&gt; (&lt;code&gt;collect_pm.py&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries the project management platform's GraphQL API (Monday.com in my case, but the pattern works for Jira, Linear, etc.)&lt;/li&gt;
&lt;li&gt;Stores boards, items, and subitems&lt;/li&gt;
&lt;li&gt;Extracts status, assignee, department, and deadline&lt;/li&gt;
&lt;li&gt;Flags web-team tasks automatically (&lt;code&gt;is_web = 1&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: a single SQLite database holding everything needed to answer cross-cutting questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Link git branches to project tasks
&lt;/h3&gt;

&lt;p&gt;This was the crucial step. Git branches like &lt;code&gt;feature/example-promo-banner&lt;/code&gt; don't obviously connect to project items like &lt;em&gt;"Example Promo Banner — Launch"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I created a &lt;code&gt;branch_task_map&lt;/code&gt; table that links them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;branch_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;board_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;branch_task_map&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;branch_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%promo-banner%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the system cross-reference: &lt;em&gt;"What tasks relate to this branch?"&lt;/em&gt; or &lt;em&gt;"What commits were made for this launch?"&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The RAG System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Ollama?
&lt;/h3&gt;

&lt;p&gt;Privacy was non-negotiable. Project data, commit messages, and task details shouldn't leave the machine. &lt;strong&gt;Ollama runs the LLM entirely locally&lt;/strong&gt; — no internet needed, no data sent anywhere.&lt;/p&gt;

&lt;p&gt;I chose &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; as the model — it's excellent at SQL generation and runs fast on Apple Silicon.&lt;/p&gt;

&lt;h3&gt;
  
  
  The smart prompt
&lt;/h3&gt;

&lt;p&gt;The system prompt is where the magic happens. It includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Full database schema&lt;/strong&gt; — auto-introspected at startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample values&lt;/strong&gt; — actual repo names, anonymized author identifiers, statuses from the database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Few-shot SQL examples&lt;/strong&gt; — teaches the model the query patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Today's date&lt;/strong&gt; — so "this week" and "last month" work correctly
&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_system_prompt&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_schema&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;         &lt;span class="c1"&gt;# Auto-introspect SQLite tables
&lt;/span&gt;    &lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sample_values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Real values from the DB
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a data analyst assistant...

## Database Schema
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

## Sample Values
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auto-discovery: the secret sauce
&lt;/h3&gt;

&lt;p&gt;Before the LLM even sees the question, the system extracts keywords and searches across all tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_keywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Search task_boards, task_items, commits, branches...
&lt;/span&gt;    &lt;span class="c1"&gt;# Return matching IDs, names, values
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means when you ask &lt;em&gt;"What's happening with the example promo banner launch?"&lt;/em&gt;, the system has already found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The matching project board&lt;/li&gt;
&lt;li&gt;Related branches: &lt;code&gt;feature/example-promo-banner&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Recent commits referencing the same keywords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM gets these &lt;strong&gt;exact values&lt;/strong&gt;, so it writes precise SQL instead of guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-correcting queries
&lt;/h3&gt;

&lt;p&gt;If a SQL query returns 0 results, the system automatically retries with different keyword strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attempt 1: WHERE branch = 'feature/example-promo-banner'  → 0 results
Attempt 2: WHERE message LIKE '%promo banner%'             → 12 results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles the reality that commits are often on parent branches, not the feature branch itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;A CLI tool where I type questions and get answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 main.py &lt;span class="s2"&gt;"who committed the most this month?"&lt;/span&gt;

Developer A and Developer B lead this month
with roughly 350 commits each, followed by
Developer C with around 280 commits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 main.py &lt;span class="s2"&gt;"what web tasks are pending for the next launch?"&lt;/span&gt;

The upcoming launch has 8 web tasks remaining:
3 &lt;span class="k"&gt;in &lt;/span&gt;progress, 2 ready &lt;span class="k"&gt;for &lt;/span&gt;review, 3 not started...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 main.py &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"what files changed for the example promo banner?"&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; SQL: SELECT DISTINCT fc.file_path, SUM&lt;span class="o"&gt;(&lt;/span&gt;fc.insertions&lt;span class="o"&gt;)&lt;/span&gt;...
&lt;span class="nt"&gt;--&lt;/span&gt; WHERE c.message LIKE &lt;span class="s1"&gt;'%promo banner%'&lt;/span&gt;...

Several template and snippet files were modified,
concentrated &lt;span class="k"&gt;in &lt;/span&gt;the promo banner section and a few
related shared components.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;The entire system is &lt;strong&gt;8 files, ~400 lines of code, 2 dependencies&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;custom-rag/&lt;/span&gt;
  &lt;span class="s"&gt;main.py&lt;/span&gt;           &lt;span class="c1"&gt;# CLI entry point — REPL + one-shot mode&lt;/span&gt;
  &lt;span class="s"&gt;agent.py&lt;/span&gt;          &lt;span class="c1"&gt;# LLM conversation loop (question → SQL → answer)&lt;/span&gt;
  &lt;span class="s"&gt;db.py&lt;/span&gt;             &lt;span class="c1"&gt;# SQLite read-only, schema introspection, auto-discovery&lt;/span&gt;
  &lt;span class="s"&gt;prompts.py&lt;/span&gt;        &lt;span class="c1"&gt;# System prompt with schema + few-shot examples&lt;/span&gt;
  &lt;span class="s"&gt;tools.py&lt;/span&gt;          &lt;span class="c1"&gt;# Tool definitions&lt;/span&gt;
  &lt;span class="s"&gt;formatter.py&lt;/span&gt;      &lt;span class="c1"&gt;# Rich terminal output&lt;/span&gt;
  &lt;span class="s"&gt;config.py&lt;/span&gt;         &lt;span class="c1"&gt;# Paths and model settings&lt;/span&gt;
  &lt;span class="s"&gt;requirements.txt&lt;/span&gt;  &lt;span class="c1"&gt;# rich, requests (that's it)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No LangChain. No vector database. No embeddings. No cloud services.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Not all RAG needs vectors.&lt;/strong&gt; If your data is structured, Text-to-SQL is simpler and more accurate than embedding everything into a vector store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local LLMs are production-ready.&lt;/strong&gt; Ollama + &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; runs fast on a MacBook and generates correct SQL reliably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-discovery beats prompt engineering.&lt;/strong&gt; Instead of hoping the LLM guesses the right table values, search the database first and feed it exact matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy and simplicity can coexist.&lt;/strong&gt; The whole system is a few hundred lines of Python, runs offline, and handles real questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-referencing is the real value.&lt;/strong&gt; Any single data source is easy to query manually. The power comes from connecting git history with project management data in one natural language interface.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;ul&gt;
&lt;li&gt;Cron job to auto-refresh data every hour&lt;/li&gt;
&lt;li&gt;Adding chat message history as a third data source&lt;/li&gt;
&lt;li&gt;A simple web UI for non-terminal users&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with Python, SQLite, Ollama, and &lt;code&gt;qwen2.5-coder&lt;/code&gt;. All code runs locally — no data leaves the machine. All examples in this article use illustrative names and rounded figures; real commit authors, project codenames, and counts have been replaced or generalized.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're interested in the implementation details or want to build something similar, feel free to reach out or comment below.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>llm</category>
      <category>rag</category>
      <category>privacy</category>
      <category>python</category>
    </item>
  </channel>
</rss>
