Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/commands/evaluate-cv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
description: Score a resume against the CV Builder rubric, fully locally
argument-hint: <path-to-resume> [--jd <path-to-job-description>]
---

Evaluate the resume using the `cv-evaluation` skill.

Arguments: $ARGUMENTS

- The first argument is the path to the resume (PDF, markdown, or text).
- An optional `--jd <path>` adds a job description as keyword context only.

Read the file(s), run the extract → detect → score → validate-claims pipeline,
validate the result against `EvalResultSchema`, then present the overall score,
the per-dimension breakdown, the top issues with fixes, and any unsupported
claims. Nothing leaves this machine.
15 changes: 15 additions & 0 deletions .claude/commands/setup-profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
description: Gather a resume to evaluate, three ways
argument-hint: [path-to-file]
---

Help the user get a resume ready for `/evaluate-cv`. Pick the path that fits:

1. **File** — if `$ARGUMENTS` names a file, read it and confirm what you found.
2. **Paste** — if they pasted resume text, use it directly.
3. **Interview** — if they have nothing prepared, ask a short, focused set of
questions (roles, dates, what they built, measurable outcomes, links) and
assemble a plain-text resume from their answers.

Once you have the resume text, save it to a file the user chooses and tell them
to run `/evaluate-cv <that-file>`. Don't invent details they didn't give you.
17 changes: 17 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"permissions": {
"allow": ["Read", "Glob", "Grep"]
},
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/welcome.sh\""
}
]
}
]
}
}
46 changes: 46 additions & 0 deletions .claude/skills/cv-evaluation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: cv-evaluation
description: Evaluate a resume against the CV Builder rubric and return a scored, schema-valid EvalResult. Use when the user wants their CV or resume scored, critiqued, or checked for ATS issues and unsupported claims.
---

# CV Evaluation

Run a resume through the same three-step evaluation the hosted product uses,
entirely locally. Everything you need lives in this repo — read the source, don't
guess.

## Inputs

- A resume file (PDF, markdown, or plain text). For PDF, read the text content.
- Optionally a job description, used only as keyword context (Phase 1 does not
fit-score against a JD).

## Pipeline

1. **Extract.** Apply `packages/prompts/prompts/extract.md` to the raw resume
text to produce a structured `Resume`.
2. **Detect archetype.** Match the resume against the role archetypes in
`packages/intelligence/src/archetypes/`. Pick the best fit; default to
Software Engineer when there's no clear signal. See [archetypes](./archetypes.md).
3. **Score.** Apply `packages/prompts/prompts/score.md` with the detected
archetype's weights and the rubric. See [rubric](./rubric.md) and
[scoring](./scoring.md). Surface **at least 3** issues, each quoting the exact
resume text and giving a concrete fix.
4. **Validate claims.** Apply `packages/prompts/prompts/validate-claims.md` to
flag unsupported claims. See [claim-validation](./claim-validation.md).

## Output

Produce one `EvalResult` (schema: `packages/schemas/src/evaluation.ts`). It must
carry `rubricVersion` and `archetypeVersion`. Validate the object against
`EvalResultSchema` before presenting it — never show an unvalidated result.

Run the validation from inside `packages/schemas` (zod only resolves there under
pnpm's layout). Pipe the JSON to node over stdin rather than writing it to a
file, so nothing derived from the resume touches the working tree. If
dependencies aren't installed, don't error out: check the object field by field
against `evaluation.ts` instead, and tell the user that running `pnpm install`
enables strict validation.

Then summarize for the user: overall score, the per-dimension breakdown, the top
issues with their fixes, and any unsupported claims.
10 changes: 10 additions & 0 deletions .claude/skills/cv-evaluation/archetypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Archetypes

Each role archetype (keywords, dimension weights, action verbs, anti-patterns)
is one file in `packages/intelligence/src/archetypes/`. Read the relevant file —
the weights there drive the overall score, so use them exactly.

To detect the archetype: count how many of each archetype's keywords appear in
the resume and pick the highest. The same heuristic is implemented in
`packages/intelligence/src/detect.ts` (`detectArchetype`). When nothing clearly
matches, use Software Engineer.
9 changes: 9 additions & 0 deletions .claude/skills/cv-evaluation/claim-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Claim Validation

The prompt and the rules for what counts as an unsupported claim are in
`packages/prompts/prompts/validate-claims.md` (rules sourced from
`packages/intelligence/src/validators/claims.ts`).

Goal: catch what a recruiter would quietly distrust — a metric with no scope, a
tool listed but never used in a bullet, a seniority claim with no team size or
reach. For each flagged claim, say what's missing, not just that it's weak.
8 changes: 8 additions & 0 deletions .claude/skills/cv-evaluation/rubric.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Rubric

The six dimensions, their 0–5 anchors, and the current `RUBRIC_VERSION` are
defined in `packages/intelligence/src/rubric.ts`. Read that file — it is the
source of truth. Don't reproduce the anchors from memory.

Score each dimension on its own 0–5 scale, then weight by the archetype (see
[archetypes](./archetypes.md)) to get the overall 0–5 score.
12 changes: 12 additions & 0 deletions .claude/skills/cv-evaluation/scoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Scoring

The scoring instructions and exact output shape are in
`packages/prompts/prompts/score.md`. Fill its `{{...}}` placeholders from the
detected archetype's weights and the rubric.

Hold the line on quality:

- Every issue quotes the exact resume text it's about — no paraphrasing.
- Every issue has a concrete fix, not "consider improving this".
- Overall score = sum(dimension score × weight), on the 0–5 scale.
- Be honest. A weak resume should score low; inflating it doesn't help anyone.
19 changes: 19 additions & 0 deletions .claude/welcome.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Emits the CV Builder menu as a SessionStart hook systemMessage, shown to the
# user when they open Claude Code at the repo root.

# No jq → skip the banner rather than break the session start.
command -v jq >/dev/null 2>&1 || exit 0

read -r -d '' MENU <<'EOF'
CV Builder — evaluate your resume locally and privately. Nothing leaves your machine.

/evaluate-cv <resume.pdf|.md|.txt> [--jd <job.md>] Score a resume against the rubric
/setup-profile Assemble a resume if you don't have a file yet

No build or install needed to evaluate.

What would you like to do?
EOF

jq -n --arg msg "$MENU" '{systemMessage: $msg}'
25 changes: 25 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# CV Builder

Open source, privacy-first resume evaluator. This repo doubles as a **power-user
pack**: clone it, open Claude Code here, and evaluate your resume locally with
your own agent — nothing leaves your machine.

## Power-user commands

- `/evaluate-cv <resume.pdf|.md|.txt> [--jd <job.md>]` — score a resume against
the rubric and surface issues + unsupported claims.
- `/setup-profile` — assemble a resume if you don't have a file ready.

## No build needed for evaluation

The `cv-evaluation` skill (`.claude/skills/cv-evaluation/`) reads the prompts in
`packages/prompts/prompts/` and the rubric/archetypes in
`packages/intelligence/src/` straight from the repo, and returns an `EvalResult`
validated against `packages/schemas`. `pnpm install` is only needed to run the
package tests or the web app — not for the power-user flow.

## Repo shape

pnpm + turbo monorepo. `packages/schemas` (contract) → `packages/intelligence`
(rubric + archetypes) → `packages/prompts` (the prompt pack) → the skill ties
them together.
60 changes: 60 additions & 0 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Power User — evaluate your resume locally

Run the full CV evaluation on your own machine with your own
[Claude Code](https://claude.com/claude-code). Your resume never leaves your
computer — no server, no account, no API keys in this repo.

## Quickstart

```bash
git clone https://github.com/TechImmigrants/cv-builder.git
cd cv-builder
claude # open Claude Code at the repo root
```

No build or install is needed to evaluate — the skill reads the prompts and
rubric straight from the repo. (`pnpm install` is only for running the package
tests or the web app.)

Then, inside Claude Code:

```text
/evaluate-cv ./my-resume.pdf
```

Optionally add a job description as keyword context:

```text
/evaluate-cv ./my-resume.pdf --jd ./job.md
```

No resume handy? Run `/setup-profile` and it'll help you assemble one.

## What you get

An honest, role-adaptive score (0–5) with:

- a per-dimension breakdown across the six rubric dimensions,
- at least three specific issues, each quoting the exact line and giving a fix,
- any unsupported claims a recruiter would distrust,
- an ATS-compatibility read.

## How it works

The `/evaluate-cv` command drives the **cv-evaluation** skill
(`.claude/skills/cv-evaluation/`), which runs four steps against this repo:

| Step | Source |
|---|---|
| Extract structured resume | `packages/prompts/prompts/extract.md` |
| Detect role archetype | `packages/intelligence/src/archetypes/` |
| Score against the rubric | `packages/prompts/prompts/score.md` + `packages/intelligence/src/rubric.ts` |
| Flag unsupported claims | `packages/prompts/prompts/validate-claims.md` |

The result is validated against `EvalResultSchema`
(`packages/schemas`) before you see it.

## Privacy

Everything runs through your local Claude Code. This pack makes no network calls
of its own and stores nothing.
6 changes: 6 additions & 0 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@cv-builder/power-user",
"version": "0.1.0",
"description": "Power-user pack: evaluate your resume locally with your own Claude Code",
"private": true
}
16 changes: 16 additions & 0 deletions packages/intelligence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# @cv-builder/intelligence

The scoring brain the prompts and skill reference: the fixed rubric, the role
archetypes, and the validator specs.

- **Rubric v1** — six dimensions (Shipped Evidence, Quantified Impact, Tech/Tool
Visibility, ATS Compatibility, Keyword Match, Public Proof) with 0–5 anchors,
tagged `RUBRIC_VERSION`.
- **Archetypes** — role config (keywords, dimension weights, action verbs,
anti-patterns). Ships Software Engineer, Product Manager, Data & ML Engineer.
Add one by dropping a file in `src/archetypes/` and registering it.
- **Validators** — `checkAtsCompatibility()` plus the ATS and claim rule sets
the prompts encode.

`detectArchetype(resumeText)` picks the best-matching archetype, falling back to
Software Engineer when there's no signal.
36 changes: 36 additions & 0 deletions packages/intelligence/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@cv-builder/intelligence",
"version": "0.1.0",
"description": "Scoring rubric, role archetypes, and validators for CV evaluation",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@cv-builder/schemas": "workspace:*"
},
"devDependencies": {
"@types/node": "^25.6.2",
"typescript": "^5.7.0",
"vitest": "^3.0.0"
},
"keywords": [
"cv",
"resume",
"rubric",
"archetypes",
"ats"
],
"license": "MIT"
}
62 changes: 62 additions & 0 deletions packages/intelligence/src/__tests__/intelligence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ArchetypeSchema } from "@cv-builder/schemas";
import { describe, expect, it } from "vitest";
import { ARCHETYPES, checkAtsCompatibility, detectArchetype, RUBRIC } from "../index.js";

describe("archetypes", () => {
it("ships at least 3, each valid against the schema", () => {
expect(ARCHETYPES.length).toBeGreaterThanOrEqual(3);
for (const archetype of ARCHETYPES) {
expect(ArchetypeSchema.safeParse(archetype).success, archetype.id).toBe(true);
}
});

it("has weights summing to 1.0", () => {
for (const { id, evaluationWeights } of ARCHETYPES) {
const sum = Object.values(evaluationWeights).reduce((a, b) => a + b, 0);
expect(sum, id).toBeCloseTo(1, 5);
}
});

it("covers every rubric dimension with a weight", () => {
const keys = RUBRIC.dimensions.map((d) => d.key).sort();
for (const { id, evaluationWeights } of ARCHETYPES) {
expect(Object.keys(evaluationWeights).sort(), id).toEqual(keys);
}
});
});

describe("detectArchetype", () => {
it("detects a software engineer resume", () => {
const text =
"Built React and Node services, scaled Postgres, deployed on Kubernetes.";
expect(detectArchetype(text).id).toBe("software-engineer");
});

it("detects a product manager resume", () => {
const text = "Owned the roadmap, ran A/B testing and user research, grew retention.";
expect(detectArchetype(text).id).toBe("product-manager");
});

it("falls back to the default on no signal", () => {
expect(detectArchetype("hello world").id).toBe("software-engineer");
});

it("matches whole words only, not substrings", () => {
const text =
"Drove go-to-market with MongoDB-backed analytics, owned the roadmap and funnel.";
expect(detectArchetype(text).id).toBe("product-manager");
});
});

describe("checkAtsCompatibility", () => {
it("flags a table layout", () => {
expect(checkAtsCompatibility("| Skills | Years |\n| React | 5 |").compatible).toBe(
false
);
});

it("passes clean single-column text", () => {
const text = "Experience\nBuilt things at Acme.\nContact: jane@example.com";
expect(checkAtsCompatibility(text).compatible).toBe(true);
});
});
Loading
Loading