# How JD Skill Gap Analysis Identifies Resume Gaps in Career‑Ops: A Zero‑LLM Deep Dive

> Discover how JD skill gap analysis in Career-Ops identifies resume gaps without LLMs. Our regex pipeline precisely matches your skills to job requirements.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: deep-dive
- Published: 2026-08-19

---

**Career‑Ops performs JD skill gap analysis using a deterministic, three‑stage regex pipeline that extracts technical tokens from job descriptions, splits your resume into explicit and implicit skill sets, and classifies each requirement as existing, supported‑by‑resume, or a gap—without ever using LLMs or modifying your CV.**

JD skill gap analysis helps you identify missing qualifications before submitting applications. The [Career‑Ops](https://github.com/santifer/career-ops) repository implements this functionality in `jd‑skill‑gap.mjs` as a read‑only, zero‑LLM checker that parses job descriptions and compares extracted requirements against both the named skills section and prose of your resume. This approach produces three distinct buckets—**existing skills**, **skills supported by resume prose**, and **actual gaps**—enabling honest, evidence‑based tailoring without AI hallucinations.

## How the Career‑Ops Skill Gap Analyzer Works

The analysis proceeds through three deterministic stages, each implemented as a pure function with no external API dependencies.

### Stage 1: Extracting Technical Skills from Job Descriptions

The process begins with `scanJd` (lines 56‑73) in `jd‑skill‑gap.mjs`, which parses the job description line‑by‑line to locate requirement‑style headings. The algorithm uses `REQUIREMENT_HEADER_RE` to detect synonyms like “Requirements,” “What we’re looking for,” or “Qualifications,” while `NON_REQUIREMENT_HEADER_RE` closes the block when encountering sections like “Benefits” or “Perks.”

Once inside a requirements block, the scanner processes bullet points with `SKILL_TOKEN_RE`, a regex that captures uppercase‑leading technical terms (e.g., `Kubernetes`, `C++`, `Docker`) while filtering out generic nouns via the `STOPWORDS` array (lines 21‑37). The `extractJdSkills` function (lines 200‑202) returns a deduplicated list of candidate skills and a flag indicating whether a requirements block was ever opened.

### Stage 2: Parsing the Resume Skills Section

The analyzer uses `splitSkillsSection` (lines 81‑113) to bifurcate your [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) file. It locates the markdown heading “Skills” and treats everything beneath it as **named skills** (explicitly listed capabilities). All remaining content—work experience, projects, and education—becomes **prose** (implicit skill mentions). This separation allows the tool to distinguish between skills you claim explicitly versus those buried in narrative text.

### Stage 3: Classifying Matches, Partial Matches, and Gaps

The `classifySkillGaps` function (lines 24‑63) tokenizes both the named skills and prose sections using `skill‑extract.mjs` (`extractSkills` + `canonicalize`), which normalizes aliases like “k8s” to “Kubernetes.” For each JD skill extracted in Stage 1, the algorithm assigns one of three classifications:

- **Existing** – The canonical token appears in the named skills section.
- **Supported‑by‑Resume** – The token appears only in the prose section (you have experience but didn’t list it explicitly).
- **Gap** – The token is absent from the entire CV.

If the canonical extractor fails to recognize a token, the system falls back to `skillMentionedInText`, a word‑boundary regex that prevents false matches like “Java” inside “JavaScript.”

## Key Implementation Details and Edge Case Handling

**Diagnosing Extraction Failures**

When the JD yields no skills, `diagnoseExtraction` (lines 31‑55) distinguishes between *no requirements section found* (the regex never matched a header) and *no skill candidates extracted* (the section existed but contained no tokens matching `SKILL_TOKEN_RE`). This distinction allows downstream consumers to decide whether to skip the check or warn the user.

**Canonicalization and Aliases**

The `canonicalize` function imported from `skill‑extract.mjs` handles variant spellings and abbreviations, ensuring that “React.js” and “React” resolve to the same token during comparison. This normalization happens before the three‑bucket classification, preventing surface‑syntax mismatches from appearing as false gaps.

## Running JD Skill Gap Analysis from the Command Line

The CLI provides immediate human‑readable reports without modifying source files.

```bash

# Analyze a saved JD and get the three‑bucket summary

node jd-skill-gap.mjs jds/acme.md --summary

```

The `--summary` flag prints the existing skills, prose‑supported skills, and gaps to stdout. If the JD lacked a recognizable requirements block, the tool emits a warning suggesting manual review. Output from this command feeds into downstream modes like [`modes/pdf.md`](https://github.com/santifer/career-ops/blob/main/modes/pdf.md), which surface gaps to users before PDF generation begins.

## Programmatic Usage in Node.js

Integrate the analyzer into custom workflows by importing the core functions directly.

```javascript
import { extractJdSkills, classifySkillGaps } from './jd-skill-gap.mjs';
import { readFileSync } from 'fs';

// Load source documents
const jdText = readFileSync('jds/acme.md', 'utf8');
const cvText = readFileSync('cv.md', 'utf8');

// Stage 1: Extract JD requirements
const jdSkills = extractJdSkills(jdText);

// Stage 2 & 3: Compare against resume
const { existing, supportedByResume, gap } = classifySkillGaps(jdSkills, cvText);

console.log('Explicitly listed:', existing);
console.log('Mentioned in experience:', supportedByResume);
console.log('Critical gaps:', gap);

```

The API returns plain arrays, making it trivial to feed results into JSON APIs, CI pipelines, or custom reporting tools. Because the tool never writes to [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md), you can run this analysis safely in read‑only environments.

## Summary

- **Zero‑LLM Architecture** – `jd‑skill‑gap.mjs` uses only regex patterns (`SKILL_TOKEN_RE`, `REQUIREMENT_HEADER_RE`) and deterministic tokenization, ensuring reproducible results without API costs or hallucinations.
- **Three‑Bucket Classification** – Every JD skill is categorized as existing (named section), supported‑by‑resume (prose only), or gap (missing entirely).
- **Smart Tokenization** – Canonicalization via `skill‑extract.mjs` handles aliases, while word‑boundary fallbacks prevent substring false positives.
- **Read‑Only Design** – The tool strictly reports findings; it never mutates your resume, ensuring manual oversight of any tailoring decisions.
- **CLI and Programmatic APIs** – Use `node jd-skill-gap.mjs <file> --summary` for quick checks, or import `extractJdSkills` and `classifySkillGaps` for custom integrations.

## Frequently Asked Questions

### What makes Career‑Ops JD skill gap analysis “zero‑LLM”?

The entire pipeline relies on deterministic regex matching and static token lists rather than large language model inference. According to the Career‑Ops source code, functions like `scanJd` and `classifySkillGaps` use only JavaScript regular expressions (`SKILL_TOKEN_RE`, word‑boundary checks) and the `canonicalize` mapper to compare skills. This eliminates API latency, token costs, and the risk of LLM hallucinations inventing skill requirements that do not exist in the job description.

### How does the tool prevent false positives when matching skills?

Career‑Ops employs multiple guardrails: the `STOPWORDS` array filters generic nouns (lines 21‑37), `canonicalize` normalizes variants like “k8s” to “Kubernetes,” and the `skillMentionedInText` function uses word‑boundary regex to ensure “Java” does not match inside “JavaScript.” Additionally, the `NON_REQUIREMENT_HEADER_RE` pattern actively closes parsing blocks when encountering non‑requirement sections like “Benefits,” preventing the extraction of irrelevant tokens.

### Can the tool modify my resume to fill the identified gaps?

No. The JD skill gap analysis is strictly read‑only. As implemented in `jd‑skill‑gap.mjs`, the code never writes to [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md); it only reports the three classification buckets (existing, supported‑by‑resume, gap) to stdout or consuming modes like [`modes/pdf.md`](https://github.com/santifer/career-ops/blob/main/modes/pdf.md). This design ensures that only **honest, evidence‑based** tailoring occurs, requiring you to manually verify and add any missing skills rather than having an AI fabricate experience.

### What happens if the job description lacks a clear requirements section?

The `diagnoseExtraction` function (lines 31‑55) detects whether the JD parser failed to find a requirements header (`REQUIREMENT_HEADER_RE` never matched) or whether the section existed but contained no skill tokens. In the former case, the tool warns that no requirements block was detected; in the latter, it reports that the section was empty. Downstream consumers can then decide whether to skip the gap check entirely or prompt the user for manual skill extraction.