diff --git a/README.md b/README.md index df8b8f6..4183d9c 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ # Projectify -An AI-powered project brief generator for developers. Give it a topic, difficulty level, tech stack, and your available hours per week β€” it returns a complete project brief with features, milestones, file structure, an architecture diagram, downloadable starter code, learning outcomes, and resources. It can also review any public GitHub repo and score it as a portfolio piece. +Projectify is an AI-powered project brief generator that creates structured software project plans based on a topic, difficulty, preferred tech stack, and available weekly hours. It also reviews public GitHub repositories and provides portfolio-oriented feedback. -Built as a microservices architecture with a LangGraph-orchestrated LLM pipeline: idea generation β†’ scope validation β†’ full brief expansion, grounded by retrieval over a curated corpus of real project patterns. - -**Live:** [projectify-ai.onrender.com](https://projectify-ai.onrender.com/health) - -> **πŸ” Self-reviewed via Projectify's own `/review-repo` endpoint** (as of 2026-07-02, post-cleanup): -> **LLM mode** β€” architecture clarity **8/10**, test coverage signal **7/10**, documentation quality **9/10**, hiring signal **8/10**. -> Heuristic mode (rule-based counting, not an AI judgment) scored the same repo 10 / 5 / 10 / 8 β€” a good illustration of why results are mode-labeled. +## Features ---- +- Generate complete project briefs using AI +- Milestones, learning roadmap, and project structure +- Mermaid architecture diagrams +- Downloadable starter code +- GitHub repository reviewer with AI-based scoring +- Retrieval-Augmented Generation (RAG) using ChromaDB +- JWT authentication and project history ## Architecture -``` +```text β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ React │────▢│ Node.js/Express │────▢│ FastAPI β”‚ β”‚ Frontend β”‚ β”‚ Backend (API) β”‚ β”‚ AI Service β”‚ @@ -27,379 +27,58 @@ Built as a microservices architecture with a LangGraph-orchestrated LLM pipeline β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -**LangGraph pipeline inside the AI service** (`graph.py`) β€” an explicit state graph with five named nodes: +### AI Workflow ``` -planner β†’ requirements β†’ architecture β†’ generator β†’ reviewer +Planner β†’ Requirements β†’ Architecture β†’ Generator β†’ Reviewer ``` -- **planner** (Chain 1, `chains/idea.py`) β€” generates the raw project idea, grounded by top-k retrieval over the Chroma seed corpus -- **requirements** (Chain 2, `chains/validate.py`) β€” validates and adjusts scope to difficulty and available hours -- **architecture** β€” normalizes the inputs the expansion step depends on -- **generator** (Chain 3, `chains/expand.py`) β€” expands into the full brief: milestones, file structure, starter code skeletons, Mermaid architecture diagram -- **reviewer** β€” final completeness check on the brief - -Each chain calls `openrouter/free` (OpenRouter's free-model auto-router). Everything falls back to mock mode if no API key is set β€” useful for local development without burning credits. - -A separate chain (`chains/review.py`) powers the GitHub repo reviewer: it fetches a repo's file tree and README via the GitHub REST API (cached, rate-limit-aware) and scores it on architecture clarity, test coverage signal, documentation quality, and hiring signal. Every response carries a `mode` field: `llm` means a model actually read the tree and README and judged each category; `heuristic` (used when no API key is set) means the scores come from rule-based counting β€” number of test files, presence of README sections, Docker/CI markers β€” **not** an AI judgment. The two are not comparable, and the UI labels which one you're looking at. - ---- - ## Tech Stack -| Layer | Technology | -|---|---| -| Frontend | React, Vite, Mermaid (diagram rendering), JSZip (starter-file downloads) | -| Backend | Node.js, Express, JWT auth, Helmet | -| AI Service | Python, FastAPI, LangChain, LangGraph | -| Vector Store | ChromaDB (RAG grounding corpus) | -| Database | MongoDB | -| LLM Provider | OpenRouter (`openrouter/free` auto-router) | -| Containerization | Docker, Docker Compose | -| CI/CD | GitHub Actions | -| Deployment | Render | - ---- - -## Features - -- **AI-generated project briefs** β€” title, description, core features, stretch goals, milestones, file structure, learning outcomes, resources -- **Starter code skeletons** β€” each brief includes minimal, stack-appropriate starter files (`skeleton_files`), downloadable as a zip from the results page -- **Architecture diagrams** β€” every brief ships a Mermaid `graph TD` diagram of the proposed architecture, rendered inline -- **GitHub repo reviewer** β€” paste any public repo URL and get 1–10 scores with rationales for architecture clarity, test coverage signal, documentation quality, and overall hiring signal; results are labeled `llm` (model-judged) or `heuristic` (rule-based file/README counting when no API key is set β€” not an AI judgment) -- **LangGraph orchestration** β€” the generation pipeline is an explicit five-node state graph (planner β†’ requirements β†’ architecture β†’ generator β†’ reviewer) -- **RAG grounding** β€” idea generation retrieves similar patterns from a curated 38-entry corpus in ChromaDB and injects them as prompt context -- **Mock fallback** β€” works fully without an API key (and without a populated vector store) for local development and CI -- **Rate limiting** β€” 5 requests per minute per IP (AI service) and per user (Node server) -- **Input validation** β€” Pydantic on the Python side, custom validation on the Node side -- **JWT authentication** β€” signup, login, token-based access on all protected routes -- **Security headers & CORS allowlists** β€” Helmet on Express; both services restrict origins via `ALLOWED_ORIGINS` -- **Project persistence** β€” save, view, and delete generated projects and repo reviews per user - ---- +- **Frontend:** React, Vite +- **Backend:** Node.js, Express +- **AI Service:** FastAPI, LangChain, LangGraph +- **Database:** MongoDB +- **Vector Store:** ChromaDB +- **LLM:** OpenRouter +- **DevOps:** Docker, GitHub Actions, Render ## Project Structure -``` +```text Projectify/ -β”œβ”€β”€ ai_service/ # FastAPI AI service -β”‚ β”œβ”€β”€ chains/ -β”‚ β”‚ β”œβ”€β”€ idea.py # Chain 1: generates raw project idea (RAG-grounded) -β”‚ β”‚ β”œβ”€β”€ validate.py # Chain 2: validates and adjusts scope -β”‚ β”‚ β”œβ”€β”€ expand.py # Chain 3: full brief + skeletons + Mermaid diagram -β”‚ β”‚ └── review.py # Chain 4: GitHub repo reviewer -β”‚ β”œβ”€β”€ data/ -β”‚ β”‚ └── project_patterns.json # 38-entry RAG seed corpus -β”‚ β”œβ”€β”€ scripts/ -β”‚ β”‚ └── ingest.py # One-off: embed seed corpus into Chroma -β”‚ β”œβ”€β”€ models/ -β”‚ β”‚ └── schemas.py # Pydantic request/response schemas -β”‚ β”œβ”€β”€ tests/ -β”‚ β”‚ β”œβ”€β”€ conftest.py -β”‚ β”‚ └── test_ai_service.py # 83 tests -β”‚ β”œβ”€β”€ graph.py # LangGraph pipeline (5 named nodes) -β”‚ β”œβ”€β”€ retrieval.py # Chroma retrieval + deterministic embeddings -β”‚ β”œβ”€β”€ main.py # FastAPI app, rate limiting, validation -β”‚ β”œβ”€β”€ requirements.txt -β”‚ └── Dockerfile -β”œβ”€β”€ server/ # Node.js Express backend -β”‚ β”œβ”€β”€ routes/ -β”‚ β”‚ β”œβ”€β”€ auth.js # Signup, login -β”‚ β”‚ β”œβ”€β”€ generate.js # Calls AI service, saves to DB -β”‚ β”‚ β”œβ”€β”€ review.js # Proxies repo reviews, saves per user -β”‚ β”‚ └── projects.js # CRUD for saved projects -β”‚ β”œβ”€β”€ models/ -β”‚ β”‚ β”œβ”€β”€ User.js -β”‚ β”‚ β”œβ”€β”€ Project.js -β”‚ β”‚ └── Review.js -β”‚ β”œβ”€β”€ middleware/ -β”‚ β”‚ └── auth.js # JWT verification -β”‚ β”œβ”€β”€ tests/ -β”‚ β”‚ └── server.test.js # 54 tests -β”‚ β”œβ”€β”€ index.js # Express app, Helmet, CORS allowlist -β”‚ β”œβ”€β”€ jest.config.json -β”‚ └── Dockerfile -β”œβ”€β”€ client/ # React frontend -β”‚ β”œβ”€β”€ src/pages/ # Home, Result, Saved, ReviewRepo, auth pages -β”‚ β”œβ”€β”€ src/components/ # MermaidDiagram, Milestone, Navbar, ... -β”‚ └── Dockerfile -β”œβ”€β”€ .github/ -β”‚ └── workflows/ -β”‚ └── ci.yml # CI/CD pipeline -β”œβ”€β”€ LICENSE # MIT +β”œβ”€β”€ client/ +β”œβ”€β”€ server/ +β”œβ”€β”€ ai_service/ +β”œβ”€β”€ .github/workflows/ β”œβ”€β”€ docker-compose.yml -└── render.yaml # Render deployment config +└── render.yaml ``` ---- - ## Getting Started -### Prerequisites - -- Node.js 20+ -- Python 3.11+ -- Docker and Docker Compose -- MongoDB (or use Docker) -- OpenRouter API key (free tier works β€” get one at [openrouter.ai](https://openrouter.ai)) - -### Local Setup - -**1. Clone the repo** ```bash git clone https://github.com/SamridhiShreyaa/Projectify.git cd Projectify -``` - -**2. Set up environment variables** - -`ai_service/.env`: -``` -OPENROUTER_API_KEY=sk-or-v1-your-key-here -OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 -ALLOWED_ORIGINS=http://localhost:3000 -``` - -`server/.env`: -``` -PORT=5000 -MONGO_URI=mongodb://localhost:27017/projectify -JWT_SECRET=your-secret-key -AI_SERVICE_URL=http://localhost:8001 -``` - -**3. Run with Docker Compose** -```bash docker compose up --build ``` -Or run services individually: +Create `.env` files for `server` and `ai_service` using the provided `.env.example` files. -```bash -# AI Service -cd ai_service -pip install -r requirements.txt -uvicorn main:app --port 8001 --reload - -# Server -cd server -npm install -npm run dev +## Testing -# Client -cd client -npm install -npm run dev -``` - -**4. Verify everything is running** -```bash -curl http://localhost:8001/health -# {"status":"ok","mode":"llm","rate_limit":"5 requests per 60s"} -``` - -If `mode` is `mock`, your `OPENROUTER_API_KEY` isn't being picked up. If no key is set intentionally, mock mode returns realistic pre-built responses β€” fine for development. - ---- - -## API Reference - -### AI Service (port 8001) - -#### `POST /generate` -Generate a project brief. - -**Request:** -```json -{ - "topic": "web development", - "difficulty": "intermediate", - "stack": "React, Node.js, MongoDB", - "hours_per_week": 10 -} -``` - -**Constraints:** -- `topic`: 3–200 characters -- `difficulty`: `beginner` | `intermediate` | `advanced` -- `stack`: 2–200 characters -- `hours_per_week`: 1–80 - -**Response:** -```json -{ - "title": "Real-Time Collaborative Whiteboard", - "description": "...", - "core_features": ["...", "..."], - "stretch_goals": ["...", "..."], - "milestones": ["Week 1: ...", "Week 2: ...", "Week 3: ...", "Week 4: ..."], - "file_structure": "project/\nβ”œβ”€β”€ src/\n...", - "learning_outcomes": ["...", "..."], - "resources": ["MDN Web Docs β€” https://developer.mozilla.org", "..."], - "scope_notes": "Scope looks appropriate.", - "skeleton_files": [{"path": "src/App.jsx", "content": "..."}], - "mermaid_diagram": "graph TD\n U[User Browser] --> C[React Client]\n ..." -} -``` - -**Rate limit:** 5 requests per 60 seconds per IP. - -#### `POST /review-repo` -Score a public GitHub repository as a portfolio piece. - -**Request:** -```json -{"repo_url": "https://github.com/owner/repo"} -``` - -**Response:** -```json -{ - "repo": "owner/repo", - "mode": "llm", - "scores": { - "architecture_clarity": {"score": 7, "rationale": "..."}, - "test_coverage_signal": {"score": 6, "rationale": "..."}, - "documentation_quality": {"score": 8, "rationale": "..."}, - "hiring_signal": {"score": 7, "rationale": "..."} - } -} -``` - -`mode` is `llm` when a model judged the repo, or `heuristic` when no API key is configured β€” heuristic scores are rule-based counting (test-file count, README sections, Docker/CI markers), not an AI assessment. - -**Errors:** `422` invalid URL Β· `404` repo missing or private Β· `503` GitHub API rate limit reached (unauthenticated GitHub calls are cached for 10 minutes to stay under the 60 req/hour limit). - -#### `GET /health` -```json -{"status": "ok", "mode": "llm", "rate_limit": "5 requests per 60s"} -``` - -### Backend Server (port 5000) - -All `/api/generate`, `/api/projects`, and `/api/review` routes require `Authorization: Bearer `. - -| Method | Route | Description | -|---|---|---| -| POST | `/api/auth/signup` | Register a new user | -| POST | `/api/auth/login` | Login, returns JWT | -| POST | `/api/generate` | Generate and save a project brief | -| POST | `/api/review` | Review a GitHub repo, save the result | -| GET | `/api/review` | Get the user's past repo reviews | -| GET | `/api/projects` | Get all projects for logged-in user | -| DELETE | `/api/projects/:id` | Delete a project | -| GET | `/api/health` | Health check with DB status | - ---- - -## Running Tests - -**Python (83 tests):** ```bash cd ai_service -pytest tests/ -v -``` +pytest -**Node.js (54 tests):** -```bash -cd server +cd ../server npm test ``` -Tests run in full mock/in-memory mode β€” no API keys, real database, or network needed (GitHub responses are mocked; retrieval tests use a temporary Chroma store). - -**What's tested:** -- Health endpoint and mode reporting -- `/generate` happy path β€” response shape, all difficulty levels, skeleton files, Mermaid diagram syntax -- LangGraph pipeline β€” node execution order, end-to-end mock mode -- RAG retrieval β€” ingest + retrieve for a known topic, empty-store fallback -- `/review-repo` β€” URL parsing, mocked GitHub happy path, response caching, 404/422/503 handling -- Input validation β€” 11 invalid payload cases -- Rate limiting β€” window enforcement and reset, per-endpoint -- Auth β€” signup, login, JWT structure, password hashing -- CORS allowlist and Helmet security headers -- AI service error handling β€” 503, 504, 429 mapping -- Project & review persistence β€” user isolation, sort order, ownership checks -- Chain unit tests β€” idea generation, scope validation, expansion logic - ---- - -## CI/CD Pipeline - -Every push and pull request runs: - -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Test AI Service β”‚ β”‚ Test Server β”‚ -β”‚ pytest + ruff lint β”‚ β”‚ jest β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β–Ό - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ Docker Build Check β”‚ - β”‚ compose build + β”‚ - β”‚ containers start β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ (main branch only) - β–Ό - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ Post-Deploy Smoke β”‚ - β”‚ Test /health on β”‚ - β”‚ live Render URLs β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` - -Branch protection on `main` requires all three CI jobs to pass before merging. - ---- - ## Deployment -Deployed on Render via `render.yaml`. Both services auto-deploy on push to `main`. - -- AI Service: [projectify-ai.onrender.com](https://projectify-ai.onrender.com/health) -- Backend: projectify-api.onrender.com - -> **Note:** Render free tier services spin down after inactivity. First request after a period of inactivity may take 30–60 seconds. - ---- - -## Environment Variables Reference - -| Variable | Service | Description | -|---|---|---| -| `OPENROUTER_API_KEY` | ai_service | OpenRouter API key for LLM calls | -| `OPENROUTER_BASE_URL` | ai_service | OpenRouter base URL | -| `ALLOWED_ORIGINS` | ai_service | Comma-separated allowed CORS origins | -| `RATE_LIMIT_MAX` | ai_service | Max requests per window (default: 5) | -| `RATE_LIMIT_WINDOW` | ai_service | Window in seconds (default: 60) | -| `CHROMA_DIR` | ai_service | Chroma persistence dir (default: `ai_service/.chroma`) | -| `PORT` | server | Server port (default: 5000) | -| `MONGO_URI` | server | MongoDB connection string | -| `JWT_SECRET` | server | Secret for signing JWTs | -| `AI_SERVICE_URL` | server | URL of the AI service | -| `ALLOWED_ORIGINS` | server | Comma-separated allowed CORS origins (default: `http://localhost:3000`) | - -To populate the RAG corpus locally (optional β€” generation works ungrounded without it): -```bash -cd ai_service -python scripts/ingest.py -``` - ---- - -## Design Decisions - -**Why LangGraph?** The pipeline was originally three chained function calls inside the `/generate` handler. That worked, but the control flow was implicit β€” you had to read the handler to know what ran when, and there was no place to attach cross-cutting concerns. Moving it into an explicit `StateGraph` with named nodes (`planner β†’ requirements β†’ architecture β†’ generator β†’ reviewer`) makes the pipeline self-describing, records an execution trace in state (which the tests assert on), and gives future features an obvious seam: conditional edges (e.g. re-planning when the reviewer flags gaps), retries per node, or parallel branches β€” none of which fit naturally in straight-line code. The refactor deliberately changed no behavior: the same three chains run in the same order with the same arguments, and mock mode is untouched. - -**Why Chroma over a hosted vector DB?** The retrieval corpus is 38 documents. A hosted vector DB (Pinecone, Weaviate Cloud, etc.) would add an API key, a network dependency in every environment including CI, latency, and a bill β€” for a dataset that fits in memory a thousand times over. Chroma runs embedded in the service process, persists to a local directory, and needs zero infrastructure. - -**The embeddings are not a trained model β€” read this before judging retrieval quality.** The "embeddings" are deterministic feature-hashed bag-of-words vectors computed in-process (`retrieval.py:embed_text`): tokens are MD5-hashed into a 512-dim vector and L2-normalized. This is lexical keyword overlap, **not** semantic similarity β€” "car" and "automobile" are unrelated vectors here, where a real embedding model would place them together. It was chosen deliberately so CI and local dev run fully offline: no model downloads (Chroma's default embedder pulls an ONNX model from the network), no embedding API key, and bit-for-bit reproducible tests. For a 38-entry corpus queried by topic/stack keywords, lexical matching is adequate. Swapping in a real embedding later is straightforward and confined to `retrieval.py`: replace `embed_text` with a sentence-transformers model or an API-based embedding (e.g. OpenAI/Voyage), re-run `scripts/ingest.py` to re-embed the corpus, and keep the same Chroma calls β€” nothing upstream changes. Retrieval is also strictly best-effort: an empty or missing store falls back to ungrounded generation rather than erroring. - -**In-memory vs Redis rate limiter.** Both services rate-limit with in-process maps (IP-keyed in FastAPI, user-keyed in Express). The tradeoff is deliberate: an in-memory limiter is zero-dependency and exactly right for a single-instance deployment (which Render free tier is), but it resets on restart and doesn't share state across replicas β€” scale to two instances and each enforces its own window, doubling the effective limit. Redis-backed limiting (e.g. `express-rate-limit` + `rate-limit-redis`) fixes both at the cost of running Redis everywhere, including local dev and CI. Until there's more than one replica, that cost buys nothing; the code paths are small and isolated (`checkRateLimit` / `_check_rate_limit`), so the swap is mechanical when it's needed. - ---- +Hosted on Render with automatic deployment from the `main` branch. ## License -This project is licensed under the [MIT License](LICENSE). +MIT