design-agent
Version:
Universal AI Design Review Agent - CLI tool for scanning code for design drift
281 lines (230 loc) • 7.53 kB
JavaScript
/**
* Validation utilities for design agent
* Validates configuration, inputs, and outputs
*/
export function validateConfig(config) {
const errors = [];
const warnings = [];
// Required fields
if (!config.project) {
errors.push('Project name is required');
}
if (!config.code || !config.code.include) {
errors.push('Code include paths are required');
}
if (!config.designSystem || !config.designSystem.sources) {
errors.push('Design system sources are required');
}
// Validate design system sources
if (config.designSystem?.sources) {
for (const source of config.designSystem.sources) {
if (!source.kind) {
errors.push('Design system source missing kind');
}
if (!source.path) {
errors.push('Design system source missing path');
}
}
}
// Validate checks configuration
if (config.checks) {
const validChecks = [
'tokenDrift', 'utilities', 'inlineStyles', 'typography',
'contrastStatic', 'storybookProps', 'accessibility', 'security'
];
for (const check of Object.keys(config.checks)) {
if (!validChecks.includes(check)) {
warnings.push(`Unknown check type: ${check}`);
}
}
}
// Validate integrations
if (config.integrations) {
if (config.integrations.slackWebhook && !isValidUrl(config.integrations.slackWebhook)) {
errors.push('Invalid Slack webhook URL');
}
}
return { errors, warnings };
}
export function validateFindings(findings) {
const errors = [];
for (const finding of findings) {
// Required fields
if (!finding.file) {
errors.push('Finding missing file path');
}
if (!finding.line) {
errors.push('Finding missing line number');
}
if (!finding.kind) {
errors.push('Finding missing kind');
}
if (!finding.msg) {
errors.push('Finding missing message');
}
// Validate severity
const validSeverities = ['critical', 'major', 'minor'];
if (finding.severity && !validSeverities.includes(finding.severity)) {
errors.push(`Invalid severity: ${finding.severity}`);
}
// Validate autofix if present
if (finding.autofix) {
if (!finding.autofix.before || !finding.autofix.after) {
errors.push('Autofix missing before/after values');
}
if (finding.autofix.confidence && (finding.autofix.confidence < 0 || finding.autofix.confidence > 100)) {
errors.push('Autofix confidence must be between 0 and 100');
}
}
}
return errors;
}
export function validateEnvironmentVariables() {
const errors = [];
const warnings = [];
// Check for required environment variables
const required = ['NODE_ENV'];
for (const env of required) {
if (!process.env[env]) {
errors.push(`Required environment variable missing: ${env}`);
}
}
// Check for optional but recommended environment variables
const recommended = ['ANTHROPIC_API_KEY', 'SLACK_WEBHOOK_URL'];
for (const env of recommended) {
if (!process.env[env]) {
warnings.push(`Recommended environment variable not set: ${env}`);
}
}
// Validate API keys if present
if (process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_API_KEY.startsWith('sk-ant-')) {
errors.push('Invalid Anthropic API key format');
}
if (process.env.OPENAI_API_KEY && !process.env.OPENAI_API_KEY.startsWith('sk-')) {
errors.push('Invalid OpenAI API key format');
}
return { errors, warnings };
}
export function validateFilePaths(paths) {
const errors = [];
const warnings = [];
for (const path of paths) {
// Check if path exists
try {
const fs = require('fs');
if (!fs.existsSync(path)) {
warnings.push(`Path does not exist: ${path}`);
}
} catch (error) {
// Skip if fs is not available
}
// Check for dangerous paths
if (path.includes('..') || path.includes('~')) {
errors.push(`Potentially dangerous path: ${path}`);
}
// Check for system directories
const systemDirs = ['/etc', '/usr', '/bin', '/sbin', '/var'];
for (const dir of systemDirs) {
if (path.startsWith(dir)) {
errors.push(`Accessing system directory: ${path}`);
}
}
}
return { errors, warnings };
}
export function validateOutputFormat(format) {
const validFormats = ['markdown', 'json', 'sarif', 'html'];
if (!validFormats.includes(format)) {
return `Invalid output format: ${format}. Valid formats: ${validFormats.join(', ')}`;
}
return null;
}
export function validateSeverity(severity) {
const validSeverities = ['critical', 'major', 'minor'];
if (!validSeverities.includes(severity)) {
return `Invalid severity: ${severity}. Valid severities: ${validSeverities.join(', ')}`;
}
return null;
}
export function validateAdapter(adapter) {
const validAdapters = [
'tailwind', 'tokens', 'storybook', 'vercel', 'openai',
'anthropic', 'langchain', 'llamaindex', 'aws', 'docker',
'vite', 'nodejs'
];
if (!validAdapters.includes(adapter)) {
return `Invalid adapter: ${adapter}. Valid adapters: ${validAdapters.join(', ')}`;
}
return null;
}
export function generateValidationReport(validationResults) {
let report = '## Validation Report\n\n';
if (validationResults.errors.length === 0 && validationResults.warnings.length === 0) {
report += '✅ All validations passed!\n';
return report;
}
if (validationResults.errors.length > 0) {
report += '### Errors (Must Fix)\n\n';
validationResults.errors.forEach(error => {
report += `- ❌ ${error}\n`;
});
report += '\n';
}
if (validationResults.warnings.length > 0) {
report += '### Warnings (Consider Fixing)\n\n';
validationResults.warnings.forEach(warning => {
report += `- ⚠️ ${warning}\n`;
});
}
return report;
}
function isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}
export function sanitizeInput(input) {
if (typeof input !== 'string') {
return input;
}
// Remove potentially dangerous characters
return input
.replace(/[<>]/g, '') // Remove angle brackets
.replace(/javascript:/gi, '') // Remove javascript: protocol
.replace(/on\w+=/gi, '') // Remove event handlers
.trim();
}
export function validateJsonSchema(data, schema) {
const errors = [];
// Basic schema validation
if (schema.required) {
for (const field of schema.required) {
if (!(field in data)) {
errors.push(`Required field missing: ${field}`);
}
}
}
if (schema.properties) {
for (const [field, rules] of Object.entries(schema.properties)) {
if (field in data) {
const value = data[field];
if (rules.type && typeof value !== rules.type) {
errors.push(`Field ${field} must be of type ${rules.type}`);
}
if (rules.enum && !rules.enum.includes(value)) {
errors.push(`Field ${field} must be one of: ${rules.enum.join(', ')}`);
}
if (rules.minLength && value.length < rules.minLength) {
errors.push(`Field ${field} must be at least ${rules.minLength} characters`);
}
if (rules.maxLength && value.length > rules.maxLength) {
errors.push(`Field ${field} must be at most ${rules.maxLength} characters`);
}
}
}
}
return errors;
}