#!/usr/bin/env node /** * cv-sync-check.mjs — Validates that the career-ops setup is consistent. * * Checks: * 1. cv.md exists * 2. config/profile.yml exists and has required fields * 3. No hardcoded metrics in _shared.md or batch/batch-prompt.md * 4. article-digest.md freshness (if exists) */ import { readFileSync, existsSync, statSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const projectRoot = __dirname; const warnings = []; const errors = []; // 1. Check cv.md exists const cvPath = join(projectRoot, 'cv.md'); if (!existsSync(cvPath)) { errors.push('cv.md not found in project root. Create it with your CV in markdown format.'); } else { const cvContent = readFileSync(cvPath, 'utf-8'); if (cvContent.trim().length < 100) { warnings.push('cv.md seems too short. Make sure it contains your full CV.'); } } // 2. Check profile.yml exists const profilePath = join(projectRoot, 'config', 'profile.yml'); if (!existsSync(profilePath)) { errors.push('config/profile.yml not found. Copy from config/profile.example.yml and fill in your details.'); } else { const profileContent = readFileSync(profilePath, 'utf-8'); const requiredFields = ['full_name', 'email', 'location']; for (const field of requiredFields) { if (!profileContent.includes(field) || profileContent.includes(`"Jane Smith"`)) { warnings.push(`config/profile.yml may still have example data. Check field: ${field}`); break; } } } // 3. Check for hardcoded metrics in prompt files const filesToCheck = [ { path: join(projectRoot, 'modes', '_shared.md'), name: '_shared.md' }, { path: join(projectRoot, 'batch', 'batch-prompt.md'), name: 'batch-prompt.md' }, ]; // Pattern: numbers that look like hardcoded metrics (e.g., "170+ hours", "90% self-service") const metricPattern = /\b\d{2,4}\+?\s*(hours?|%|evals?|layers?|tests?|fields?|bases?)\b/gi; for (const { path, name } of filesToCheck) { if (!existsSync(path)) continue; const content = readFileSync(path, 'utf-8'); // Skip lines that are clearly instructions (contain "NEVER hardcode" etc.) const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.includes('NEVER hardcode') || line.includes('NUNCA hardcode') || line.startsWith('#') || line.startsWith('