santifer/career-ops · warning

⚠️ No config/cv-facts.json — forbidden/advisory phrase…

Error message

⚠️  No config/cv-facts.json — forbidden/advisory phrase checks did not run.

What it means

generate-pdf.mjs runs a fact gate via verify-cv-facts.mjs's assertFacts(), which checks the HTML for forbidden and advisory phrases using lists loaded from config/cv-facts.json. When that config file is missing, assertFacts reports configMissing and the phrase checks cannot run — this warning clarifies that any subsequent 'passed' verdict covers metrics/facts only, not phrase screening.

Solutions

  1. Create config/cv-facts.json with forbidden/advisory phrase lists (even empty lists) to make the gate explicit
  2. Run from the repository root so the relative config path resolves
  3. If phrase screening is intentionally not wanted, ignore this warning — the fact/metric checks still ran

Example fix

// before: file missing
$ node generate-pdf.mjs output/cv.html
⚠️  No config/cv-facts.json — forbidden/advisory phrase checks did not run.
// after: minimal config
echo '{"forbidden":[],"advisory":[]}' > config/cv-facts.json
$ node generate-pdf.mjs output/cv.html
✅ Fact check passed
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'fs';
if (!existsSync('config/cv-facts.json')) console.warn('cv-facts.json missing: forbidden/advisory phrase checks will be skipped');

Type guard

const factConfigExists = () => existsSync('config/cv-facts.json') && (() => { try { JSON.parse(readFileSync('config/cv-facts.json','utf8')); return true; } catch { return false; } })();

Prevention

When it happens

Trigger: PDF generation reaches the fact-check step, `await import('./verify-cv-facts.mjs')` succeeds, but assertFacts() returns factCheck.configMissing === true because config/cv-facts.json does not exist in the repo. Emitted at generate-pdf.mjs:1359 before the verdict output.

Common situations: Fresh clone or new project where config/cv-facts.json was never created (it is an optional gate); user deleted the config after too many advisory warnings; running generate-pdf.mjs outside the repo root so the relative path does not resolve.

Understand the failure class

Background: "Config file not found": what it means and how to fix it in docker-sync, Maven, Vagrant, Turborepo and other tools — this error's family across 60 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/68f9b29500933c52. Report an issue: GitHub.

Appendix: source

Thrown at generate-pdf.mjs:1360

  // carries the numbers a reader acts on, but the CV path enforced the gate only
  // as an instructed step in the mode prompts — so a programmatic caller (a
  // bridge, a script, a batch run) rendered inflated metrics in silence. Gate the
  // normalized HTML, which is the document that actually prints.
  if (!skipFactCheck && cvMarkdown) {
    // Imported lazily, INSIDE the guard. A static import is resolved at module
    // load whether or not this branch runs, and the page-budget/batch suites copy
    // generate-pdf.mjs alone into a temp workspace — a static import of a sibling
    // that isn't copied made every one of those suites die with
    // ERR_MODULE_NOT_FOUND before reaching the behaviour under test. Those
    // fixtures also ship no cv.md, so this branch is never entered there. If the
    // module is genuinely missing in a real workspace this throws and the render
    // fails, which is the correct direction to fail for a fact gate.
    const { assertFacts } = await import('./verify-cv-facts.mjs');
    const factCheck = assertFacts(html, { label: basename(inputPath) });
    // Ahead of the verdict, because it qualifies it: with no config the phrase
    // lists are empty, so a "passed" below covers metrics and facts only.
    if (factCheck.configMissing) {
      console.warn('⚠️  No config/cv-facts.json — forbidden/advisory phrase checks did not run.');
    }
    if (factCheck.verdict === 'warn') {
      console.warn(`⚠️  CV fact check warning: ${basename(inputPath)}`);
      for (const phrase of factCheck.warnings) console.warn(`  - advisory phrase: ${phrase}`);
    } else {
      console.log('✅ Fact check passed');
    }
  }

  return renderHtmlToPdf(html, outputPath, {
    format,
    baseDir: dirname(inputPath),
    reportNum,
    inputPath,
    maxPages,
    strictPages,
    styleTokens: readStyleTokens(resolve(workspaceRoot, 'config', 'profile.yml')),
  });

View on GitHub (pinned to aac998c7ed)