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.
// CVForge · How it’s built
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
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
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.
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.
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.
If something fails, the model gets told exactly what and rewrites once. Whatever still fails is shown, not hidden or retried in a loop.
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
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
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
// 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.