Upload or clone a repository. Instead of "here are similar chunks," it answers:
- Explain architecture — repo-wide structure, core modules, languages
- Where is authentication handled? — locates the actual implementation, not just nearby text
- Which code calls the database? — traces real call-graph edges, not keyword guesses
- What breaks if I remove this? — blast-radius / impact analysis via reverse call-graph traversal
- Generate dependency graphs — interactive force-directed call graph, rendered live in the browser
Powered by NVIDIA Nemotron (free tier, via OpenRouter) for generation, and local, free, CPU-only embeddings for retrieval — no paid API required to run this end-to-end.
Plain codebase RAG retrieves chunks by embedding similarity and asks the LLM to summarize them. That fails on anything structural: "what calls this," "what breaks if I delete it," "what's the architecture" are graph questions, not similarity questions.
This project parses every file into a real AST (via Tree-sitter), builds two graphs from it — a call graph (function/method → function/method) and a file dependency graph (file → file, resolved from imports) — and uses a lightweight query router to decide, per question, whether to answer from:
- Semantic chunk retrieval (symbol-aware chunks — each function/class is its own chunk, not an arbitrary line window)
- Structured call-graph traversal (callers/callees, resolved by name across the whole repo)
- Reverse-graph blast-radius analysis (for "what breaks if I remove X")
- Repo-wide centrality ranking (for "explain the architecture" / repo map)
The LLM then receives structured context — actual resolved call edges and import edges, not just nearby text — and is explicitly instructed to reason over relationships, not summarize chunks.
+-------------+ +------------------------------------------------+
| Frontend | | Backend (FastAPI) |
| (vanilla |<---->| |
| JS + D3, | HTTP | ingestion.py --+--> ast_parser.py (Tree-sitter) |
| no build | | (clone/zip) | symbols, calls, imports |
| step) | | +--> graph_builder.py (networkx)|
+-------------+ | | call graph, file graph, |
| | impact analysis, repo map |
| +--> vector_store.py (Chroma) |
| symbol-aware chunking, |
| local sentence-transformer|
| |
| rag_engine.py -- query router + structured |
| context assembly |
| | |
| v |
| llm_client.py -- OpenRouter -> Nemotron (free) |
+------------------------------------------------+
| Feature | Where |
|---|---|
| AST parsing | app/services/ast_parser.py — Tree-sitter grammars for Python, JS/TS/TSX, Go, Java, Ruby, Rust, C/C++, C#, PHP |
| Call graphs | app/services/graph_builder.py — resolves call sites to definitions across the whole repo, builds a networkx.DiGraph |
| Symbol retrieval | app/services/vector_store.py — chunks are symbol-aligned (one function/class = one chunk + metadata) |
| Tree-sitter | Same parser is reused for every supported language via tree_sitter_languages |
| Repository map | graph_builder.repo_map_summary() — centrality-ranked "most depended-on files" and "most-called symbols" |
You need a free OpenRouter API key (no credit card required for free-tier models): https://openrouter.ai/keys
git clone <this-repo>
cd codebase-rag
cp backend/.env.example backend/.env
# edit backend/.env and paste your OPENROUTER_API_KEY
./run.shThen open http://localhost:8000 in your browser.
The first run downloads the local embedding model (~80MB, one-time, runs on CPU) and installs Python dependencies — this can take a few minutes. Subsequent runs are fast.
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then add your OPENROUTER_API_KEY
uvicorn app.main:app --reload- Click "+ Add repository" — paste a GitHub URL (public repos, shallow-cloned) or upload a
.zipof a local project. - Wait for ingestion (parsing + graph building + embedding) — usually 10–60 seconds for small-to-medium repos.
- Ask questions, or use the quick actions in the sidebar:
- Explain architecture
- Where is authentication handled?
- Which code touches the database?
- What breaks if I remove
function_name? - Generate repository map
- The right-hand panel shows a live, draggable call graph for the symbols involved in your last answer, or a structured impact report / repository map, depending on the question.
- Symbol-aware chunking: instead of fixed-size sliding windows, each parsed function/class becomes its own chunk (with file path + signature + docstring prepended as a header). Module-level code not covered by any symbol falls back to windowed chunking. This means retrieval returns complete, coherent units of code.
- Best-effort import resolution: import/require statements are resolved to actual files in the repo using path heuristics (relative imports, common extensions, index files). Unresolved imports become
external::<package>nodes so the dependency graph still reflects "this file depends on an external package" without crashing on it. - Free-tier-first: embeddings run locally via
sentence-transformers(no API cost, no rate limits), and the LLM defaults tonvidia/nemotron-nano-9b-v2:freeon OpenRouter with automatic fallback tonvidia/llama-3.1-nemotron-70b-instruct:freeon rate-limit (HTTP 429) — free-tier endpoints can be rate-limited under load. - No build step on the frontend: plain HTML/CSS/vanilla JS + D3 from a CDN, so there's nothing to compile and the whole frontend is readable in two files.
- Call-graph resolution matches callees by identifier name across the repo; in large codebases with many same-named functions (e.g. multiple
get()methods), this can over-link. A production version would add type-aware resolution per language. - Import resolution is heuristic, not a full per-language module resolver (no
tsconfig.json/go.mod/package.jsonpath-alias awareness yet). - Free-tier OpenRouter models can be slower or rate-limited under heavy use; the fallback model mitigates but doesn't eliminate this.
codebase-rag/
├── backend/
│ ├── app/
│ │ ├── core/config.py # settings (env vars)
│ │ ├── services/
│ │ │ ├── ast_parser.py # Tree-sitter parsing -> symbols/calls/imports
│ │ │ ├── graph_builder.py # call graph + file graph + impact analysis
│ │ │ ├── vector_store.py # symbol-aware chunking + Chroma embeddings
│ │ │ ├── ingestion.py # orchestrates clone/upload -> parse -> embed
│ │ │ ├── llm_client.py # OpenRouter / Nemotron client
│ │ │ └── rag_engine.py # query router + structured context + answer
│ │ ├── routers/
│ │ │ ├── repos.py # ingest (git/upload), status, list, delete
│ │ │ └── rag.py # ask, repo map, graph export, impact, files
│ │ ├── models/schemas.py # Pydantic request/response models
│ │ └── main.py # FastAPI app + static frontend serving
│ ├── requirements.txt
│ └── .env.example
├── frontend/
│ ├── index.html
│ └── static/
│ ├── app.js # SPA logic (state, rendering, D3 force graph)
│ └── styles.css # design system
├── run.sh
└── README.md