From 947a4c3d56089c156cf7b6df32fe7d0ac70af314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Ari=C3=B1o=20de=20la=20Rubia?= Date: Sat, 2 May 2026 19:37:51 +0000 Subject: [PATCH] v0.4.7 fix: teach the tailor how to write a strong resume bullet (#65) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TAILOR_PROMPT had heavy guardrails (anchoring, no fabrication, no identity changes) but never taught the craft of writing a strong bullet. Cautious models read the silence on craft as "stay close to source" and produced under-edited output — Sonnet 4.6 leaked 3 forbidden openings ("Performed", "Assisted with", "participated in") through 13 bullets on a deliberately-weak fixture. Reported by @guiqvlaixi2164-max in #65: their tailored resume came out 3 pages because resumasher "didn't summarize them." Adds three new craft sections to scripts/prompts.py, sourced from a literature review across Harvard / MIT / Stanford / Bock / Indeed / Jobscan / Teal / Notch: - Bullet craft: [verb][scope][outcome][method] formula, 7 hard rules, 12-item forbidden-openings list, "do not invent percentages — use scope/frequency/selectivity/recognition substitutes", 7 curated weak->strong examples spanning SWE/ops/HR/marketing/leadership/finance - Summary craft: trailer-shape (identity + 1-2 evidence + optional ask), FORBIDDEN list covering generic adjectives, first-person, meta-claims ("directly matching X's mandate", "perfect fit for") - Skills section craft: explicit "skills are claims about the candidate" rule with WRONG/RIGHT example showing why a JD's BigQuery requirement doesn't justify adding BigQuery to skills when the source has only Snowflake Empirical validation: six iterative claude -p runs across Sonnet 4.6 and Haiku 4.5. Final results vs OLD prompt + Sonnet: Forbidden-opening violations: 3 -> 0 (even on weaker Haiku) Fabricated metrics: 0 -> 0 (preserved) Keyword-stuffing ("demand-planning" added to a bullet): YES -> NO Meta-summary statements: YES -> NO Skills-section fabrication ("BigQuery" added): N/A -> NO First-person pronouns: 0 -> 0 Concrete WRONG/RIGHT worked examples turned out to be load-bearing across all three iterations — the model literally pattern-matched onto the example text in subsequent runs, replacing the failing output with the prompt's stated correct version. No breaking changes; students on v0.4.6 upgrade in-place. No schema or variable changes, so existing test_prompts.py schema-literal preservation assertions still pass. 459 deterministic tests still green (5:32 sandbox run). Consciously deferred: typography-side compression (smaller font / tighter leading / page-overflow auto-shrink) — if real-world testing on long source resumes shows the prompt-side fix isn't enough, a renderer-side fallback is the natural follow-up. Also deferred: explicit per-role bullet count caps (literature suggests 3-5 for recent / 1-3 for older), since the existing Length and Recency section already covers overall length. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 22 +++++ VERSION | 2 +- scripts/prompts.py | 209 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc636bd..a9740fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to resumasher will be captured here. Format loosely follows `Unreleased` covers work in progress on `main`. Tagged versions will appear below it once we start cutting releases. +## [0.4.7] — 2026-05-02 + +v0.4.7 teaches the tailoring sub-agent how to actually write a strong resume bullet. + +The prompt up to this point told the LLM what *not* to do (don't fabricate, don't change identity, anchor every bullet to evidence) but never taught the *craft* of writing a strong bullet — no XYZ formula, no action-verb-first instruction, no "lead with outcome over activity," no weak→strong examples. With the loudest signals all pointing toward "stay honest" and nothing pulling the model toward "rewrite well," cautious models like Sonnet 4.6 produced lightly-edited source bullets rather than transformed ones. Reported by [@guiqvlaixi2164-max](https://github.com/guiqvlaixi2164-max) in [#65](https://github.com/earino/resumasher/issues/65): the tailored resume came out 3 pages long because resumasher "didn't summarize them" — the original verbose bullets passed through almost unchanged. + +Fixed by adding three new "craft" sections to `scripts/prompts.py`'s `TAILOR_PROMPT`, sourced from a literature review across Harvard / MIT / Stanford career services, Bock's XYZ formula, Indeed, Jobscan, Teal, and Notch: + +- **Bullet craft.** A composite formula (`[Strong past-tense verb] [scope] [outcome — quantified if honest, scoped/selectivity-substituted otherwise] by [method]`), 7 hard rules including a 12-item forbidden-openings list ("Responsible for", "Helped with", "Worked on", "Was involved in", "Tasked with", "Performed", "Participated in", "Engaged in", "Took part in", "Posted on", "Assisted with", "Duties included"), an explicit "do not invent percentages — substitute scope/frequency/selectivity/recognition" rule, and 7 curated weak→strong examples spanning SWE / ops / HR / marketing / leadership / finance. +- **Summary craft.** A trailer-shape teaching (identity sentence + 1-2 evidence sentences + optional ask) with a FORBIDDEN list covering generic adjectives ("passionate", "hardworking", "proven track record"), first-person pronouns, vague unsubstantiated claims, verbatim bullet repetition, every-skill-listed, AND meta-statements that claim the match instead of showing it ("directly matching X's mandate", "perfect fit for", "ideally suited for"). +- **Skills section craft.** An explicit "the skills list is a set of claims about the candidate" rule with a worked WRONG/RIGHT example showing why a JD's BigQuery requirement does not justify adding BigQuery to a candidate's skills list when their resume only lists Snowflake. + +Empirical validation via three iterative `claude -p` runs against a deliberately-weak fixture resume across Sonnet 4.6 and Haiku 4.5. The OLD prompt with Sonnet leaked 3 forbidden-opening violations through 13 bullets ("Performed", "Assisted with", "participated in"); the NEW prompt with Haiku produced 0 violations through 9 bullets. No fabricated metrics across any run. Two further rounds tightened the prompt against keyword-stuffing (model adding "demand-planning" to a bullet because the JD asked for it though the source resume said "merchandising") and meta-summary statements (model claiming alignment instead of showing it), and a third round closed the skills-section fabrication gap (model padding skills with BigQuery because the JD listed it though the candidate had only Snowflake). + +Consciously deferred: the typography-side of #65 (smaller font / tighter leading / page-overflow auto-shrink) — if real-world testing on long source resumes shows the new prompt-side fix isn't enough, a renderer-side fallback is the natural follow-up. Also deferred: an explicit "3-5 bullets per recent role, 1-3 for older roles" budget rule from the literature, since the existing Length and Recency section already covers overall length and per-role caps can be added if needed. + +No breaking changes; students on v0.4.6 upgrade in-place. Tailored resumes from the new prompt will read as more transformed and tighter than v0.4.6 outputs against the same source resume — no schema or filename changes, just better content. + +### Changed + +- **Tailoring prompt teaches resume bullet craft instead of just guarding against fabrication** ([#65](https://github.com/earino/resumasher/issues/65), reported by [@guiqvlaixi2164-max](https://github.com/guiqvlaixi2164-max)). The pre-existing `TAILOR_PROMPT` was heavy on guardrails (anchoring rule, no fabrication, no identity changes) and silent on craft — cautious models read this as "stay close to the source" and produced under-edited output. Adds three new sections to `scripts/prompts.py`: Bullet craft (composite `[verb][scope][outcome][method]` formula + 7 hard rules + 12-item forbidden-openings list + 7 weak→strong examples), Summary craft (trailer-shape teaching + FORBIDDEN list that includes meta-statements claiming alignment instead of showing it), and Skills section craft (explicit "skills are claims about the candidate" rule with WRONG/RIGHT example). Sourced from a literature review across Harvard / MIT / Stanford / Bock / Indeed / Jobscan / Teal / Notch. Empirically validated via six `claude -p` runs across Sonnet 4.6 and Haiku 4.5 against a deliberately-weak fixture: pre-fix Sonnet produced 3 forbidden-opening violations through 13 bullets; post-fix Haiku produced 0 violations through 9 bullets. Four failure modes uncovered and patched: under-editing (bullets passing through with weak verbs and "Responsible for" / "Helped with" openings), JD-vocabulary keyword-stuffing (model adding "demand-planning" because the JD asked for it though the source resume said "merchandising"), meta-summary claims ("directly matching X's mandate"), and skills-section fabrication (model adding BigQuery to skills because the JD asked for it though the candidate had only Snowflake). Each failure mode addressed with a concrete WRONG/RIGHT worked example in the prompt — pattern-matching teaches faster than rules. No schema or variable changes, so existing `test_prompts.py` schema-literal preservation assertions still pass. 459 deterministic tests still green. + ## [0.4.6] — 2026-05-02 v0.4.6 makes the cover letter look like a real cover letter. diff --git a/VERSION b/VERSION index ef52a64..f905682 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.6 +0.4.7 diff --git a/scripts/prompts.py b/scripts/prompts.py index 76c1675..ac5aa3d 100644 --- a/scripts/prompts.py +++ b/scripts/prompts.py @@ -283,6 +283,215 @@ **Do not invent experience, metrics, technologies, or project outcomes.** Do not change the candidate's name, email, phone, LinkedIn, or location. +## Bullet craft (how to write each bullet) + +The anchoring rule above governs WHAT goes in a bullet — only what the +source actually supports. This section governs HOW you write it. A bullet +that's anchored in real evidence but written weakly buries the candidate. +The dominant tailoring failure is not fabrication; it's under-editing — +lightly paraphrasing the source bullet instead of transforming it. + +**The bullet shape:** + + [Strong past-tense action verb] [specific scope or object], + [outcome — quantified if honest, scoped/selectivity-substituted otherwise], + by [method — tools, approach, or scale]. + +In plain English: lead with what the candidate DID, then what HAPPENED +because of it, then HOW they did it. Outcome before activity. The most +interesting fact goes at the front of the bullet — that's where the +recruiter's six-second scan will land. + +**Hard rules — apply to every bullet:** + +1. Start with a strong past-tense action verb (present tense only for + the candidate's current role). FORBIDDEN openings: "Responsible + for...", "Duties included...", "Helped with...", "Assisted with...", + "Worked on...", "Was involved in...", "Tasked with...", "Performed + ...", "Participated in...", "Engaged in...", "Took part in...". These + describe being present, not contributing. +2. No first-person pronouns ("I", "my", "we"). Subject is implied. +3. Active voice, never passive. "Improved the pipeline" — not "The + pipeline was improved." +4. One sentence, one accomplishment. No semicolons stitching two + ideas into a megabullet — split or pick the stronger one. +5. 1 line preferred, 2 lines hard cap. ~15-25 words target. +6. Specific over general. "Reduced query latency 40%" beats "Improved + performance." "Led a 6-person team across 3 product surfaces" beats + "Led a cross-functional team." +7. Mirror the JD's terminology for the noun-phrase ONLY when the + candidate's evidence genuinely uses that terminology or a clean + truthful equivalent. Do not parrot JD phrases that don't match the + candidate's real work — that's keyword-stuffing, not tailoring. + + INVALID (keyword-stuffing): + RESUME says: "daily SQL reporting on sales data for the + merchandising team." + JD says: "translate raw transactional data into demand-planning + insights." + WRONG: "Deliver daily SQL reports... supporting ongoing + demand-planning decisions." (Resume never says demand-planning; + adding it extrapolates beyond evidence to chase JD vocabulary.) + RIGHT: "Deliver daily SQL reports on retail sales data for the + merchandising team, supporting cross-departmental decisions." + + The test: can you point to the line in RESUME or EVIDENCE that + uses this term, or a near-synonym the candidate's actual work + genuinely earns? If not, the term doesn't belong in the bullet. + +**On numbers (read carefully — this is the most-failed rule):** + +Quantify when an honest metric is in the source. Numbers — money, people, +time, percentage, scale, frequency, rank — convert claims to evidence. +If the resume says a model lifted conversion 12%, the bullet says 12%. + +**Do NOT invent a percentage.** If the source says "improved the checkout +flow" with no number, do NOT write "improved the checkout flow by 30%." +The placeholder pattern (`[INSERT METRIC]`) governed by the section below +is the correct tool when the candidate has done a specific thing whose +only missing piece is the number — not when there's no metric to begin +with. + +When no honest metric exists, substitute scope, frequency, selectivity, +or recognition: + +- Scope: "across 3 sites and 450 employees", "for a 6-person team", + "supporting $14M in annual ad spend" +- Frequency: "shipped weekly", "ran 48 campaigns annually" +- Selectivity: "1 of 12 selected from a 200-person cohort" +- Recognition: "adopted by the data team for ongoing use", "presented + at the company all-hands" + +A bullet with honest scope is strictly better than one with a fabricated +percentage. A bullet with neither — pure activity description, no scope +or outcome — should usually be cut. + +**Weak -> strong examples.** The strong version always names a specific +verb, a specific scope, and a specific outcome (qualitative when no +honest metric is available). No claims are invented; the strong version +says only what the weak version implied. + + WEAK: Worked on the CI/CD pipeline. + STRONG: Implemented CI/CD pipeline using Jenkins and Docker, reducing + build times 60% and lifting deployment frequency 40%. + + WEAK: Responsible for vendor relationships. + STRONG: Renegotiated contracts with 3 office-supply vendors, + consolidating orders into one monthly shipment that simplified + tracking for a 60-person office. + + WEAK: Helped onboard new hires. + STRONG: Redesigned the onboarding flow for a 12-person data team, + eliminating recurring first-week questions and freeing 3+ + manager hours per new hire. + + WEAK: Managed email marketing campaigns. + STRONG: Designed and executed 48 email campaigns annually to a 75K- + subscriber list, achieving a 28% open rate (industry average + 21%) and driving $890K in attributed revenue. + + WEAK: Posted on social media regularly. + STRONG: Planned and published daily content across three channels, + growing Instagram following from 2,000 to 5,800 in 8 months. + + WEAK: Member of Leadership for Tomorrow Society. + STRONG: Selected as 1 of 275 participants nationwide for a 12-month + leadership-development program based on demonstrated + leadership potential. + + WEAK: Helped with cost-saving initiatives. + STRONG: Reduced procurement costs $500K by consolidating spend across + 12 vendors onto a single master contract. + +**Self-check before emitting each bullet:** + +1. Starts with a strong action verb (past tense, or present for current + role)? Verb is specific, not "managed/handled/worked on"? +2. Has a scope or outcome — even qualitative — or is this pure activity? +3. If there's a number, did it come from the source? (If you cannot + point to the RESUME/EVIDENCE line that contains it, delete it.) +4. One sentence, <=2 lines? No semicolons stitching two ideas? +5. Free of "Responsible for" and its cousins? + +If a bullet would fail any check, do not emit it. A shorter, sharper +resume is strictly better than a longer one with weak bullets. + +## Summary craft (the paragraph at the top) + +The summary is the candidate's trailer for THIS job — not a generic +"about me." Most weak summaries fail by being either (a) a list of +adjectives the bullets don't back up, or (b) a verbatim restatement +of the strongest bullet. + +**Length:** 2-4 sentences. If the candidate's identity and value are +already obvious from the H1 + first role's bullets, OMIT the summary +entirely. A missing summary is better than a generic one. + +**Shape:** + +1. Sentence 1: identity + level + domain. Example: "MS Business + Analytics candidate with 2 years of e-commerce data engineering + experience." +2. Sentence 2-3: the 1-2 strongest pieces of evidence from the resume + that map to the JD's top requirements. Concrete, with scope or + metric. Different angle than the top bullet — the summary frames, + the bullets prove. +3. Optional sentence 4: what the candidate is looking for, framed in + terms of the JD ("seeking to apply forecasting and SQL skills to + demand-planning roles"). + +**FORBIDDEN in the summary:** + +- Generic adjectives unsupported by evidence: "hardworking", "passionate", + "results-oriented", "team player", "detail-oriented", "go-getter", + "self-starter", "highly motivated", "proven track record" +- First-person pronouns +- Vague unsubstantiated claims ("excellent communication skills") +- Verbatim repetition of a bullet from below +- Listing every skill the candidate has — pick the 2-3 that match the JD +- Meta-statements that claim the match instead of showing it ("directly + matching X's mandate", "perfect fit for", "ideally suited for", + "uniquely positioned to"). Let the evidence prove alignment — that's + what the bullets do. The cover letter is where alignment is stated + explicitly; the resume's job is to show, not tell. + +A weak generic summary actively hurts the candidate by burning the most +valuable real estate on the page (the spot directly below the name) with +non-signal. A good summary or no summary at all. + +## Skills section craft + +The skills list is a set of CLAIMS about what the candidate knows. Every +line is a fact assertion: "this candidate has used Power BI." Apply the +same anchoring discipline as bullets — if a tool, language, framework, +or technology is not in RESUME or EVIDENCE, it does NOT belong in +skills, even if the JD asks for it. + +You MAY: +- Cut tools that aren't relevant to the JD (don't list 10 languages + when the role calls for 2) +- Reorganize categories to surface the JD-relevant tools first +- Rename categories to match the JD's vocabulary if the substance + is identical ("BI Tools" vs "Visualization Tools") + +You MUST NOT: +- Add a tool from the JD that the candidate's source does not list +- Substitute a JD tool for a similar source tool (source says + Snowflake; JD asks for BigQuery; do NOT add BigQuery — they are + not the same product even if they fill the same role) +- Pad the list with tools "everyone has" (Microsoft Office, etc.) + that the source didn't claim + +INVALID (skills-section fabrication): + RESUME says: "Snowflake, dbt, Airflow" + JD says: "experience with cloud warehouses (Snowflake, BigQuery, + Redshift)" + WRONG: "Pipeline & Warehouse: Snowflake, BigQuery, dbt, Airflow" + (BigQuery isn't in the source — the candidate hasn't used it.) + RIGHT: "Pipeline & Warehouse: Snowflake, dbt, Airflow" + (Honest claim. The fit-assessment phase already noted any gap; + the resume isn't the place to close it.) + **Length and recency.** Detailed entries should cover roughly the last 10-15 years. For candidates with a longer history, compress anything older into a single "Earlier roles" section at the end — one line per role, format