How Relevance-Weighted CV Cutting Works for Long CVs in MadsLorentzen/ai-job-search
The relevance-weighted CV cutting algorithm removes the least important lines first by scoring every bullet on relevance to the job posting, uniqueness in the document, and narrative dependency on the cover letter.
The MadsLorentzen/ai-job-search repository implements intelligent length management for generated CVs. When content exceeds the two-page limit, the system preserves high-impact material through a multi-factor scoring system rather than arbitrary truncation. This article explains the mechanics of relevance-weighted CV cutting and where the logic lives in the codebase.
The Three Scoring Dimensions
Every line or bullet in the CV receives a score based on three weighted criteria. According to the CV-template guidance in .claude/skills/job-application-assistant/05-cv-templates.md (see section "Relevance-weighted cutting" around line 298), these dimensions ensure that job-specific and irreplaceable content survives the cut.
Relevance to Target Posting
This criterion measures keyword overlap between the CV line and the job description. Lines that explicitly match responsibilities, qualifications, or technical requirements from the posting receive higher scores. The algorithm counts matching terms to produce a numeric relevance value.
Uniqueness in the Document
Duplicated information receives a penalty. If a skill, achievement, or responsibility appears multiple times across different roles, the duplicate instances score lower. The system prioritizes keeping the first or strongest occurrence while trimming redundant repetitions.
Narrative Load (Cover-Letter Dependency)
Lines referenced by the cover letter carry a protective weight. Removing such a line would force cover-letter revisions, so the algorithm assigns a higher "cost" to preserve narrative consistency. Any bullet that serves as an anchor for cover-letter claims receives a bonus score.
The Cutting Algorithm
The implementation follows a four-step iterative process. The /apply command in .claude/commands/apply.md (line 245) invokes this routine when CV generation produces overflow.
Step 1: Scoring
The parser decomposes the CV into individual lines or bullets. Each element receives a total score = relevance + uniqueness + narrative load.
Step 2: Ranking
All lines sort by ascending total score. Lower scores indicate higher candidacy for removal.
Step 3: Iterative Removal
The algorithm deletes the lowest-scoring line, re-evaluates page count, and repeats until the document fits within two pages. This greedy approach ensures minimal loss of valuable content.
Step 4: Final Sanity Check
After cutting completes, the system verifies that no required cover-letter references were broken. If removal orphaned a cover-letter citation, the line restores and the next lowest-scoring candidate deletes instead.
Code Implementation
The repository's internal tooling implements this logic. These Python snippets illustrate the core scoring and cutting mechanics:
def compute_line_score(line, posting_keywords, cover_letter_refs):
# 1. Relevance: count matching keywords
relevance = sum(1 for kw in posting_keywords if kw.lower() in line.lower())
# 2. Uniqueness: penalize if the line appears elsewhere
uniqueness = 1 if line.count(line) == 1 else 0
# 3. Narrative load: 1 if the cover letter references this line, else 0
narrative_load = 1 if any(ref in line for ref in cover_letter_refs) else 0
# Lower total score = less important → cut first
return relevance + uniqueness + narrative_load
def cut_cv_to_two_pages(cv_lines, posting_keywords, cover_letter_refs):
# Keep cutting until the page count fits (simple character-count heuristic)
while estimated_pages(cv_lines) > 2:
scores = [(i, compute_line_score(l, posting_keywords, cover_letter_refs))
for i, l in enumerate(cv_lines)]
# Find the line with the lowest score
cut_idx = min(scores, key=lambda x: x[1])[0]
del cv_lines[cut_idx] # remove that line
return cv_lines
The compute_line_score function implements the three-dimensional scoring model. The cut_cv_to_two_pages function executes the iterative removal loop with page-count validation.
Key Source Files
| File | Role in Relevance-Weighted Cutting |
|---|---|
README.md |
Feature overview and motivation (line 240) |
.claude/skills/job-application-assistant/05-cv-templates.md |
Detailed scoring criteria and cutting rules (line 298) |
.claude/commands/apply.md |
Invocation during /apply workflow (line 245) |
Summary
- Relevance-weighted CV cutting preserves job-specific content by scoring every line on three criteria: posting relevance, document uniqueness, and cover-letter dependency.
- The algorithm removes the lowest-scoring lines first through an iterative process with built-in narrative protection.
- Implementation resides in the Claude skills framework under
.claude/skills/job-application-assistant/and activates automatically during the/applycommand. - The sanity check ensures cover-letter consistency even when aggressive cutting is required.
Frequently Asked Questions
What happens if the algorithm cannot reduce the CV to two pages?
If all remaining lines carry high scores (for example, every bullet is cover-letter referenced and highly relevant), the system may need manual intervention. The current implementation prioritizes quality over strict page limits and will preserve critical content even if slightly over length. Users receive a warning to manually review.
Does the relevance score use semantic matching or exact keyword matches?
The documented implementation uses exact keyword counting. The compute_line_score function performs case-insensitive substring matching: kw.lower() in line.lower(). More sophisticated semantic similarity remains unimplemented in the current codebase.
How does the algorithm handle duplicate skills across multiple positions?
The uniqueness criterion penalizes duplication. Lines appearing elsewhere in the document receive uniqueness = 0 versus 1 for unique content. This ensures the strongest single instance survives while weaker repetitions are trimmed first.
Can users adjust the weighting between the three scoring dimensions?
The current implementation uses fixed 1:1:1 weighting. No configuration parameters exist to boost relevance versus uniqueness or narrative load. All three dimensions contribute equally to the total score in the released version.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →