Version: 2.0 | Type: Portfolio SaaS Product | Stack: Next.js 14 + TypeScript + Tailwind + Supabase + Gemini API
What we are building: RepoDoc is an AI-powered documentation intelligence platform that connects to your GitHub repositories, reads your actual code, generates accurate README files, raises PRs automatically, and monitors documentation drift over time — keeping your docs always in sync with your codebase.
Target User:
- Individual developers with OSS or portfolio projects
- Small engineering teams (2–10 devs)
- Open source maintainers tired of stale documentation
Core Value Proposition:
"RepoDoc reads your code, writes your docs, and tells you when they break — automatically."
The 5 pillars:
- 🔥 Roast My README — viral public tool, zero login required
- 🧠 GitHub Repo Analyzer — reads real code, not user descriptions
- ⚡ Smart README Generator — accurate, specific, code-grounded output
- 🤖 Auto PR Creation — raises a real GitHub PR with updated docs
- 🔄 Doc Drift Detection + History — monitors staleness over time with chart
What makes this impressive on a resume:
- Full OAuth + GitHub write-scope integration (branch creation, PR creation)
- Real PostgreSQL database with relational schema and Row Level Security
- Async analysis pattern with real-time progress feedback
- Time-series data + recharts visualization
- Production deployment with proper environment management
- End-to-end AI pipeline: code → analysis → generation → PR → monitoring
┌─────────────────────────────────────────────────────────────────────┐
│ FRONTEND │
│ Next.js 14 App Router + TypeScript + Tailwind │
│ │
│ / → Landing + Roast My README │
│ /dashboard → Repo grid + quick actions │
│ /repo/[owner]/[repo] → Repo workspace: analyze/generate/drift │
│ /repo/[owner]/[repo]/history → Drift score history + chart │
└───────────────────────────────┬─────────────────────────────────────┘
│ HTTP + Server Actions
┌───────────────────────────────▼─────────────────────────────────────┐
│ NEXT.JS API ROUTES │
│ │
│ POST /api/roast → Score a README (public, no auth) │
│ POST /api/analyze → Trigger repo analysis │
│ POST /api/generate → Generate README from analysis │
│ POST /api/pr → Raise GitHub PR with new README │
│ POST /api/drift → Run drift check, save to DB │
│ GET /api/drift/history → Fetch drift score history │
│ GET /api/repos → List user's GitHub repos │
│ GET /api/auth/[...nextauth] → GitHub OAuth │
└────┬──────────────────────────┬──────────────────────────────────┬──┘
│ │ │
┌────▼────────┐ ┌──────────▼──────────┐ ┌──────────▼──┐
│ GitHub API │ │ Gemini API │ │ Supabase │
│ (Octokit) │ │ (gemini-2.5-flash) │ │ (PostgreSQL)│
│ │ │ │ │ │
│ OAuth flow │ │ Roast prompts │ │ users │
│ Repo tree │ │ Analysis prompts │ │ repos │
│ File fetch │ │ Generation prompts │ │ analyses │
│ Branch ops │ │ Drift prompts │ │ readmes │
│ PR creation │ │ │ │ drift_logs │
└─────────────┘ └──────────────────────┘ └─────────────┘
| Decision | Choice | Reason |
|---|---|---|
| Database | Supabase (PostgreSQL) | Free tier, instant setup, Row Level Security, looks great on resume |
| Auth | NextAuth.js + GitHub Provider | Handles token refresh, session, easy to extend |
| AI Model | gemini-2.5-flash | Fast (< 3s), cheap, sufficient. Upgrade path: gemini-2.5-pro |
| Deployment | Vercel | Zero-config for Next.js, free tier, preview deployments |
| GitHub Integration | OAuth App with repo scope |
Sufficient for portfolio; GitHub App is V2 |
| Styling | Tailwind + shadcn/ui | Fast, professional, what real SaaS products look like |
/repodoc
├── /app
│ ├── /api
│ │ ├── /auth
│ │ │ └── [...nextauth]/route.ts # GitHub OAuth via NextAuth
│ │ ├── /roast/route.ts # POST: score README by URL
│ │ ├── /analyze/route.ts # POST: analyze repo
│ │ ├── /generate/route.ts # POST: generate README
│ │ ├── /pr/route.ts # POST: raise GitHub PR
│ │ ├── /drift/route.ts # POST: run drift check
│ │ ├── /drift/history/route.ts # GET: drift score history
│ │ └── /repos/route.ts # GET: user's GitHub repos
│ ├── /dashboard/page.tsx # Repo grid
│ ├── /repo/[owner]/[repo]/page.tsx # Main repo workspace
│ ├── /repo/[owner]/[repo]/history/page.tsx # Drift history chart
│ ├── layout.tsx # Root layout + SessionProvider
│ └── page.tsx # Landing + Roast My README
│
├── /components
│ ├── /ui # shadcn/ui components
│ ├── RoastInput.tsx # URL input + submit
│ ├── RoastResult.tsx # Grade + issues display
│ ├── RepoCard.tsx # Card for dashboard grid
│ ├── AnalysisProgress.tsx # Animated step progress
│ ├── ReadmeEditor.tsx # Split pane markdown editor
│ ├── PrCreator.tsx # Branch + PR creation modal
│ ├── DriftPanel.tsx # Drift score + changed items
│ ├── DriftChart.tsx # recharts line chart
│ └── Navbar.tsx # Top nav with auth state
│
├── /lib
│ ├── github.ts # Octokit + all GitHub helpers
│ ├── gemini.ts # Gemini client + timeout/fallback
│ ├── analyzer.ts # Repo analysis logic
│ ├── generator.ts # README generation logic
│ ├── drift.ts # Drift detection logic
│ └── supabase.ts # Supabase server + browser clients
│
├── /db
│ ├── schema.sql # Full database schema
│ └── queries.ts # All DB queries in one place
│
├── /prompts
│ ├── roast.ts # Roast prompt builder
│ ├── analyze.ts # Analysis prompt builder
│ ├── generate.ts # Generation prompt builder
│ └── drift.ts # Drift prompt builder
│
├── /types/index.ts # All shared TypeScript interfaces
│
├── /utils
│ ├── github-url.ts # Parse + validate GitHub URLs
│ ├── markdown.ts # Markdown helpers
│ ├── rate-limit.ts # Simple IP rate limiter
│ └── constants.ts # App-wide constants
│
├── /hooks
│ ├── useAnalysis.ts # Analysis state + polling
│ ├── useReadme.ts # README generation state
│ └── useDrift.ts # Drift detection state
│
├── middleware.ts # Protect /dashboard and /repo routes
├── .env.local
├── next.config.ts
└── package.json
Folder purpose summary:
/app/api/*— All server-side logic. One file per feature. No business logic in components./lib— All reusable business logic. This is where the real work lives./prompts— All LLM prompts isolated. Change a prompt without touching feature code./db— Schema + all queries together. No raw SQL scattered around the codebase./types— Single source of truth for all data shapes across frontend and backend./hooks— Client-side state management for async operations.
-- /db/schema.sql
-- Paste and run this in Supabase SQL Editor
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
github_id TEXT UNIQUE NOT NULL,
github_username TEXT NOT NULL,
github_avatar TEXT,
email TEXT,
access_token TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE repos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
owner TEXT NOT NULL,
name TEXT NOT NULL,
full_name TEXT NOT NULL,
is_private BOOLEAN DEFAULT FALSE,
default_branch TEXT DEFAULT 'main',
last_analyzed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, full_name)
);
CREATE TABLE analyses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID REFERENCES repos(id) ON DELETE CASCADE,
analysis_data JSONB NOT NULL,
file_tree JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE readmes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID REFERENCES repos(id) ON DELETE CASCADE,
analysis_id UUID REFERENCES analyses(id),
content TEXT NOT NULL,
version INTEGER DEFAULT 1,
pr_url TEXT,
pr_number INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE drift_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID REFERENCES repos(id) ON DELETE CASCADE,
drift_score INTEGER NOT NULL,
status TEXT NOT NULL,
drift_data JSONB NOT NULL,
readme_id UUID REFERENCES readmes(id),
checked_at TIMESTAMPTZ DEFAULT NOW()
);
-- Row Level Security
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE repos ENABLE ROW LEVEL SECURITY;
ALTER TABLE analyses ENABLE ROW LEVEL SECURITY;
ALTER TABLE readmes ENABLE ROW LEVEL SECURITY;
ALTER TABLE drift_logs ENABLE ROW LEVEL SECURITY;Goal: Public zero-login tool. Viral entry point. No DB writes.
User Flow:
- User lands on
/ - Pastes any public GitHub repo URL
- Clicks "Roast It 🔥"
- Sees letter grade + specific issues in ~5 seconds
- CTA: "Fix this automatically →" → sign in + full product
Backend Logic (/api/roast/route.ts):
- Validate URL (client-side first: must match
github.com/{owner}/{repo}) - Extract owner/repo with regex:
/github\.com\/([^\/]+)\/([^\/]+)/ - Fetch README:
GET https://api.github.com/repos/{owner}/{repo}/readmewith headerAccept: application/vnd.github.raw+json - Build roast prompt → call Gemini → parse JSON response
- Rate limit: 10 requests per IP per hour
- Return
RoastResult
Frontend UI:
- Hero: bold headline + URL input + "Roast It 🔥" button
- Loading: skeleton with "Reading your README..."
- Result: large letter grade (color coded A=green → F=red) + score + summary + issues list
- Share button: copy URL with
?score=B&repo=owner/repoparams - CTA card: "Let AI fix this in 60 seconds →"
- Below fold: 3-step "How it works" explainer
Edge Cases:
- Private repo → "This repo is private. Sign in to analyze private repos."
- No README → grade F, top issue: "No README.md found in this repository."
- Invalid URL → client-side validation, never hits API
- GitHub rate limit (60 req/hr unauthenticated) → catch 403 → "GitHub rate limit reached. Try again shortly."
Goal: Read actual repo code and extract structured technical context. Foundation for all other features.
User Flow:
- Authenticated user opens
/repo/{owner}/{repo} - Clicks "Analyze Repo" (or auto-triggers if never analyzed)
- Sees named progress steps animate in real time
- Analysis completes → stored in DB → README generation starts automatically
Backend Logic (/api/analyze/route.ts):
- Auth check — 401 if no session
- Receive
{ owner, repo } - Upsert repo in
repostable - Fetch file tree:
GET /repos/{owner}/{repo}/git/trees/HEAD?recursive=1 - Identify key files to fetch (priority order, max 20 files total):
- Manifest:
package.json/pyproject.toml/Cargo.toml/go.mod - Environment:
.env.example/.env.sample/.env.template - Entry point:
src/index.*/app/main.*/cmd/main.*/main.* - Routes: files matching
*route*/*router*/urls.py - Config:
Dockerfile/docker-compose.yml/.github/workflows/*.yml - Docs:
README.md(existing)
- Manifest:
- Fetch each file, truncate to 300 lines
- Build prompt input → call Gemini → parse
RepoAnalysisJSON - Save to
analysestable + updaterepos.last_analyzed_at - Return
{ analysisId, analysis }
Edge Cases:
- No manifest → infer language from file extensions
- Repo > 5000 files → traverse top 2 directory levels only
- File fetch 404 → skip, continue with available data
- Gemini JSON parse failure → strip backticks, retry once with stricter prompt instruction
- Re-analysis → create new
analysesrow, keep full history
Goal: Use RepoAnalysis to produce README with real function names, real commands, real env vars.
User Flow:
- Auto-triggers after analysis completes (or manual "Generate" button)
- ~8–15 second generation
- Split view: editable raw markdown (left) + live rendered preview (right)
- User can edit, copy, download, or raise a PR
Backend Logic (/api/generate/route.ts):
- Auth check
- Receive
{ repoId, analysisId } - Fetch
RepoAnalysisfrom DB - Build generation prompt → call Gemini → receive raw markdown
- Strip any backtick wrappers from response
- Get current max version for this repo → increment
- Save to
readmestable - Return
{ readmeId, content, version }
Frontend UI:
- Left pane: editable
<textarea>(monospace font) - Right pane:
<ReactMarkdown remark-gfm>rendered preview - Sync: re-render on textarea change (debounced 300ms)
- Buttons: Copy | Download
README.md| Regenerate - Version badge: "Version 3 — 2 minutes ago"
- "Raise PR →" button at bottom
Edge Cases:
- Gemini wraps output in backticks → strip before saving
- Minimal analysis (empty repo) → generate skeleton with
[TODO]placeholders - User edits → save on textarea blur, not every keystroke
- Multiple versions → version switcher dropdown
Goal: Raise a real GitHub PR with the generated README. Most impressive feature for a resume/portfolio.
User Flow:
- User clicks "Raise PR 🚀" button in ReadmeEditor
- Modal opens: branch name + PR title + PR description (all pre-filled, all editable)
- User clicks "Create PR"
- Step indicators: "Creating branch... Uploading file... Opening PR..."
- Success: PR URL + "View on GitHub →" link
- PR URL saved to DB
Backend Logic (/api/pr/route.ts):
- Auth check
- Receive
{ repoId, readmeId, branchName, prTitle, prBody } - Fetch repo + README content + user access token from DB
- Step 1 — Get default branch SHA:
GET /repos/{owner}/{repo}/git/ref/heads/{defaultBranch}→ extract SHA - Step 2 — Create new branch:
POST /repos/{owner}/{repo}/git/refsBody:{ ref: "refs/heads/{branchName}", sha: "{defaultBranchSHA}" } - Step 3 — Get existing README SHA (if file exists):
GET /repos/{owner}/{repo}/contents/README.md→ extract SHA - Step 4 — Create or update README on new branch:
PUT /repos/{owner}/{repo}/contents/README.mdBody:{ message: "docs: update README via RepoDoc", content: base64(readmeContent), branch: branchName, sha: existingFileSHA (if exists) } - Step 5 — Create PR:
POST /repos/{owner}/{repo}/pullsBody:{ title, body: prBody + footer, head: branchName, base: defaultBranch } - Save
pr_url+pr_numbertoreadmestable - Return
{ prUrl, prNumber }
Edge Cases:
- Branch already exists → append
-{unix_timestamp}to name, retry once - README.md doesn't exist → omit
shafrom PUT body (creates new file) - Token lacks
repowrite scope → "Reconnect GitHub with write permissions to create PRs." - PR already open for this branch → surface GitHub error clearly
Goal: Monitor documentation staleness. Show history chart. Create returning users.
User Flow:
- User clicks "Check Drift" on repo workspace
- System re-runs analysis, compares to last saved analysis
- Drift report: score + changed items + recommendation
- User visits
/repo/{owner}/{repo}/historyfor full score chart - CTA: "Regenerate README" if drift is significant
Backend Logic (/api/drift/route.ts):
- Auth check
- Receive
{ repoId } - Fetch most recent analysis from DB
- If none → return
{ status: 'no_baseline', message: 'Analyze your repo first to enable drift detection.' } - Run new analysis (same logic as Feature 2)
- Fetch current README content from DB
- Build drift prompt with old + new analysis + current README
- Call Gemini → parse
DriftReportJSON - Save to
drift_logstable - Return
DriftReport
Backend Logic (/api/drift/history/route.ts):
- Auth check
- Receive
{ repoId }from query params - Query all
drift_logsfor this repo ordered bychecked_atASC - Return
{ date: checked_at, score: drift_score, status }[]
Frontend (DriftPanel.tsx):
- Large drift score with color (≥80 green, 60–79 yellow, 40–59 orange, <40 red)
- Status badge: "In Sync" / "Minor Drift" / "Moderate Drift" / "Major Drift"
- Summary sentence
- Changed items list: category tag + severity badge + what changed
- "Regenerate README" CTA if score < 80
Frontend (DriftChart.tsx):
rechartsLineChart- X-axis: check date | Y-axis: drift score 0–100
- Horizontal reference lines at 80 (green), 60 (yellow), 40 (orange)
- Custom tooltip showing score + status on hover
- Empty state: "Run your first drift check to start tracking"
Edge Cases:
- No changes since last analysis → score 100, message: "Fully in sync. Your README accurately reflects your code."
- Drift check same day multiple times → save all, show all in chart
- Repo 404 on re-analysis → show error, preserve last known score
// /prompts/roast.ts
export function buildRoastPrompt(readmeContent: string, repoName: string): string {
return `
You are a brutally honest senior developer reviewing README quality.
Analyze the following README and return a strict JSON evaluation.
Repository: ${repoName}
README Content:
---
${readmeContent.slice(0, 4000)}
---
Score each criterion 0–10:
1. clarity — Project purpose obvious in first 2 lines?
2. setup — Installation steps present and specific?
3. usage — Real code examples provided?
4. structure — Sections organized logically?
5. completeness — License, contributing guide, contact present?
6. specificity — Content specific to THIS project or generic filler?
Grade: A=90–100, B=75–89, C=60–74, D=45–59, F=0–44
Overall score = average of all 6 × 10
Return ONLY valid JSON. No explanation. No markdown fences. No backticks.
{
"grade": "B",
"score": 68,
"summary": "One brutal sentence about the single biggest problem",
"criteria": {
"clarity": { "score": 7, "issue": "specific issue or null" },
"setup": { "score": 4, "issue": "specific issue or null" },
"usage": { "score": 3, "issue": "specific issue or null" },
"structure": { "score": 8, "issue": "specific issue or null" },
"completeness": { "score": 5, "issue": "specific issue or null" },
"specificity": { "score": 6, "issue": "specific issue or null" }
},
"top_issues": [
"Specific actionable issue 1",
"Specific actionable issue 2",
"Specific actionable issue 3"
]
}
`.trim();
}// /prompts/analyze.ts
export function buildAnalyzePrompt(input: AnalysisPromptInput): string {
return `
You are a senior engineer analyzing a GitHub repository to extract structured technical context for documentation generation.
Repository: ${input.repoName}
File tree (first 100 paths):
${input.fileTree.slice(0, 100).join('\n')}
Key file contents:
=== package.json / manifest ===
${input.manifest || 'not found'}
=== .env.example ===
${input.envExample || 'not found'}
=== Entry point ===
${input.entryPoint ? input.entryPoint.slice(0, 500) : 'not found'}
=== Dockerfile ===
${input.dockerfile || 'not found'}
=== CI config ===
${input.ciConfig || 'not found'}
Detected file extensions: ${input.detectedExtensions.join(', ')}
Rules:
- Use ONLY information visible above
- For missing data use null — never guess
- Detect API routes only if route files were provided above
Return ONLY valid JSON. No explanation. No markdown fences. No backticks.
{
"project_name": "exact repo name",
"description": "1–2 sentence technical description based strictly on code above",
"tech_stack": ["TypeScript", "Next.js", "PostgreSQL"],
"framework": "Next.js or null",
"language": "TypeScript",
"package_manager": "npm",
"scripts": {
"install": "npm install or null",
"dev": "npm run dev or null",
"build": "npm run build or null",
"test": "npm test or null",
"start": "npm start or null"
},
"env_variables": ["DATABASE_URL", "NEXTAUTH_SECRET"],
"key_dependencies": ["next", "react", "prisma"],
"has_docker": true,
"has_ci": true,
"has_tests": true,
"entry_point": "src/index.ts",
"api_routes": ["GET /api/users", "POST /api/auth/login"],
"project_type": "web-app",
"notable_features": ["JWT authentication", "REST API"],
"prerequisites": ["Node.js >= 18", "PostgreSQL"]
}
`.trim();
}// /prompts/generate.ts
export function buildGeneratePrompt(analysis: RepoAnalysis): string {
return `
You are a world-class technical writer. Generate a professional README.md.
Use ONLY the data provided. Do NOT invent features, commands, or dependencies.
Project Analysis:
${JSON.stringify(analysis, null, 2)}
STRICT RULES:
- Use ONLY real data from the analysis above
- If a field is null or empty array, OMIT that entire section
- Use EXACT command strings from scripts object
- List ONLY env_variables that appear in the analysis
- Write in second person ("Run..." not "You should run...")
- Every sentence must add information — zero filler
Section order (skip any section where data is unavailable):
1. # Project Name — one-line description
2. Badges (ONLY if has_ci=true OR has_docker=true)
3. ## About — 2–3 sentences max
4. ## Tech Stack — bullet list
5. ## Prerequisites — list from prerequisites field
6. ## Installation — numbered steps with real commands
7. ## Environment Variables — table: | Variable | Description |
8. ## Usage — exact command from scripts.start or scripts.dev
9. ## API Routes — table: | Method | Endpoint | Description | (ONLY if api_routes non-empty)
10. ## Running Tests — exact command (ONLY if has_tests=true)
11. ## Docker — build + run (ONLY if has_docker=true)
12. ## Contributing — standard 3-sentence section
13. ## License — MIT
Return ONLY raw markdown. No explanation. No backticks wrapping the output.
`.trim();
}// /prompts/drift.ts
export function buildDriftPrompt(input: DriftPromptInput): string {
return `
You are a documentation auditor. Compare previous and current repository states.
Identify ONLY changes that make the existing README inaccurate or misleading.
Repository: ${input.repoName}
Days since last analysis: ${input.daysSinceSnapshot}
PREVIOUS ANALYSIS:
${JSON.stringify(input.snapshot, null, 2)}
CURRENT ANALYSIS:
${JSON.stringify(input.current, null, 2)}
CURRENT README (first 3000 chars):
---
${input.currentReadme.slice(0, 3000)}
---
Focus ONLY on documentation-breaking changes:
- New/removed dependencies affecting installation
- New/changed/removed env variables
- Changed scripts (install, run, build commands changed)
- New API routes not documented
- Tech stack changes
- Features removed but still documented
Ignore: cosmetic changes, non-breaking additions, internal refactors not visible in docs
Drift score: 100=perfect sync, 0=completely stale
Status: in-sync=90–100, minor-drift=70–89, moderate-drift=40–69, major-drift=0–39
Return ONLY valid JSON. No explanation. No markdown fences. No backticks.
{
"drift_score": 85,
"status": "minor-drift",
"summary": "One sentence verdict",
"changed_items": [
{
"category": "dependency",
"change": "prisma added as new dependency",
"readme_impact": "Installation section missing prisma generate step",
"severity": "high"
}
],
"sections_to_update": ["Installation", "Environment Variables"],
"recommendation": "Short specific action"
}
`.trim();
}-
Task 0.1: Initialize project
npx create-next-app@latest repodoc --typescript --tailwind --app cd repodocVerify:
npm run dev→ localhost:3000 loads -
Task 0.2: Install dependencies
npm install next-auth @auth/core octokit npm install @google/generative-ai npm install @supabase/supabase-js npm install react-markdown remark-gfm recharts npm install lucide-react clsx tailwind-merge npx shadcn-ui@latest init npx shadcn-ui@latest add button card badge input textarea dialog toast
Verify:
npm run devwith no errors -
Task 0.3: Supabase setup
- Create project at supabase.com
- SQL Editor → run
schema.sql - Copy Project URL + anon key + service role key Verify: All 5 tables visible in Table Editor
-
Task 0.4: GitHub OAuth App
- GitHub → Settings → Developer Settings → OAuth Apps → New
- Callback:
http://localhost:3000/api/auth/callback/github - Scopes to request:
read:user user:email repoVerify: Client ID + Secret available
-
Task 0.5: Configure
.env.localNEXTAUTH_SECRET= # openssl rand -base64 32 NEXTAUTH_URL=http://localhost:3000 GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GEMINI_API_KEY= NEXT_PUBLIC_SUPABASE_URL= NEXT_PUBLIC_SUPABASE_ANON_KEY= SUPABASE_SERVICE_ROLE_KEY=
-
Task 0.6: Configure NextAuth
- Create
/app/api/auth/[...nextauth]/route.ts - GitHub provider,
reposcope signIncallback: upsert user + save access token to Supabase Verify: Sign in → session exists → user row in Supabase
- Create
-
Task 0.7: Create middleware
- Protect
/dashboardand/reporoutes Verify:/dashboardwithout auth → redirects to signin
- Protect
-
Task 0.8: Create all types
- Create
/types/index.ts— all interfaces from Appendix Verify: TypeScript compiles cleanly
- Create
-
Task 0.9: Create Supabase clients
/lib/supabase.ts— server client (service role) + browser client (anon) Verify: Test query from API route returns data
-
Task 0.10: Create DB query functions
/db/queries.ts—upsertUser,upsertRepo,saveAnalysis,getLatestAnalysis,saveReadme,getLatestReadme,updateReadmePR,saveDriftLog,getDriftHistoryVerify: Each function has correct TypeScript signature
-
Task 1.1: Create Gemini client
/lib/gemini.ts- 10 second timeout on all calls
parseJSON<T>(text: string): Thelper that strips backticks before parsing- Export
generateText(prompt: string): Promise<string>Verify: Test call returns text from Gemini
-
Task 1.2: Create rate limiter
/utils/rate-limit.ts- In-memory
Map<ip, timestamp[]> checkRateLimit(ip, max, windowMs): booleanVerify: Returns false after max calls in window
-
Task 1.3: Create roast API route
/app/api/roast/route.ts- Rate limit → parse URL → fetch README → Gemini → return JSON Verify:
curl -X POST localhost:3000/api/roast \ -H "Content-Type: application/json" \ -d '{"repoUrl":"https://github.com/facebook/react"}'
Returns valid
RoastResultJSON -
Task 1.4: Build RoastInput component
- URL input + validation + loading state + error state Verify: Validates URL, calls API, shows spinner
-
Task 1.5: Build RoastResult component
- Grade (large, color-coded) + score + summary + criteria + issues
- Share button + "Fix this →" CTA Verify: Renders all states with mock data
-
Task 1.6: Build landing page
/app/page.tsx: hero + RoastInput + 3-step explainer Verify: Full roast flow end-to-end in browser
-
Task 2.1: Create GitHub client
/lib/github.tsgetOctokit(token)factorygetUserRepos(token)→GitHubRepo[]getRepoTree(token, owner, repo)→string[]getFileContent(token, owner, repo, path)→string | nullgetDefaultBranch(token, owner, repo)→stringVerify:getUserReposreturns real repos in console
-
Task 2.2: Create analyzer logic
/lib/analyzer.tsanalyzeRepo(token, owner, repo): Promise<RepoAnalysis>- Fetch tree → identify key files → fetch content → build prompt → call Gemini → parse
Verify: Returns full
RepoAnalysisfor a test repo
-
Task 2.3: Create analyze API route
- Auth guard → upsert repo → run analysis → save to DB → return
Verify: Returns analysis JSON + new row in
analysestable
- Auth guard → upsert repo → run analysis → save to DB → return
Verify: Returns analysis JSON + new row in
-
Task 2.4: Create repos API route
/app/api/repos/route.ts- Fetch GitHub repos + enrich with
last_analyzed_atfrom DB Verify: Returns enriched repo list
-
Task 2.5: Build AnalysisProgress component
- 5 named steps with check animations
- Show "Reading N files..." Verify: Steps animate during a real analysis run
-
Task 2.6: Build RepoCard component
- Name + visibility badge + last analyzed date + drift score badge Verify: Renders correctly with mock data
-
Task 2.7: Build dashboard page
/app/dashboard/page.tsx- Fetch repos → grid of RepoCards
- Click card → navigate to
/repo/{owner}/{repo}Verify: Real repos from GitHub shown after login
-
Task 3.1: Create generator logic
/lib/generator.tsgenerateReadme(analysis: RepoAnalysis): Promise<string>- Strip backtick wrappers from Gemini output Verify: Returns clean markdown string
-
Task 3.2: Create generate API route
- Auth guard → fetch analysis from DB → generate → increment version → save readme → return
Verify: Returns markdown + new row in
readmestable with correct version
- Auth guard → fetch analysis from DB → generate → increment version → save readme → return
Verify: Returns markdown + new row in
-
Task 3.3: Build ReadmeEditor component
- Split pane:
<textarea>left +<ReactMarkdown>right - Sync on change (debounced 300ms)
- Copy | Download | Regenerate buttons
- Version badge + switcher Verify: Edit left → preview updates right. Copy/download work.
- Split pane:
-
Task 3.4: Build repo workspace page
/app/repo/[owner]/[repo]/page.tsx- Auto-trigger analysis if never analyzed
- Auto-trigger generation after analysis
- Compose: AnalysisProgress + ReadmeEditor + PrCreator + DriftPanel Verify: Full flow: open repo page → auto-analyze → auto-generate → see README
-
Task 4.1: Add GitHub write operations to client
- Add to
/lib/github.ts: getBranchSHA(token, owner, repo, branch)→ SHAcreateBranch(token, owner, repo, branchName, sha)→ voidgetFileSHA(token, owner, repo, path)→ SHA | nullcreateOrUpdateFile(token, owner, repo, path, content, message, branch, sha?)→ voidcreatePR(token, owner, repo, title, body, head, base)→{ url, number }Verify: Each function works in isolation with test calls
- Add to
-
Task 4.2: Create PR API route
/app/api/pr/route.ts- Chain all 5 GitHub operations in sequence
- Handle branch name conflicts (append timestamp)
- Save PR URL + number to DB Verify: Creates real PR on a test private repo
-
Task 4.3: Build PrCreator component
- "Raise PR 🚀" trigger button
- Modal: branch name + PR title + PR description (all editable)
- Step indicators: "Creating branch... Uploading... Opening PR..."
- Success: PR URL link
- Error: exact GitHub error message Verify: Full PR creation flow works end-to-end
-
Task 5.1: Create drift logic
/lib/drift.tsdetectDrift(snapshot, current, readme): Promise<DriftReport>- Pre-compute structured diff before sending to Gemini (reduces prompt noise)
Verify: Returns valid
DriftReportwith real changes detected
-
Task 5.2: Create drift API route
- Fetch latest analysis → re-run analysis → detect drift → save log → return
Verify: Returns drift report + row in
drift_logs
- Fetch latest analysis → re-run analysis → detect drift → save log → return
Verify: Returns drift report + row in
-
Task 5.3: Create drift history API route
- Query all drift logs for repo ordered by date ASC
Verify: Returns array of
{ date, score, status }
- Query all drift logs for repo ordered by date ASC
Verify: Returns array of
-
Task 5.4: Build DriftPanel component
- Score + status + summary + changed items + CTA Verify: All states render correctly
-
Task 5.5: Build DriftChart component
rechartsLineChart with reference lines + tooltip- Empty state Verify: Chart renders with mock time-series data
-
Task 5.6: Build drift history page
/app/repo/[owner]/[repo]/history/page.tsx- Chart + data table below Verify: Shows real historical data after 2+ drift checks
- Task 6.1: Wrap all async UI in
<Suspense>+ error boundaries - Task 6.2: Skeleton loaders for all data-fetching components
- Task 6.3: Responsive layout (mobile → desktop breakpoints)
- Task 6.4: Build Navbar (logo + auth state + sign out)
- Task 6.5: Add
metadatato allpage.tsxfiles (title, description, OG) - Task 6.6: Deploy to Vercel (
vercel --prod) - Task 6.7: Add all env vars to Vercel dashboard
- Task 6.8: Update GitHub OAuth callback URL to production domain
- Task 6.9: Add production domain to Supabase allowed CORS origins
- Task 6.10: Run full flow on production URL end-to-end
| Scenario | Handler |
|---|---|
| 404 — README not found (roast) | Return grade F: "No README.md found" |
| 403 — Unauthenticated rate limit | "GitHub rate limit reached. Try again shortly." |
| 401 — Token expired | Clear session, return 401, redirect to signin |
| 422 — Branch already exists (PR) | Append -{Date.now()} to branch name, retry once |
| 403 — Insufficient token scope (PR) | "Reconnect GitHub with write permissions to create PRs." |
| Scenario | Handler |
|---|---|
| Timeout (> 10s) | Return 504: "AI analysis timed out. Please try again." |
| Invalid JSON | Strip backticks → retry JSON.parse → if still fails, return 500 |
| Empty response | Retry once → if still empty, return 500 |
| Token limit exceeded | Truncate files to 200 lines, tree to 50 paths, retry |
| Scenario | Handler |
|---|---|
| Connection error | Log server-side, return 500: "Service temporarily unavailable." |
| Insert conflict | Use UPSERT / ON CONFLICT DO UPDATE for all inserts |
| Query returns null | Return empty state to UI — never throw on missing data |
| Feature | Notes |
|---|---|
| Roast My README (public) | No auth required |
| GitHub OAuth + user persistence | Supabase users table |
| Repo Analyzer | Max 20 files, structured output |
| Smart README Generator | Versioned, editable, download |
| Auto PR Creation | Real branch + PR via GitHub API |
| Doc Drift Detection | Manual trigger, saved to DB |
| Drift History Chart | recharts line chart |
| Production deployment | Vercel + Supabase |
| Feature | Why later |
|---|---|
| GitHub App (webhooks) | Requires GitHub App review + org installation flow |
| Email drift alerts | Needs email service + user preferences + unsubscribe |
| Multi-doc generation | Perfect paid upgrade. Build after core retention is proven. |
| Stripe / payments | No users yet. Build after 30-day retention data exists. |
| Team / org accounts | Requires multi-tenancy in DB schema |
| CLI tool | Build after web product proves value |
| VS Code extension | Lower distribution ROI than GitHub App |
| AI model fine-tuning | Requires accumulated usage data you don't have yet |
// /types/index.ts
export interface RoastResult {
grade: 'A' | 'B' | 'C' | 'D' | 'F';
score: number;
summary: string;
criteria: {
clarity: { score: number; issue: string | null };
setup: { score: number; issue: string | null };
usage: { score: number; issue: string | null };
structure: { score: number; issue: string | null };
completeness: { score: number; issue: string | null };
specificity: { score: number; issue: string | null };
};
top_issues: string[];
}
export interface RepoAnalysis {
project_name: string;
description: string;
tech_stack: string[];
framework: string | null;
language: string;
package_manager: string;
scripts: {
install: string | null;
dev: string | null;
build: string | null;
test: string | null;
start: string | null;
};
env_variables: string[];
key_dependencies: string[];
has_docker: boolean;
has_ci: boolean;
has_tests: boolean;
entry_point: string;
api_routes: string[];
project_type: 'web-app' | 'cli' | 'library' | 'api' | 'mobile' | 'other';
notable_features: string[];
prerequisites: string[];
}
export interface DriftReport {
drift_score: number;
status: 'in-sync' | 'minor-drift' | 'moderate-drift' | 'major-drift';
summary: string;
changed_items: Array<{
category: 'dependency' | 'env-var' | 'script' | 'api-route' | 'feature' | 'stack';
change: string;
readme_impact: string;
severity: 'low' | 'medium' | 'high';
}>;
sections_to_update: string[];
recommendation: string;
}
export interface GitHubRepo {
id: number;
full_name: string;
owner: string;
name: string;
is_private: boolean;
default_branch: string;
description: string | null;
updated_at: string;
}
export interface DriftHistoryPoint {
date: string;
score: number;
status: DriftReport['status'];
}
export interface AnalysisPromptInput {
repoName: string;
fileTree: string[];
manifest: string | null;
envExample: string | null;
entryPoint: string | null;
dockerfile: string | null;
ciConfig: string | null;
detectedExtensions: string[];
}
export interface DriftPromptInput {
repoName: string;
snapshot: RepoAnalysis;
current: RepoAnalysis;
currentReadme: string;
daysSinceSnapshot: number;
}- All
process.env.*vars present and typed - No hardcoded secrets anywhere in codebase
-
npm run buildpasses with zero errors - All API routes handle auth failure (no 500s on missing session)
-
vercel --prod - Add all env vars in Vercel dashboard (Settings → Environment Variables)
- Set
NEXTAUTH_URLto production domain - Enable preview deployments
- Add production domain to allowed CORS origins (Settings → API)
- Confirm all 5 tables have RLS enabled
- Enable automatic daily backups
- Update Homepage URL to production domain
- Update callback:
https://yourdomain.vercel.app/api/auth/callback/github
- Roast tool works (no auth, public repo URL)
- GitHub OAuth sign-in creates user in Supabase
- Repo analysis runs and saves to DB
- README generation returns correct output
- PR creation raises real PR on test repo
- Drift check runs and saves to
drift_logs - History chart renders with real data points
PRD Version 2.0 — RepoDoc — Resume/Portfolio Quality Features: Roast + Analyzer + Generator + Auto PR + Drift Detection + History Chart Stack: Next.js 14 + TypeScript + Tailwind + Supabase + Gemini + GitHub OAuth