UNPKG

design-agent

Version:

Universal AI Design Review Agent - CLI tool for scanning code for design drift

240 lines (209 loc) 7.42 kB
export function generateSARIF(findings) { return { $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json', version: '2.1.0', runs: [{ tool: { driver: { name: 'Design Agent', version: '1.0.0', informationUri: 'https://github.com/ollyaston/design-agent', rules: generateRules(findings) } }, results: generateResults(findings), invocations: [{ executionSuccessful: true, startTimeUtc: new Date().toISOString(), endTimeUtc: new Date().toISOString() }] }] }; } function generateRules(findings) { const ruleIds = [...new Set(findings.map(f => f.kind))]; return ruleIds.map(ruleId => ({ id: ruleId, name: getRuleName(ruleId), shortDescription: { text: getRuleDescription(ruleId) }, fullDescription: { text: getRuleFullDescription(ruleId) }, help: { text: getRuleHelp(ruleId) }, properties: { category: getRuleCategory(ruleId), severity: getRuleSeverity(ruleId) } })); } function generateResults(findings) { return findings.map(finding => ({ ruleId: finding.kind, level: mapSeverityToLevel(finding.severity), message: { text: finding.msg }, locations: [{ physicalLocation: { artifactLocation: { uri: finding.file }, region: { startLine: finding.line, endLine: finding.line } } }], properties: { value: finding.value, autofix: finding.autofix || null } })); } function getRuleName(ruleId) { const ruleNames = { 'tokenDriftColor': 'Design Token Color Drift', 'spacingRawPx': 'Raw Pixel Spacing', 'inlineStyle': 'Inline Styles', 'utilities': 'Utility Class Issues', 'typography': 'Typography Issues', 'accessibility': 'Accessibility Issues', 'designSystem': 'Design System Violations', 'configError': 'Configuration Error', 'contrastFail': 'Color Contrast Failure' }; return ruleNames[ruleId] || ruleId; } function getRuleDescription(ruleId) { const descriptions = { 'tokenDriftColor': 'Hardcoded color values that should use design tokens', 'spacingRawPx': 'Raw pixel values that should use design tokens', 'inlineStyle': 'Inline styles that should use CSS classes or design tokens', 'utilities': 'Issues with utility classes or CSS utilities', 'typography': 'Typography-related design issues', 'accessibility': 'Accessibility compliance issues', 'designSystem': 'Design system consistency violations', 'configError': 'Configuration file parsing errors', 'contrastFail': 'Color contrast accessibility failures' }; return descriptions[ruleId] || 'Design review finding'; } function getRuleFullDescription(ruleId) { const fullDescriptions = { 'tokenDriftColor': 'This rule detects hardcoded color values (hex, rgb, etc.) that should be replaced with design tokens for consistency and maintainability.', 'spacingRawPx': 'This rule detects raw pixel values that should be replaced with design tokens for responsive design and consistency.', 'inlineStyle': 'This rule detects inline styles that should be moved to CSS classes or design tokens for better maintainability.', 'utilities': 'This rule detects issues with utility classes, such as redundant or conflicting classes.', 'typography': 'This rule detects typography-related issues, such as inconsistent font usage or missing design tokens.', 'accessibility': 'This rule detects accessibility issues, such as missing alt text or insufficient color contrast.', 'designSystem': 'This rule detects violations of design system guidelines and consistency rules.', 'configError': 'This rule detects errors in configuration files that prevent proper design token usage.', 'contrastFail': 'This rule detects color contrast issues that fail accessibility guidelines.' }; return fullDescriptions[ruleId] || 'Design review finding that requires attention.'; } function getRuleHelp(ruleId) { const helpTexts = { 'tokenDriftColor': 'Replace hardcoded color values with design tokens from your design system.', 'spacingRawPx': 'Replace raw pixel values with design tokens for consistent spacing.', 'inlineStyle': 'Move inline styles to CSS classes or use design tokens.', 'utilities': 'Review and optimize utility class usage.', 'typography': 'Use consistent typography tokens from your design system.', 'accessibility': 'Ensure proper accessibility attributes and contrast ratios.', 'designSystem': 'Follow your design system guidelines and naming conventions.', 'configError': 'Fix configuration file syntax or structure issues.', 'contrastFail': 'Improve color contrast to meet accessibility standards.' }; return helpTexts[ruleId] || 'Review the finding and apply appropriate fixes.'; } function getRuleCategory(ruleId) { const categories = { 'tokenDriftColor': 'design-tokens', 'spacingRawPx': 'design-tokens', 'inlineStyle': 'code-style', 'utilities': 'code-style', 'typography': 'design-tokens', 'accessibility': 'accessibility', 'designSystem': 'design-system', 'configError': 'configuration', 'contrastFail': 'accessibility' }; return categories[ruleId] || 'design-review'; } function getRuleSeverity(ruleId) { const severities = { 'tokenDriftColor': 'major', 'spacingRawPx': 'major', 'inlineStyle': 'minor', 'utilities': 'minor', 'typography': 'minor', 'accessibility': 'major', 'designSystem': 'minor', 'configError': 'minor', 'contrastFail': 'critical' }; return severities[ruleId] || 'minor'; } function mapSeverityToLevel(severity) { const levelMap = { 'critical': 'error', 'major': 'error', 'minor': 'warning', 'info': 'note' }; return levelMap[severity] || 'warning'; } export function generateSARIFSummary(findings) { const summary = { totalFindings: findings.length, bySeverity: {}, byRule: {}, byFile: {} }; // Count by severity findings.forEach(finding => { const severity = finding.severity || 'minor'; summary.bySeverity[severity] = (summary.bySeverity[severity] || 0) + 1; }); // Count by rule findings.forEach(finding => { const rule = finding.kind || 'unknown'; summary.byRule[rule] = (summary.byRule[rule] || 0) + 1; }); // Count by file findings.forEach(finding => { const file = finding.file || 'unknown'; summary.byFile[file] = (summary.byFile[file] || 0) + 1; }); return summary; } export function validateSARIF(sarif) { const errors = []; // Check required fields if (!sarif.$schema) { errors.push('Missing $schema field'); } if (!sarif.version) { errors.push('Missing version field'); } if (!sarif.runs || !Array.isArray(sarif.runs)) { errors.push('Missing or invalid runs array'); } if (sarif.runs && sarif.runs.length > 0) { const run = sarif.runs[0]; if (!run.tool || !run.tool.driver) { errors.push('Missing tool.driver in run'); } if (!run.results || !Array.isArray(run.results)) { errors.push('Missing or invalid results array'); } } return { valid: errors.length === 0, errors }; }