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
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test": "vitest run --passWithNoTests"
},
"dependencies": {
"@cv-builder/core": "workspace:*"
"@cv-builder/core": "workspace:*",
"chalk": "^5.6.2"
},
"devDependencies": {
"@types/node": "^25.6.2",
Expand Down
78 changes: 51 additions & 27 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { evaluate, listArchetypes } from "@cv-builder/core";
import chalk from "chalk";

const [, , command, ...args] = process.argv;

Expand All @@ -22,12 +23,31 @@ async function main() {
printHelp();
break;
default:
console.error(`Unknown command: ${command}\n`);
console.error(chalk.red(`Unknown command: ${command}\n`));
printHelp();
process.exit(1);
}
}

function colorScore(score: number): string {
if (score <3) return chalk.red(`${score}/5`);
if (score <4) return chalk.yellow(`${score}/5`);
return chalk.green(`${score}/5`);
}

function colorDimScore(score: number, max: number): string {
if (score<3) return chalk.red(`${score}/${max}`);
if (score<4) return chalk.yellow(`${score}/${max}`);
return chalk.green(`${score}/${max}`);
}

function colorBar(score: number, max: number): string {
const filled = "█".repeat(score);
const empty = "░".repeat(max - score);
if (score <3) return chalk.red(filled) + chalk.dim(empty);
if (score <4) return chalk.yellow(filled) + chalk.dim(empty);
return chalk.green(filled) + chalk.dim(empty);
}
Comment on lines +26 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

No test coverage for new coloring/threshold logic.

colorScore, colorDimScore, and colorBar introduce new bucketing behavior (red/yellow/green thresholds) but no accompanying tests are visible in this change. As per path instructions, "Behavior changes should be covered by tests" for packages/cli/**.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/src/cli.ts` around lines 26 - 50, Add tests for the new CLI
coloring thresholds in colorScore, colorDimScore, and colorBar. Cover the
red/yellow/green bucket boundaries and the filled/empty bar rendering so the
behavior change is exercised. Place the tests alongside the existing CLI
coverage for packages/cli/src/cli.ts and assert the expected output for values
around the cutoff points.

Source: Path instructions

async function handleEvaluate(args: string[]) {
const cvPath = args[0];
const jdPath = readOptionValue(args, "--jd");
Expand Down Expand Up @@ -58,32 +78,36 @@ async function handleEvaluate(args: string[]) {
return;
}

console.log(`\n CV Score: ${result.score}/5\n`);
console.log(` Archetype detected: ${result.archetype.name}\n`);
console.log(" Dimensions:");
console.log(`\n CV Score: ${colorScore(result.score)}\n`);
console.log(` Archetype detected: ${chalk.cyan(result.archetype.name)}\n`);
console.log(chalk.bold(" Dimensions:"));
for (const dim of result.dimensions) {
const bar = "█".repeat(dim.score) + "░".repeat(dim.maxScore - dim.score);
console.log(` ${bar} ${dim.score}/${dim.maxScore} ${dim.name}`);
const bar = colorBar(dim.score, dim.maxScore);
const score = colorDimScore(dim.score, dim.maxScore);
console.log(` ${bar} ${score} ${dim.name}`);
}

if (result.strengths.length > 0) {
console.log("\n Strengths:");
console.log(chalk.bold("\n Strengths:"));
for (const s of result.strengths) {
console.log(` ${s}`);
console.log(` ${chalk.green("✓")} ${s}`);
}
}

if (result.issues.length > 0) {
console.log("\n Issues:");
console.log(chalk.bold("\n Issues:"));
for (const issue of result.issues) {
const icon = issue.severity === "critical" ? "✗" : "⚠";
console.log(` ${icon} [${issue.severity}] ${issue.element}`);
console.log(` Why: ${issue.why}`);
console.log(` Fix: ${issue.fix}`);
if (issue.severity === "critical") {
console.log(` ${chalk.red("✗")} ${chalk.red(`[${issue.severity}]`)} ${issue.element}`);
} else {
console.log(` ${chalk.yellow("⚠")} ${chalk.yellow(`[${issue.severity}]`)} ${issue.element}`);
}
console.log(` ${chalk.dim("Why:")} ${issue.why}`);
console.log(` ${chalk.dim("Fix:")} ${issue.fix}`);
}
}

console.log(`\n ATS Compatible: ${result.atsCompatible ? "✓ Yes" : "✗ No"}\n`);
console.log(`\n ATS Compatible: ${result.atsCompatible ? chalk.green("✓ Yes") : chalk.red("✗ No")}\n`);
}

function readOptionValue(args: string[], option: string): string | undefined {
Expand All @@ -103,29 +127,29 @@ function readOptionValue(args: string[], option: string): string | undefined {

function handleListArchetypes() {
const archetypes = listArchetypes();
console.log("\n Available role archetypes:\n");
console.log(chalk.bold("\n Available role archetypes:\n"));
for (const a of archetypes) {
console.log(` ${a.name} (${a.id})`);
console.log(` ${chalk.cyan("•")} ${chalk.bold(a.name)} ${chalk.dim(`(${a.id})`)}`);
console.log(` ${a.description}`);
console.log(` Keywords: ${a.keywords.slice(0, 5).join(", ")}...\n`);
console.log(` ${chalk.dim("Keywords:")} ${a.keywords.slice(0, 5).join(", ")}...\n`);
}
}

function printHelp() {
console.log(`
CV Builder CLI — Evaluate and tailor your CV
${chalk.bold("CV Builder CLI")} ${chalk.dim("— Evaluate and tailor your CV")}

USAGE
${chalk.bold("USAGE")}
cv-builder <command> [options]

COMMANDS
evaluate <cv-file> [--jd <jd-file>] [--format <text|json>]
Score your CV (optionally against a JD).
Use --format json for machine-readable output.
archetypes List available role archetypes
help Show this help

EXAMPLES
${chalk.bold("COMMANDS")}
${chalk.cyan("evaluate")} <cv-file> [--jd <jd-file>] [--format <text|json>]
Score your CV (optionally against a JD).
Use --format json for machine-readable output.
${chalk.cyan("archetypes")} List available role archetypes
${chalk.cyan("help")} Show this help
${chalk.bold("EXAMPLES")}
cv-builder evaluate ./my-cv.md
cv-builder evaluate ./my-cv.md --jd ./job-description.md
cv-builder evaluate ./my-cv.md --format json
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.