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 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);
});
});
52 changes: 52 additions & 0 deletions packages/intelligence/src/archetypes/data-ml-engineer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { Archetype } from "@cv-builder/schemas";

export const dataMlEngineer: Archetype = {
id: "data-ml-engineer",
name: "Data & ML Engineer",
description: "Engineers building data pipelines and machine learning systems",
keywords: [
"python",
"sql",
"spark",
"airflow",
"dbt",
"pandas",
"pytorch",
"tensorflow",
"scikit-learn",
"feature engineering",
"etl",
"data pipeline",
"warehouse",
"snowflake",
"bigquery",
"mlops",
"model serving",
"experiment tracking",
],
evaluationWeights: {
shippedEvidence: 0.25,
quantifiedImpact: 0.2,
toolingVisibility: 0.25,
atsCompatibility: 0.1,
keywordMatch: 0.1,
publicProof: 0.1,
},
actionVerbs: [
"Built",
"Trained",
"Deployed",
"Productionized",
"Optimized",
"Automated",
"Modeled",
"Scaled",
],
antiPatterns: [
"familiar with machine learning",
"exposure to data science",
"worked with big data",
"passionate about AI",
],
version: "1.0.0",
};
20 changes: 20 additions & 0 deletions packages/intelligence/src/archetypes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Archetype } from "@cv-builder/schemas";
import { dataMlEngineer } from "./data-ml-engineer.js";
import { productManager } from "./product-manager.js";
import { softwareEngineer } from "./software-engineer.js";

// Register new archetypes here. Software Engineer is the fallback when nothing
// else clearly matches.
export const ARCHETYPES: Archetype[] = [softwareEngineer, productManager, dataMlEngineer];

export const DEFAULT_ARCHETYPE = softwareEngineer;

export function getArchetype(id: string): Archetype | undefined {
return ARCHETYPES.find((a) => a.id === id);
}

export function listArchetypes(): Archetype[] {
return ARCHETYPES;
}

export { dataMlEngineer, productManager, softwareEngineer };
53 changes: 53 additions & 0 deletions packages/intelligence/src/archetypes/product-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Archetype } from "@cv-builder/schemas";

export const productManager: Archetype = {
id: "product-manager",
name: "Product Manager",
description: "Product managers owning discovery, roadmap, and delivery",
keywords: [
"roadmap",
"discovery",
"user research",
"a/b testing",
"experiment",
"okrs",
"kpis",
"retention",
"activation",
"funnel",
"churn",
"stakeholder",
"prioritization",
"agile",
"amplitude",
"mixpanel",
"go-to-market",
"pricing",
],
evaluationWeights: {
shippedEvidence: 0.25,
quantifiedImpact: 0.25,
toolingVisibility: 0.1,
atsCompatibility: 0.15,
keywordMatch: 0.15,
publicProof: 0.1,
},
actionVerbs: [
"Launched",
"Owned",
"Drove",
"Grew",
"Defined",
"Prioritized",
"Increased",
"Reduced",
],
antiPatterns: [
"passionate about products",
"leveraged synergies",
"aligned stakeholders",
"wore many hats",
"helped with",
],
version: "1.0.0",
};
54 changes: 54 additions & 0 deletions packages/intelligence/src/archetypes/software-engineer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Archetype } from "@cv-builder/schemas";

export const softwareEngineer: Archetype = {
id: "software-engineer",
name: "Software Engineer",
description: "Frontend, backend, and full-stack engineers building software",
keywords: [
"typescript",
"javascript",
"python",
"go",
"java",
"react",
"node",
"postgres",
"redis",
"kafka",
"docker",
"kubernetes",
"aws",
"ci/cd",
"microservices",
"rest",
"graphql",
"testing",
"system design",
],
evaluationWeights: {
shippedEvidence: 0.3,
quantifiedImpact: 0.2,
toolingVisibility: 0.2,
atsCompatibility: 0.1,
keywordMatch: 0.1,
publicProof: 0.1,
},
actionVerbs: [
"Built",
"Shipped",
"Designed",
"Scaled",
"Optimized",
"Migrated",
"Automated",
"Refactored",
],
antiPatterns: [
"familiar with",
"exposure to",
"team player",
"fast learner",
"responsible for various tasks",
],
version: "1.0.0",
};
33 changes: 33 additions & 0 deletions packages/intelligence/src/detect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Archetype } from "@cv-builder/schemas";
import { ARCHETYPES, DEFAULT_ARCHETYPE } from "./archetypes/index.js";

function hasKeyword(text: string, keyword: string): boolean {
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// hyphen counts as a word char so "go" doesn't match "go-to-market"
return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`).test(text);
}

function countMatches(text: string, keywords: string[]): number {
return keywords.reduce((n, kw) => (hasKeyword(text, kw.toLowerCase()) ? n + 1 : n), 0);
}

// Picks the archetype whose keywords appear most in the resume. Falls back to
// the default when there's no signal, so detection never returns nothing.
export function detectArchetype(
resumeText: string,
archetypes: Archetype[] = ARCHETYPES
): Archetype {
const text = resumeText.toLowerCase();
let best = DEFAULT_ARCHETYPE;
let bestScore = 0;

for (const archetype of archetypes) {
const score = countMatches(text, archetype.keywords);
if (score > bestScore) {
best = archetype;
bestScore = score;
}
}

return best;
}
21 changes: 21 additions & 0 deletions packages/intelligence/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Rubric v1, role archetypes, and the validator specs the prompts encode.

export {
ARCHETYPES,
DEFAULT_ARCHETYPE,
dataMlEngineer,
getArchetype,
listArchetypes,
productManager,
softwareEngineer,
} from "./archetypes/index.js";
export { detectArchetype } from "./detect.js";
export {
RUBRIC,
RUBRIC_DIMENSIONS,
RUBRIC_VERSION,
type RubricDimension,
} from "./rubric.js";

export { ATS_RULES, type AtsCheck, checkAtsCompatibility } from "./validators/ats.js";
export { CLAIM_RULES } from "./validators/claims.js";
Loading
Loading