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 /apply command.
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →