210 lines
6.8 KiB
TypeScript
210 lines
6.8 KiB
TypeScript
#!/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=<domain> Validate specific deployment's locales
|
|
* --file=<path> 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<void> {
|
|
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);
|
|
});
|