Skip to content

ThushanthaSanju/cvforge

Repository files navigation

cvforge

Structured CV review for technical roles, powered by Claude.

cvforge is a CV reviewer tuned for software, ML, and AI engineering roles. Paste your CV, pick the role you're targeting, and get a structured review back — not free-form prose. The output is the same shape every time: a score, ranked issues with severity, missing signals for the role, ATS keyword gaps, quantification opportunities, and a section-by-section verdict.

The technical bet: a generic "AI resume reviewer" gives bland advice because it serves everyone. A reviewer that knows what an ML engineer's CV should look like — production deployment, evaluation methodology, monitoring — produces dramatically better feedback than ChatGPT with a vague prompt.

Live demo

Add your Vercel URL here once deployed.

Screenshot

See /example for the rendered review shape (a full sample is baked into lib/sample-review.ts).

Tech stack

Layer Choice Why
Framework Next.js 14 (App Router) Server Actions + RSC give tight client/server boundaries for free
Language TypeScript strict (no any) Reviews flow through Zod schemas; types are the contract
UI Tailwind + lightweight UI primitives Owned source, dark by default, design tokens in CSS variables
Auth + DB Supabase (Postgres + Auth + RLS) RLS does authorization at the row level, not in app code
Storage Supabase Storage (Pro) Saved CVs and revision history live here, behind RLS
Billing Stripe Checkout + Customer Portal We don't custom-build account/billing flows
LLM Claude (Sonnet 4.5) via official SDK Tool use forces structured JSON output — no prompt-engineered prose
PDF pdfjs-dist Parse PDFs in the browser, only extracted text is sent to the server
Rate limit Upstash Redis + @upstash/ratelimit Free-tier IP/fingerprint limiting on the public endpoint
Observability Sentry + Vercel Analytics Error tracking + traffic, both first-party
Hosting Vercel + Supabase Cloud Closest match for the stack, zero-config previews

Architecture

              ┌────────────────────────┐
              │  Browser (Next.js RSC) │
              │  - PDF text extraction │
              │  - Supabase auth client│
              └─────────┬──────────────┘
                        │ POST /api/review
                        ▼
        ┌───────────────────────────────────────┐
        │  Next.js Route Handlers               │
        │  /api/review        → Claude tool use │
        │  /api/checkout      → Stripe          │
        │  /api/portal        → Stripe billing  │
        │  /api/webhooks/stripe (signed)        │
        │  /auth/callback     → Supabase OAuth  │
        └──────┬─────────────────────┬──────────┘
               │                     │
               ▼                     ▼
       ┌─────────────────┐    ┌────────────────┐
       │ Supabase        │    │ Anthropic API  │
       │ - Auth (email + │    │ - submit_review│
       │   Google OAuth) │    │   tool returns │
       │ - Postgres+RLS  │    │   strict JSON  │
       │ - Storage       │    └────────────────┘
       └─────────────────┘
               ▲
               │ webhook (signed, raw body)
       ┌───────┴─────────┐
       │ Stripe          │
       └─────────────────┘

The system prompt: the technical moat

This is the core design choice that makes cvforge different from a ChatGPT wrapper.

The Claude call is a tool-use call (lib/claude.ts). We define one tool, submit_review, whose input_schema is a strict JSON Schema mirroring the Zod schema in lib/schemas/review.ts. We force tool_choice to that tool, so Claude's only legal response is to call it exactly once. We never parse free-text output — and after the call we re-validate the tool input through Zod, so a model surprise still gets caught at the type boundary.

The system prompt is composed of three layers:

  1. Base prompt (lib/prompts/system.ts) — the reviewer persona, calibrated score rubric (90+ would forward to hiring manager, 0–39 not currently competitive), and hard rules ("severity must be justified by role context", "every issue must quote exact CV text", "rewrites must be drop-in replacements using only facts in the original quote", "never invent achievements").
  2. Role rubric (lib/prompts/roles/) — per-role signals to weight, things to penalize hard, common missing signals to surface, and ATS keywords. The ML engineer rubric checks for production deployment, evaluation methodology, monitoring. The frontend rubric checks for Core Web Vitals, accessibility, design-system contributions. Each rubric is short, specific, and editable in isolation.
  3. User context — the CV text, optional job description, target role.

This separation means we can iterate on one rubric without regressing others, and we snapshot-test prompt construction in tests/prompts.test.ts.

Why this matters

The output schema enforces:

  • Every issue includes a verbatim quote from the CV (so the user can find it).
  • Every issue includes a drop-in rewrite — the actual line they should paste back, not advice like "add metrics".
  • Severity is one of three values — critical/major/minor — so the UI can render and sort consistently.
  • Missing signals and keyword gaps are bounded so the model can't dump a laundry list.

A diff against ChatGPT-with-a-prompt looks like this:

ChatGPT free-form cvforge structured
"Try to add more metrics to your bullets." An exact quoted bullet + a rewritten version with metrics
"Your skills section could be improved." "Skills" verdict ≤15 words + the specific reorganization
Long mixed prose hard to act on UI components per field, sortable by severity
Hallucinated metrics Schema rule: rewrites use only facts in original quote

Local development

git clone https://github.com/<you>/cvforge.git
cd cvforge
cp .env.example .env.local   # fill in keys
npm install
npm run dev

You'll need:

  • Supabase project (free tier is fine). Apply the migrations in supabase/migrations/ via the Supabase CLI or paste them into the SQL editor in order (0001 → 0002 → 0003).
  • Anthropic API key. The default model is claude-sonnet-4-5-20250929.
  • Stripe account in test mode + a recurring Pro price configured.
  • Upstash Redis instance for rate limiting the anonymous endpoint.

For Google OAuth, configure the provider in Supabase Auth and set the redirect URI to ${SITE_URL}/auth/callback.

Deployment

  1. Push to GitHub and import into Vercel.
  2. Paste the env vars from .env.example into the Vercel project.
  3. Configure the Stripe webhook to point at https://<domain>/api/webhooks/stripe with signing secret in STRIPE_WEBHOOK_SECRET.
  4. Run the Supabase migrations against your production project.
  5. Set NEXT_PUBLIC_SITE_URL to the production URL so OG metadata, OAuth redirects, and Stripe success URLs resolve correctly.

Project layout

app/
├── page.tsx                      # landing + free anonymous review flow
├── example/page.tsx              # public sample review
├── pricing/page.tsx
├── privacy/page.tsx
├── terms/page.tsx
├── auth/login/                   # email/password + Google OAuth
├── auth/callback/route.ts
├── api/
│   ├── review/route.ts           # validated input → Claude → DB
│   ├── checkout/route.ts         # Stripe Checkout session
│   ├── portal/route.ts           # Stripe Customer Portal
│   └── webhooks/stripe/route.ts  # signed webhook handler
└── dashboard/
    ├── page.tsx                  # review history
    ├── billing/                  # plan + portal actions
    └── reviews/[id]/page.tsx
    └── reviews/compare/page.tsx  # Pro: side-by-side compare
components/                       # CvInput, ReviewOutput, ScoreRing, etc
lib/
├── claude.ts                     # tool-use Anthropic call
├── prompts/                      # base + per-role rubrics
├── schemas/                      # Zod schemas (the contract)
├── supabase/                     # SSR + browser + admin clients
├── stripe.ts
├── ratelimit.ts
└── billing.ts
supabase/migrations/              # schema + RLS + anon-purge cron
tests/                            # vitest

Roadmap

  • Supabase schema + RLS policies + anonymous-purge cron
  • Zod review schema (the contract)
  • System prompt + role rubrics + tool-use Claude call
  • Unauthenticated review flow on /
  • Auth (email + Google) + dashboard
  • Stripe billing + webhook + tier enforcement
  • PDF upload (client-side parse)
  • Revision compare view (Pro)
  • Landing page polish, pricing, privacy, terms, /example
  • CI (lint, typecheck, tests, build), Sentry, Vercel Analytics
  • Deploy to Vercel + production Supabase + live Stripe
  • PDF export of review reports (Pro)
  • Post-launch: prompt eval harness with golden CVs

License

MIT — see LICENSE.

About

Structured CV review for technical roles, powered by Claude. Next.js 14 + Supabase + Stripe.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors