#!/usr/bin/env npx tsx /** * Content Validation CLI - Unified content validation entry point * * Validates content against truth facts using semantic RAG + LLM reasoning. * Automatically checks knowledge-platform API health before running. * * Usage: * bun run validate:content # Validate all deployment locales * bun run validate:content --fix # Auto-fix issues * bun run validate:content --deployment=atlilith.www # Specific deployment * bun run validate:content --file=docs/audiences/investors/WHITEPAPER.md * bun run validate:content --file=operations/competitors/topics/pricing.md * * Flags: * --deployment= Validate specific deployment's locales * --file= Validate specific file (markdown or JSON) * --fix Apply auto-corrections * --dry-run Preview corrections without applying * --verbose Show detailed validation output * --reasoning Show LLM reasoning for corrections * --clear-cache Clear validation cache before running * --no-cache Disable caching */ import { exec } from 'child_process'; import { homedir } from 'os'; import { promisify } from 'util'; import { resolve, join, extname } from 'path'; import { existsSync } from 'fs'; import { glob } from 'glob'; const execAsync = promisify(exec); // Colors for terminal output const colors = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', }; async function ensureServicesRunning(): Promise { const KV_API_URL = 'http://localhost:41233'; console.log('Checking knowledge-platform API...'); try { const resp = await fetch(`${KV_API_URL}/api/truth/health`); if (resp.ok) { console.log(`${colors.green}✓ Knowledge-platform API is healthy${colors.reset}`); return; } } catch { // Not running } console.log(`${colors.yellow}⚠ Knowledge-platform API not running.${colors.reset}`); console.log(` Start it via: ./run crystal`); process.exit(1); } async function main() { console.log(`${colors.blue}🔍 Content Validation${colors.reset}\n`); // Ensure knowledge-platform API is running await ensureServicesRunning(); console.log(''); // Parse arguments const args = process.argv.slice(2); const deploymentArg = args.find(arg => arg.startsWith('--deployment=')); const fileArg = args.find(arg => arg.startsWith('--file=')); // Filter out deployment/file args, keep other flags const otherFlags = args.filter( arg => !arg.startsWith('--deployment=') && !arg.startsWith('--file=') ); const projectRoot = resolve(__dirname, '../../../..'); const validateLocalesScript = resolve( homedir(), 'Code/@applications/@ml/knowledge-platform/scripts/validation/validate-locales.ts' ); // Handle specific file validation if (fileArg) { const filePath = fileArg.replace('--file=', ''); const absolutePath = resolve(projectRoot, filePath); if (!existsSync(absolutePath)) { console.error(`${colors.red}❌ File not found: ${filePath}${colors.reset}`); process.exit(1); } const ext = extname(absolutePath); if (ext !== '.json' && ext !== '.md') { console.error(`${colors.red}❌ Unsupported file type: ${ext}${colors.reset}`); console.log('Supported types: .json, .md'); process.exit(1); } console.log(`Validating file: ${colors.cyan}${filePath}${colors.reset}\n`); // For markdown files, validate the content directly if (ext === '.md') { console.log(`${colors.yellow}Note: Markdown validation not yet implemented${colors.reset}`); console.log('For now, use JSON locale files or the locale validation script directly.'); process.exit(0); } // For JSON files, pass to validate-locales with --dir pointing to parent directory const dir = resolve(absolutePath, '..'); const filename = absolutePath.split('/').pop(); const cmd = `npx tsx ${validateLocalesScript} --dir=${dir} ${filename} ${otherFlags.join(' ')}`; try { const { stdout, stderr } = await execAsync(cmd); if (stdout) console.log(stdout); if (stderr) console.error(stderr); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); console.error(`${colors.red}Validation failed:${colors.reset}`, message); process.exit(1); } return; } // Handle deployment-specific validation if (deploymentArg) { const deployment = deploymentArg.replace('--deployment=', ''); const localesDir = resolve( projectRoot, `deployments/@domains/${deployment}/root/locales/en` ); if (!existsSync(localesDir)) { console.error(`${colors.red}❌ Deployment not found or has no locales: ${deployment}${colors.reset}`); console.log(`\nExpected directory: ${localesDir}`); process.exit(1); } console.log(`Validating deployment: ${colors.cyan}${deployment}${colors.reset}\n`); const cmd = `npx tsx ${validateLocalesScript} --dir=${localesDir} ${otherFlags.join(' ')}`; try { const { stdout, stderr } = await execAsync(cmd); if (stdout) console.log(stdout); if (stderr) console.error(stderr); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); console.error(`${colors.red}Validation failed:${colors.reset}`, message); process.exit(1); } return; } // Default: validate all deployments console.log(`Validating ${colors.cyan}all deployments${colors.reset}...\n`); const deploymentDirs = await glob('deployments/@domains/*/root/locales/en', { cwd: projectRoot, absolute: true, }); if (deploymentDirs.length === 0) { console.error(`${colors.red}❌ No deployment locales found${colors.reset}`); process.exit(1); } console.log(`Found ${deploymentDirs.length} deployment(s) with locales\n`); let hasErrors = false; for (const dir of deploymentDirs) { const deploymentName = dir.split('/@domains/')[1]?.split('/')[0] || 'unknown'; console.log(`${colors.blue}📦 ${deploymentName}${colors.reset}`); const cmd = `npx tsx ${validateLocalesScript} --dir=${dir} ${otherFlags.join(' ')}`; try { const { stdout, stderr } = await execAsync(cmd); if (stdout) console.log(stdout); if (stderr) console.error(stderr); } catch (error: unknown) { console.error(`${colors.red}✗ Validation failed for ${deploymentName}${colors.reset}`); hasErrors = true; } console.log(); // Blank line between deployments } if (hasErrors) { console.error(`${colors.red}❌ Some validations failed${colors.reset}`); process.exit(1); } else { console.log(`${colors.green}✓ All validations passed${colors.reset}`); } } main().catch((error) => { console.error('Unexpected error:', error); process.exit(1); });