Skip to content
Productivity
Skill

/upskill

Compares tracked job postings against the candidate profile to identify skill gaps and generate a prioritized learning plan with study resources. Triggers on: /upskill, upskill, skill gaps, what should I learn, learning plan

From plugin
madslorentzen-ai-job-search
31k3 skills1 agent12 commands
Install
$ npx -y skills add MadsLorentzen/ai-job-search --skill upskill --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/upskill

Context preview

The summary Claude sees to decide when to auto-load this skill.

Compares tracked job postings against the candidate profile to identify skill gaps and generate a prioritized learning plan with study resources. Triggers on: /upskill, upskill, skill gaps, what should I learn, learning plan

SKILL.md

upskill.SKILL.md
name: upskill
description: >
  Compares tracked job postings against the candidate profile to identify skill gaps and generate
  a prioritized learning plan with study resources. Triggers on: /upskill, upskill, skill gaps,
  what should I learn, learning plan
allowed-tools: Read, Write, Glob, Grep, WebFetch, WebSearch

Upskill

---

Overview

`/upskill` analyses jobs you have tracked and your current profile to identify skill gaps, then produces a heatmap of those gaps and a learning plan with concrete, web-searched study resources and a recommended study order.

Invocation

  • **`/upskill`** — aggregate mode: analyses all jobs in `job_search_tracker.csv`, merged with ranked postings (`rank_score >= 45`) from `job_scraper/seen_jobs.json`
  • **`/upskill <URL>`** — targeted mode: analyses a single job posting fetched from the URL

---

Step 1: Detect Mode

Check whether the user provided a URL argument:

  • If the invocation was `/upskill` with no argument → **aggregate mode**
  • If the invocation was `/upskill <URL>` → **targeted mode**, store the URL for Step 2

In targeted mode, derive a slug from the job title and company for the report filename (e.g. `guardsix-senior-ai-engineer`). You will fetch the posting in Step 2.

Step 2: Load Data

Aggregate mode

1. Read `job_search_tracker.csv`. Extract all rows. The columns are: `date, company, sector, role, role_type, channel, status, contact_person, fit_rating, notes, cv_file, cover_letter_file, source` 2. For each row, note the `role`, `company`, and `fit_rating`. The `fit_rating` column is a 0–100 score where 100 = perfect fit. You will use it to weight gaps — a lower fit rating means the role exposed more gaps. 3. Read `job_scraper/seen_jobs.json`. Keep entries with `"status": "ranked"` and `rank_score >= 45` — the Moderate Fit floor from `04-job-evaluation.md` (below that, a job is Weak/Poor Fit and would otherwise dominate the heatmap with jobs the user shouldn't chase). For each kept entry, note its `title`, `company`, `rank_score`, and — when present — its recorded `gaps`. An entry with no `gaps` field (ranked before gap persistence existed) is skipped, counted, and reported once in the terminal: *"N ranked jobs were scored before gap persistence and contribute nothing; `/rank --all` re-scores them."* Never back-fill a missing `gaps` field by guessing from the title. 4. Read `.claude/skills/job-application-assistant/01-candidate-profile.md` to get the candidate's current skills and experience. 5. Check `upskill/` for the most recent aggregate report file (`report-YYYY-MM-DD.md`) — if one exists, note its date and load it for the diff in Step 8.

Targeted mode

1. Use WebFetch to retrieve the job posting from the URL. 2. Extract: job title, company, required skills, preferred skills, responsibilities, and any domain context. 3. Read `.claude/skills/job-application-assistant/01-candidate-profile.md` for the candidate's current skills. 4. No tracker data is used in targeted mode.

Step 3: Pass 1 — Hard Skill Diff

Extract required and preferred technical skills from each job source:

Aggregate mode

This mode now merges two sources — tracker rows (Step 2.1) and ranked postings from `seen_jobs.json` (Step 2.3) — so the same job is never double-counted and recorded gaps are preferred over inferred ones:

1. **Dedupe.** Match tracker rows against ranked entries on case-insensitive company + role (casefold + strip on both fields) — the same match `/notion-sync`'s Step 2 describes. A job present in both counts once. 2. **Recorded gaps beat inferred skills.** For any job that has a recorded `gaps` array (from a ranked entry, or from a tracker row that matched one), use those gap bullets directly as the skill list for that job instead of inferring from `role`/`sector`/`notes`. For a ranked-only job with no `gaps` (already skipped and counted in Step 2.3) or a tracker-only row, fall back to inferring likely required skills from `role`, `sector`, and `notes` — optionally WebFetch the row's `source` URL for more detail, but skip if the URL is missing or dead. 3. **One weight per job**, both 0–100 on the same scale: `(100 - fit_rating) / 100` for tracker rows, `(100 - rank_score) / 100` for ranked-only rows. If a job is in both (Step 3.1 matched it), prefer the tracker's numeric `fit_rating` for the weight. 4. **Score.** Build a **skill frequency map**: for each extracted skill (recorded gap bullet or inferred skill), count how many jobs mention it, then multiply each job's contribution by its weight from Step 3.3. Track whether each contribution came from a recorded gap or an inferred one, for Step 5's provenance column.

Final score for each skill: `sum of (weight × occurrence)` across all jobs.

Targeted mode

Extract the explicit required and preferred skills from the fetched posting. Each skill gets equal weight (no fit weighting needed since there is only one job). List required skills before preferred skills, then sort alphabetically within each group.

Diff against profile

Remove any skill from the list that is already present in the candidate profile (`01-candidate-profile.md`). Be generous — if the profile mentions a skill in any form (e.g. "Python" covers "Python scripting"), remove it.

What remains is the **hard skill gap list**. In aggregate mode, rank by score descending. In targeted mode, list required skill gaps before preferred skill gaps, then sort alphabetically within each group.

Step 4: Pass 2 — LLM Synthesis

Now reason holistically about gaps that the hard skill diff would miss. Consider:

  • **Domain knowledge gaps**: Does the candidate lack familiarity with the industry, domain, or problem space the jobs operate in? (e.g. cybersecurity, climate tech, quantitative finance)
  • **Soft skill gaps**: Do the job descriptions emphasise ways of working, communication styles, or leadership expectations that the profile does not address?
  • **Tooling and process gaps**: Frameworks, cloud se
Read more
Ships withmadslorentzen-ai-job-search

The job search that runs on your machine. An AI-powered job application framework built on Claude Code. Fork it, fill in your profile, and let Claude evaluate job postings, tailor your CV, write cover letters, and prepare you for interviews.

Get the whole plugin