From f51a766e7210e0fa6ece9ba84b82f63ef57fb79b Mon Sep 17 00:00:00 2001 From: Jovin Nicholas Date: Sun, 19 Jul 2026 13:22:46 -0400 Subject: [PATCH] feat(apply): factual grounding audit against the union fact base (#185) Reviewer-side Factual Grounding Audit: every date, employer, job title, and quantitative metric in both drafts is checked against the union of 01-candidate-profile.md + cv/main_example.tex + CLAUDE.md's Candidate Profile section (grounded if ANY source supports it; inter-source mismatches surfaced as profile-consistency warnings; draft drift returned as Part A edits with reason "grounding"). Drafter-side rule makes those three sources the sole source of truth for facts - existing tailored CVs are structure/phrasing reference only. Fixes the structural drift loop where tailored output fed back as source material; companion to the maintainer-side #178 setup fix. Reported, diagnosed, and implemented by @jovin-nicholas (#177); the failure mode was validated empirically by the output benchmark (blind judges found the exact escalation class in pre-audit outputs). Closes #177 --- .claude/commands/apply.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.claude/commands/apply.md b/.claude/commands/apply.md index 813949f..1956eb9 100644 --- a/.claude/commands/apply.md +++ b/.claude/commands/apply.md @@ -64,6 +64,8 @@ Also read the most recent existing CV and cover letter files for concrete struct - Read any existing `cv/main_*.tex` file as a LaTeX template reference - Read any existing `cover_letters/cover_*.tex` or `cover_letters/Cover_*.tex` file as a template reference +*The master candidate profile (`01-candidate-profile.md`), the master CV (`cv/main_example.tex`), and CLAUDE.md's Candidate Profile section are the sole source of truth for facts; existing tailored CVs may be read for structure and phrasing only, never as a source of claims.* + ### Requirement coverage (both documents) - **Every requirement the posting states gets addressed - matched or honestly gapped, never silently omitted.** A stated requirement the candidate lacks (a tool, a clearance, years of experience) is acknowledged with an honest bridge ("not in my daily toolkit yet; a natural extension of X"), because omission reads as hiding once an interviewer asks. Build the requirement list from Step 1 and check both drafts against it before Step 3. - **Engage nice-to-haves by name** where the profile supports honest adjacency (e.g. "conceptually aligned with "), and use the posting's own term over a synonym wherever it is truthfully applicable - including in CV section headings (a posting hiring for "MLOps" should find a heading containing "MLOps", not only a paraphrase). @@ -75,6 +77,7 @@ Also read the most recent existing CV and cover letter files for concrete struct - Tailor the profile statement and experience bullets to the specific role - Reframe skills and achievements to match job requirements - Keep to 2 pages +- **Grounding Audit:** Before writing to disk, audit all tailored bullet points against the union of three sources: `.claude/skills/job-application-assistant/01-candidate-profile.md` + the master CV (`cv/main_example.tex`) + `CLAUDE.md`'s Candidate Profile section to verify that all dates, roles, and metrics match exactly (zero profile drift or fabrication). ### Cover Letter (`cover_letters/cover__.tex`) - **Match the language of the job posting** (Danish posting -> Danish cover letter, English posting -> English cover letter) @@ -111,15 +114,20 @@ Use WebSearch and WebFetch to research, starting **only** from the company ident - Company culture and values ### 2. Read Reference Materials (content-critique only) -Read these four files — and only these — to ground your critique: +Read these reference files — and only these — to ground your critique: - `.claude/skills/job-application-assistant/01-candidate-profile.md` - `.claude/skills/job-application-assistant/02-behavioral-profile.md` — use this specifically to check whether the cover letter's voice matches the candidate's natural register. A "Collaborator" PI profile, for example, should not be given a combative, solo-hero tone; a "Persuader" profile should not be given over-hedged, apologetic phrasing. - `.claude/skills/job-application-assistant/03-writing-style.md` - `.claude/skills/job-application-assistant/04-job-evaluation.md` +- The master CV baseline template (`cv/main_example.tex`) +- The workspace root `CLAUDE.md` file (specifically the Candidate Profile section) Do NOT read `05-cv-templates.md` or `06-cover-letter-templates.md` — those govern LaTeX structure the drafter already applied and are not needed for content critique. -### 3. Drafts to Review +### 3. Factual Grounding Audit +Compare every date, employer, job title, and quantitative metric in both drafts against the union of three sources: `.claude/skills/job-application-assistant/01-candidate-profile.md` + the master CV baseline template (`cv/main_example.tex`) + `CLAUDE.md`'s Candidate Profile section. A claim is grounded if ANY of these sources supports it. Mismatches between these three sources themselves must be reported to the user as a profile-consistency warning rather than treated as draft drift. Draft mismatches must be flagged as Part A edits with `"reason": "grounding"` so they can be distinguished from style changes. Keep the tolerance honest: reframed emphasis is fine; changed facts and escalated numbers are not. + +### 4. Drafts to Review Both drafts are provided inline below. Do NOT use the Read tool on the draft files — use these exact texts. @@ -130,12 +138,12 @@ Both drafts are provided inline below. Do NOT use the Read tool on the draft fil -### 4. Job Posting +### 5. Job Posting -### 5. Produce Feedback +### 6. Produce Feedback Return your feedback in **two parts**: @@ -146,7 +154,7 @@ A JSON array of concrete edits the drafter can apply directly without re-reading "file": "cv/main__.tex" | "cover_letters/cover__.tex", "old_string": "", "new_string": "", - "reason": "" + "reason": "" } ``` Only use this format when you can quote the exact `old_string` from the drafts above. Make `old_string` unique — include enough surrounding context so it matches exactly once per file.