CV Builder
A resume has two readers and both can reject it before a human ever considers the candidate:
- The parser. An applicant tracking system (ATS) converts the file into database fields. Anything it cannot read — a two-column layout, a name inside a text box, dates inside a table — becomes a blank field or garbled text. A blank "Company" field is often an automatic filter-out.
- The skimmer. A recruiter spends a handful of seconds deciding whether to read properly. They are usually not an engineer. "Contributed to an open source project" means nothing to them; "maintainer of Docusaurus, the static site generator behind React Native and Jest's docs" means a great deal.
Every rule in this skill exists to serve one of those two readers. When a judgement call comes up that the rules do not cover, resolve it by asking which reader is being served and what would help them.
Source material: the Tech Interview Handbook resume guide
(https://www.techinterviewhandbook.org/resume/). The distilled ruleset lives in
references/handbook-rules.md.
Pick a mode
| The user is asking for | Mode | Start at |
|---|---|---|
| A resume from scratch, or from notes / a LinkedIn export / an old messy doc | Build | Mode A |
| Feedback, a critique, a score, "is this good enough for FAANG?" | Review | Mode B |
| A version aimed at a specific job posting or company | Tailor | Mode C |
These overlap constantly. A review usually ends with the user wanting the fixed version, and tailoring assumes a solid base resume exists. Chain modes freely — just do not silently rebuild a resume when the user only asked what was wrong with it. Offer, then act.
The pipeline every mode ends with
Regardless of mode, a delivered resume is produced the same way. Do not hand-roll docx generation — the bundled script encodes the format rules so they cannot drift.
- Write the resume content to a JSON file (schema below).
python3 scripts/build_cv.py content.json --out resume.docx --pdfpython3 scripts/check_ats.py resume.docx --pdf resume.pdfand fix anything it flags.- Deliver both files, plus a short note on what you changed and why.
build_cv.py produces a linear, single-column, table-free document with 0.5"
margins, a whitelisted font, and right-aligned dates achieved with tab stops
rather than tables. check_ats.py re-extracts the text and verifies the parser's
view of the document — this is the automated version of the handbook's "paste it
into Notepad and see what survives" test.
If check_ats.py reports the document runs to two pages, that is a content
problem, not a formatting one. Cut the weakest bullets rather than shrinking the
font below 10pt or the margins below 0.5". Fewer strong achievements beat more
average ones, and a recruiter who sees six bullets reads all six.
Format rules that are never negotiable
Full detail and rationale in references/handbook-rules.md. The short version,
because these get violated most often:
- One page. Even for senior candidates.
- Fonts: Arial, Calibri, or Garamond, 10pt minimum.
- Margins: 0.5 inches.
- No tables, columns, text boxes, images, icons, logos, charts, or headers/footers. Contact details go in the body, never in a header — Word headers are frequently invisible to parsers.
- Standard section names, in this order: Professional Summary → Contact Information → Skills → Work Experience → Education → Projects → Awards and Certifications. Clever names ("Where I've Made Impact") break field mapping. Order the middle sections by what is strongest: students put Education above Work Experience.
- Dates as MM/YYYY.
03/2023 - Present. - Deliver PDF for submission, .docx as the editable master. Never a scanned or image-based PDF.
Writing the content
This is where resumes are actually won or lost, and it is the part a model is
most tempted to do lazily. Read references/bullet-writing.md before writing or
rewriting any experience or project bullets — it has the formula, the failure
modes, and worked before/after rewrites.
The formula, in brief:
[Accomplishment]: [what you did] resulting in [quantified outcome]
Three things make a bullet work: it leads with the result rather than the task, it names the specific technology (that is what the keyword matcher indexes), and it carries a number. A bullet with no number is a claim; a bullet with a number is evidence.
When the user has no numbers, do not quietly drop the metric and do not invent one. Help them reconstruct it: how many users did the service have, how long did the manual process take before, how many engineers were unblocked, what percentage of the test suite was flaky. Most people have the numbers and have never been asked for them. If a number genuinely cannot be recovered, reach for scale or scope instead ("across 12 microservices", "for a 40-person engineering org") rather than leaving the bullet bare.
Fabrication is the one unrecoverable error here. The user will be asked about every line of this document in an interview. Never invent employers, dates, titles, technologies, or metrics. Rephrasing what the user told you is the job; adding facts they did not give you is not. If a section is thin, say so and ask for more material.
Mode A — Build
Gather first. Read whatever the user provided — an old resume, a LinkedIn PDF, a job history in chat — before asking anything. Then ask only for what is genuinely missing.
Ask in batches, not one question at a time. A single message covering "for each role: what did you own, roughly how big was the system/team, and what changed because you were there" gets far more usable material than ten separate questions, and respects that the user is probably mid-job-hunt and tired.
What you need per role: company, location, title, MM/YYYY dates, and two to four accomplishments with scale attached. What you need overall: contact details, target role, years of experience, education, and two or more projects if they are early-career.
If the user is unavailable (an unattended or scheduled run, or they uploaded
a file and said "just do it"), do not block. Build the strongest resume the
available material supports, mark any place you had to guess with a clearly
visible [TODO: ...] placeholder in the document, and list those gaps in your
reply so they are fixed before submission. A resume with three honest
placeholders beats a resume with three invented metrics.
Section-level guidance — the specifics for each section (summary length,
skills grouping, GPA thresholds, how much education detail by career stage) are
in references/handbook-rules.md. Read it rather than guessing; the thresholds
are specific and easy to get subtly wrong.
Mode B — Review
Reviews are only useful when they are specific. "Strengthen your bullet points" helps nobody. Every critique must point at a line and show the fix.
- Extract the resume text.
python3 scripts/check_ats.py <file>works on both .docx and .pdf and reports what a parser actually sees — run it first, because a resume that parses badly has a problem that outranks any wording issue. - Score it against the rubric in
references/review-rubric.md. - Write the report using this structure:
## Verdict
[2-3 sentences: would this pass a FAANG resume screen, and what is the single
biggest thing standing in the way]
## Parser check
[What check_ats.py found — page count, extractability, missing sections]
## Scorecard
[The rubric table with scores and one-line justifications]
## Line-by-line rewrites
[For each weak bullet: the original, the rewrite, and one sentence on what
changed. Cover the weakest 5-10 lines, not all of them.]
## Cut these
[Content that costs space without earning it, with the reason]
## What's missing
[Sections, metrics, or context the resume needs and does not have]
Be direct about weaknesses — a review that flatters costs the user interviews. But critique the document, not the person, and note what is genuinely working: they need to know what not to break.
Then offer to rebuild it: "Want me to produce the corrected version as an ATS-safe .docx and PDF?" If they say yes, run the standard pipeline.
Mode C — Tailor
Tailoring is keyword alignment plus reprioritization, not exaggeration. The resume must remain true after tailoring.
- Get the target. One job description is enough to tailor to a specific posting. For a role type in general, gather 3-5 postings — the terms that recur across all of them are the ones the screen is built on, and any single posting contains noise.
- Extract keywords.
python3 scripts/extract_keywords.py jd1.txt jd2.txt ...ranks terms by frequency and cross-posting coverage, and if you pass--resume resume.docxit reports which required terms the resume is missing. - Match their exact wording. A keyword matcher looking for "Amazon Web
Services" may not match "AWS". Where the posting uses one form and the
industry uses another, include both once: "Amazon Web Services (AWS)". Do
this for the handful of terms that matter, not every acronym. If the
posting's platform is identifiable (see
references/ats-platforms-2026.md), lean harder into exact phrasing for Taleo/iCIMS-style literal matchers. - Place keywords where they are believable. A term earns more credit inside a Work Experience bullet describing real use than in a skills list, and more credit in the Summary or most recent role than in an old one — semantic screens weight keywords by location, not just presence. Move the most relevant roles' bullets to the top of their role, reorder skill categories to lead with what the posting asks for, and promote the most relevant project.
- Check density, not just coverage.
check_ats.py --jd <posting>reports both: coverage (are the required terms present at all) and density (what share of the resume's own text they make up). Target the 20-35% density band — below it under-sells real experience, above it reads as stuffing. Seereferences/ats-platforms-2026.mdfor the full rationale. - Never claim unused technology. If the posting wants Kubernetes and the user has never touched it, it does not go on the resume. Tell the user it is a gap. Getting screened in on a false keyword only relocates the rejection to a more expensive stage.
Deliver the tailored resume as a separate file (resume-<company>.docx), keep
the master intact, and summarise which terms you added and where.
Content JSON schema
build_cv.py consumes this. Every field is optional except contact.name;
omitted sections are simply not rendered.
{
"headline": "Senior Software Engineer | Distributed Systems",
"summary": "Backend engineer with 6 years building high-throughput payment infrastructure. Led the migration that cut settlement latency 60% for 4M daily transactions.",
"contact": {
"name": "Jane Doe",
"phone": "+1 415 555 0142",
"email": "jane.doe@email.com",
"location": "San Francisco, CA",
"linkedin": "linkedin.com/in/janedoe",
"github": "github.com/janedoe",
"website": "janedoe.dev"
},
"skills": [
{"category": "Languages", "items": ["Go", "Python", "TypeScript", "SQL"]},
{"category": "Infrastructure", "items": ["Amazon Web Services (AWS)", "Kubernetes", "Terraform"]}
],
"experience": [
{
"company": "Stripe",
"location": "San Francisco, CA",
"title": "Senior Software Engineer",
"start": "03/2022",
"end": "Present",
"context": "Payments infrastructure company processing $1T+ annually — include for employers the target reader won't recognise, omit for household names",
"bullets": [
"Cut payment settlement latency 60%: redesigned the ledger write path around an append-only event log, sustaining 4M transactions/day at p99 under 40ms."
]
}
],
"education": [
{
"school": "University of Colombo",
"location": "Colombo, Sri Lanka",
"degree": "BSc (Hons) in Computer Science",
"start": "09/2015",
"end": "06/2019",
"details": ["GPA: 3.8/4.0", "President, Computer Science Society (120 members)"]
}
],
"projects": [
{
"name": "Docusaurus",
"link": "github.com/facebook/docusaurus",
"context": "Meta's open-source static site generator, used by React Native and Jest",
"bullets": [
"Core maintainer of v2: shipped the plugin architecture now used by 7,600+ public repositories."
]
}
],
"awards": [
"AWS Certified Solutions Architect - Professional (2024)",
"1st place, IEEEXtreme 24-hour programming competition (2019), 3,200 teams"
],
"section_order": ["summary", "contact", "skills", "experience", "education", "projects", "awards"],
"font": "Calibri"
}
section_order overrides the default order — use it to lift education above
experience for students and new graduates. font accepts Arial, Calibri, or
Garamond; anything else is rejected by the builder.
Bundled scripts
Run them from the skill directory, or pass absolute paths.
| Script | Purpose |
|---|---|
scripts/build_cv.py |
content JSON → ATS-safe .docx (and PDF with --pdf). Rejects non-whitelisted fonts and refuses to emit tables or headers. |
scripts/check_ats.py |
Validates a .docx or .pdf: page count, font sizes, tables/images/headers, text extractability, section detection, contact-field parseability. Use --jd to also report keyword coverage. |
scripts/extract_keywords.py |
Frequency + coverage ranking across job descriptions; --resume reports gaps. |
Each takes --help. If a script fails, read the error rather than falling back
to hand-built docx — the constraints it enforces are the point of the skill.
Reference files
references/handbook-rules.md— the complete format and section ruleset, with the reasoning behind each rule. Read when building or reviewing any section.references/bullet-writing.md— the accomplishment formula, quantification patterns, action verbs, and worked before/after rewrites. Read before writing any bullet.references/review-rubric.md— the scored rubric for Mode B.references/ats-platforms-2026.md— platform-specific ATS behavior (Workday, Taleo, Greenhouse, Lever, iCIMS), the keyword density target, and how semantic screening weights keyword location. Read during Mode C, and check its validation checklist before delivering a tailored resume.assets/example_content.json— a complete filled-in content file to work from.