// CVForge · How it’s built

A promise that gets verified, not requested.

Almost every AI CV tool promises not to “make things up”. CVForge turns that into structure: every claim carries its source, every number is checked in code, and whatever can’t be proven is flagged in red before it leaves your machine. This page explains how, for anyone who wants to read the system as well as use it.

// Three nouns

Evidence, requirements, outcomes.

The whole product comes down to three things: evidence (what the person actually did, record by record, with metrics and dates), requirements (what a posting actually asks for, extracted by the model without seeing the evidence) and outcomes (what happened with each application). Every feature captures evidence, maps evidence to requirements, or learns from outcomes. The AI can select, order, condense and rephrase real evidence; it can never invent employers, dates, titles, figures or experience.

$ cvforge compose --verify

b6 · "Led digital acquisition of new merchants…"

cites → ev_06 (2018-08 – 2021-02) · figures: $1,450 → $890 MXN ✓ unit ✓ currency

✓ every claim traced to recorded evidence

b8 · "Wrote SQL queries (joins and aggregates)…"

⚠ distortion: the source says "simple SQL queries"; the bullet drops the qualifier

// How it’s checked

Five layers, four of them without a model.

01

Mandatory citations

Every CV bullet and every paragraph with facts lists the ids of the evidence it comes from. A bullet with no citation, or citing a record that wasn’t selected for that application, fails the check.

02

Numbers checked in code

Every figure in the document is looked up in the metrics of the cited records, with its unit and currency. "$1.2M USD" on a record that says "$1.2M MXN" fails. This isn’t asked of the model: it’s checked.

03

A gap can’t be claimed

If the analysis marked a requirement as uncovered, no CV bullet can claim it, not even by using the posting’s exact wording. It’s the fabrication that costs the most offers, and it’s blocked in code.

04

A single repair

If something fails, the model gets told exactly what and rewrites once. Whatever still fails is shown, not hidden or retried in a loop.

05

Exaggeration and tense

A final pass, with the sources annotated with their dates, checks that “led” didn’t come from “took part in” and that a finished job isn’t written in the present tense.

// Two stages you don’t see

The analysis is split on purpose.

Requirement extraction only sees the posting, never the evidence. The mapping receives the already-extracted requirements and the evidence projected in a stable way (the same byte string on every call, so the prompt cache bills it at a fraction). The verdict (“worth it”, “long shot”, “skip”) is arithmetic over the mapping, not a number the model makes up between 65 and 85. Location, time zone and work authorization are checked first, because no amount of rewriting fixes them.

When the evidence changes after an analysis, the system notices (a hash of the projection) and offers a one-click re-analysis; the extraction, which didn’t change, isn’t repeated or paid for again.

// Measure, don’t assume

Prompt evals and real costs.

A prompt change can’t be judged without a baseline: you can see the output changed, not that it improved. CVForge freezes real postings along with what the prompt extracted on the day it was judged good, and reruns the stages live to compare: which requirements dropped or appeared, what moved from required to nice-to-have, whether the verdict moved with the same evidence. The first measurement showed something useful: the same prompt on the same posting re-extracts with visible noise in the soft requirements, so the judged set is the hard, required ones, and every run is logged to measure the noise instead of assuming it.

Every model call logs its tokens and cost; the counter sits in the app’s top bar. Measured in real use: ≈ $0.13 USD per posting analysis; ≈ $0.50 USD per full kit (CV + letter + answers + message); $5–10 USD of API for a whole job search.

// What wasn’t built, on purpose

  • No scraping and no auto-apply: it prepares the materials; the person applies.
  • No predictive scores and no machine learning on outcomes: the numbers page is arithmetic over your own data, and it says “not enough data” below n=5.
  • Nothing in the cloud: no accounts, no telemetry, nobody’s data on any server. The only connection is to the Anthropic API with the user’s key.
  • The public demo is the same app with an in-memory database, a fictional person and results generated once; it calls no model and can’t write anything.

// Stack

Next.js 16 (App Router, server actions, streaming), React 19, strict TypeScript, SQLite in a local file with automatic migrations, the Anthropic SDK with schema-validated structured outputs (Zod) and prompt caching, Playwright only as the print engine for the PDF, Vitest tests (around 300, written first), Biome. An English and Spanish interface with a typed dictionary. All the code is open, under the MIT license: you can read it, run it and change it without asking anyone’s permission.