Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
12bce7c
Learning Exercise - Phase A.1
abhidxt299 Feb 20, 2026
e565632
Learning Exercise - Phase A.2
abhidxt299 Feb 20, 2026
d8771a2
Learning Exercise - Phase A.3
abhidxt299 Feb 20, 2026
d26ed36
Learning Exercise - Phase A.4
Vaishnavi25-hub Feb 23, 2026
dbcb322
Revert "Learning Exercise - Phase A.4"
Vaishnavi25-hub Feb 23, 2026
0392e1f
Learning Exercise - Phase A.4
abhidxt299 Feb 23, 2026
ce6a0c8
Learning Exercise - Phase A.1
abhidxt299 Feb 20, 2026
fac66e2
Learning Exercise - Phase A.2
abhidxt299 Feb 20, 2026
fdcfcc0
Learning Exercise - Phase A.3
abhidxt299 Feb 20, 2026
437a287
Learning Exercise - Phase A.4
Vaishnavi25-hub Feb 23, 2026
511f2be
Learning Exercise - Phase A.4
abhidxt299 Feb 23, 2026
cfff3c6
Learning Exercise - Phase A.5
abhidxt299 Feb 23, 2026
3c42e81
Merge branch 'rescue-branch' into learning-challenge
abhidxt299 Feb 23, 2026
48376dd
Learning Exercise - Phase A.6
abhidxt299 Feb 23, 2026
d51980a
Learning Challenge - Phase B.1
abhidxt299 Feb 24, 2026
65f2231
Learning Challenge - Phase B.2
abhidxt299 Feb 24, 2026
666c78c
Learning Challenge - Phase B.3
abhidxt299 Feb 24, 2026
8842dd6
Learning Challenge - Phase B.4
abhidxt299 Feb 24, 2026
dffb838
Learning Challenge - Phase C
abhidxt299 Feb 25, 2026
e190797
Learning Challenge - Phase D.1
abhidxt299 Feb 25, 2026
49bf4fc
Learning Challenge - Phase D.2
abhidxt299 Mar 3, 2026
f3304fc
Learning Challenge - Phase D.3
abhidxt299 Mar 3, 2026
bf18eb3
Learning Challenge - Phase D.4
abhidxt299 Mar 3, 2026
c04ade0
Learning Challenge - Phase D.5
abhidxt299 Mar 3, 2026
835a233
Learning Challenge - Phase E.1
abhidxt299 Mar 5, 2026
6a8be28
Learning Challenge - Phase E.2
abhidxt299 Mar 6, 2026
5b75af1
Learning Challenge - Phase E.3
abhidxt299 Mar 10, 2026
b80558e
Learning Challenge - Phase E.4
abhidxt299 Mar 10, 2026
44882f8
Learning Challenge - Phase E.5
abhidxt299 Mar 11, 2026
8dc75e2
Learning Challenge - Phase E.6
abhidxt299 Mar 11, 2026
dd6527b
Learning Challenge - Phase E.7
abhidxt299 Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GITHUB_TOKEN = <Your_GitHub_Personal_Access_Token>
GROQ_TOKEN = <Your_GROQ_Personal_Access_Token>
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,129 @@
# RepoChat 🤖

Chat with any public GitHub repository. Generate docs, analyze code quality, and ask questions — all powered by Claude AI and RAG.

## Features

- 💬 **Chat** — Ask questions about any part of the codebase. Gets relevant code via semantic search.
- 📄 **Docs** — Auto-generate markdown documentation for any file or the whole repo.
- 🔍 **Analyze** — Scan for bugs, code smells, security issues, and improvement suggestions.
- 📁 **File browser** — Browse indexed files with filtering.
- ⚡ **Streaming** — Chat responses stream in real-time.
- 🧠 **RAG** — ChromaDB + sentence-transformers for semantic retrieval. Scales to large repos.

## Tech Stack

| Layer | Tech |
|-------|------|
| Frontend | React + Vite + Tailwind |
| Backend | FastAPI (Python) |
| AI | Claude claude-sonnet-4-6 (Anthropic) |
| Embeddings | `all-MiniLM-L6-v2` (sentence-transformers) |
| Vector DB | ChromaDB (in-memory) |
| Repo Ingestion | GitHub REST API |
| Deployment | Docker + Compose |

## Quick Start (Local)

### Prerequisites
- Docker & Docker Compose
- An [Anthropic API key](https://console.anthropic.com)
- (Optional) [GitHub token](https://github.com/settings/tokens) for higher rate limits

### 1. Clone & configure
```bash
git clone <this-repo>
cd repo-chat
cp .env.example .env
# Edit .env and add your ANTHROPIC_API_KEY
```

### 2. Run
```bash
docker compose up --build
```

Frontend: http://localhost:3000
Backend API: http://localhost:8000
API Docs: http://localhost:8000/docs

## Local Dev (without Docker)

### Backend
```bash
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill in your keys
uvicorn main:app --reload
```

### Frontend
```bash
cd frontend
npm install
npm run dev
```

## Deploying to Railway

1. Push this repo to GitHub
2. Go to [railway.app](https://railway.app) → New Project → Deploy from GitHub
3. Add two services: `backend` and `frontend` (pointing to respective subdirectories)
4. Set env vars: `ANTHROPIC_API_KEY`, `GITHUB_TOKEN`
5. Deploy!

## Deploying to Render

1. Create two Web Services on [render.com](https://render.com):
- **Backend**: Root dir `backend`, build `pip install -r requirements.txt`, start `uvicorn main:app --host 0.0.0.0 --port $PORT`
- **Frontend**: Root dir `frontend`, build `npm install && npm run build`, serve `dist/`
2. Set env vars on backend service
3. Update `VITE_API_URL` on frontend to point to backend URL if needed

## Deploying to Fly.io

```bash
# Backend
cd backend
fly launch
fly secrets set ANTHROPIC_API_KEY=xxx GITHUB_TOKEN=xxx
fly deploy

# Frontend
cd ../frontend
fly launch
fly deploy
```

## API Reference

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/ingest` | POST | Ingest a public GitHub repo |
| `/api/chat` | POST | Chat with the codebase (SSE stream) |
| `/api/docs` | POST | Generate documentation |
| `/api/analyze` | POST | Analyze code quality |
| `/api/files/{repo_url}` | GET | List indexed files |
| `/health` | GET | Health check |

Full interactive docs at `/docs` (Swagger UI).

## Limitations & Roadmap

**Current limitations:**
- Repo state is in-memory (resets on restart) — swap ChromaDB for persistent storage for production
- Max 300 files, 100KB per file indexed
- No auth layer

**Roadmap:**
- [ ] Persistent vector store (Supabase pgvector, Pinecone)
- [ ] Auth (GitHub OAuth)
- [ ] Share indexed repos between users
- [ ] File-level chat with line references
- [ ] Diff analysis (PR review mode)
- [ ] Export docs to PDF/Word

## License

MIT
53 changes: 53 additions & 0 deletions exercises/phase-0a/async_concurrent_fetching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import asyncio
import time
from typing import List

async def fake_fetch(url: str) -> str:
"""
Simulates fetching data from a URL by sleeping for 1 second.
Args:
url (str): The URL to fetch data from.
Returns:
str: A message indicating that data has been fetched from the URL.
"""

await asyncio.sleep(1)
return f"Fetched data from {url} \n"

async def fetch_all_sequentials(urls: List[str]) -> List[str]:
"""
Fetch data from a list of URLs sequentially.
Args:
urls (List[str]): A list of URLs to fetch data from.
Returns:
List[str]: A list of results from fetching each URL.
"""

results = []
for url in urls:
result = await fake_fetch(url)
results.append(result)
return results

async def fetch_all_concurrents(urls: List[str]) -> list[str]:
"""
Fetch data from a list of URLs concurrently.
Args:
urls (List[str]): A list of URLs to fetch data from.
Returns:
List[str]: A list of results from fetching each URL.
"""

results = await asyncio.gather(*(fake_fetch(url) for url in urls))
return results

urls = ["https://api.github.com/repos/octocat/Hello-World", "https://api.github.com/repos/octocat/Spoon-Knife", "https://api.github.com/repos/octocat/linguist", "https://api.github.com/repos/octocat/atom", "https://api.github.com/repos/octocat/visual-studio-code"]
start_time = time.time()
results = asyncio.run(fetch_all_sequentials(urls))
print(f"Time taken to fetch all URLs sequentially: {time.time() - start_time:.2f} seconds")
print(results)

start_time = time.time()
results = asyncio.run(fetch_all_concurrents(urls))
print(f"Time taken to fetch all URLs concurrently: {time.time() - start_time:.2f} seconds")
print(results)
50 changes: 50 additions & 0 deletions exercises/phase-0a/file_chunk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
class FileChunk:

def __init__ (self, path: str, content: str, chunk_index: int):
self.path = path
self.content = content
self.chunk_index = chunk_index

def preview (self) -> str:
"""
Return a preview of the file chunk, including the file path and the first few characters of the content.

Returns:
str: A string containing the file path and a preview of the content.
"""

result = f"File: {self.path}\nPreview: {self.content[:self.chunk_index]}"
result += "..." if len(result) > self.chunk_index else ""
return result

def word_count (self) -> int:
"""
Count the number of words in the file chunk content.

Returns:
int: The number of words in the content.
"""

result = len(self.content.split())
return result

def __repr__ (self) -> str:
"""
Prints as FileChunk(path='src/main.py', index=0, words=42)

Returns:
str: A string representation of the FileChunk instance.
"""

return f"FileChunk(path='{self.path}', index={self.chunk_index}, words={self.word_count()})"

# Example usage:
chunk = FileChunk(
path="src/main.py",
content="def authenticate(user, password):\n if not user:\n raise ValueError('User required')\n return check_db(user, password)",
chunk_index=0
)

print(chunk.preview())
print(chunk.word_count())
print(chunk)
30 changes: 30 additions & 0 deletions exercises/phase-0a/file_safe_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
def read_file_safe (file_path: str) -> str | None:
"""
Read the contents of a file safely, handling potential exceptions.

Args:
file_path (str): The path to the file to be read.

Returns:
str | None: The contents of the file if successful, otherwise None.
"""
try:
with open(file_path, 'r', encoding = 'utf-8') as f:
print(f"Successfully read file: {file_path}")
return f.read()

except FileNotFoundError:
return "File not found."

except PermissionError:
return "Permission denied."

except UnicodeDecodeError:
return "File is not a text file or contains invalid characters."

# Example usage:
file_content_python = read_file_safe("app/main.py")
print(file_content_python)

file_content_readme = read_file_safe("README.md")
print(file_content_readme)
24 changes: 24 additions & 0 deletions exercises/phase-0a/filter_files_by_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
from typing import List

VALID_EXTENSIONS = {".ipynb", ".py", ".java", ".cpp", ".js", ".html", ".css", ".md", ".txt", ".json", ".xml", ".yml", ".yaml", ".sh", ".go", ".rb", ".erb"}

def filter_code_files(file: List[str]) -> List[str]:
"""
Filter a list of file paths to include only those with valid code extensions.

Args:
file (List[str]): A list of file paths.

Returns:
List[str]: A list of file paths that have valid code extensions.
"""

return [f for f in file if os.path.splitext(f)[1] in VALID_EXTENSIONS]

my_files = ["main.py", "styles.css", "notes.txt", "script.js", "image.png", "config.yaml", "README.md", "data.csv", "archive.zip", "presentation.pptx"]
test_files = []
filtered = filter_code_files(my_files)
filtered_empty = filter_code_files(test_files)
print(filtered)
print(filtered_empty)
40 changes: 40 additions & 0 deletions exercises/phase-0a/group_by_directory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from typing import List

def group_by_directory(file_path: List[str]) -> dict[str, List[str]]:
"""
Group a list of file paths by their directory.

Args:
file (List[str]): A list of file paths.

Returns:
dict[str, List[str]]: A dictionary where the keys are directory paths and the values are lists of file names in those directories.
"""

grouped_files = {}

for f in file_path:
dir_path_parts = f.split('/')

if len(dir_path_parts) > 1:
key = dir_path_parts[0]
else:
key = "/"

if key not in grouped_files:
grouped_files[key] = []

grouped_files[key].append(f)

return grouped_files

file_paths = [
"src/main.py",
"src/utils.py",
"src/models/user.py",
"tests/test_main.py",
"README.md",
"requirements.txt",
]
result = group_by_directory(file_paths)
print(result)
Loading