@lifeonlars/prime-yggdrasil 0.2.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai/agents/accessibility.md +588 -0
- package/.ai/agents/block-composer.md +909 -0
- package/.ai/agents/drift-validator.md +784 -0
- package/.ai/agents/interaction-patterns.md +473 -0
- package/.ai/agents/primeflex-guard.md +815 -0
- package/.ai/agents/semantic-token-intent.md +739 -0
- package/README.md +138 -12
- package/cli/bin/yggdrasil.js +134 -0
- package/cli/commands/audit.js +447 -0
- package/cli/commands/init.js +288 -0
- package/cli/commands/validate.js +433 -0
- package/cli/rules/accessibility/index.js +15 -0
- package/cli/rules/accessibility/missing-alt-text.js +201 -0
- package/cli/rules/accessibility/missing-form-labels.js +238 -0
- package/cli/rules/interaction-patterns/focus-management.js +187 -0
- package/cli/rules/interaction-patterns/generic-copy.js +190 -0
- package/cli/rules/interaction-patterns/index.js +17 -0
- package/cli/rules/interaction-patterns/state-completeness.js +194 -0
- package/cli/templates/.ai/yggdrasil/README.md +308 -0
- package/docs/AESTHETICS.md +168 -0
- package/docs/PHASE-6-PLAN.md +456 -0
- package/docs/PRIMEFLEX-POLICY.md +737 -0
- package/package.json +6 -1
- package/docs/Fixes.md +0 -258
- package/docs/archive/README.md +0 -27
- package/docs/archive/SEMANTIC-MIGRATION-PLAN.md +0 -177
- package/docs/archive/YGGDRASIL_THEME.md +0 -264
- package/docs/archive/agentic_policy.md +0 -216
- package/docs/contrast-report.md +0 -9
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { validateCommand } from './validate.js';
|
|
3
|
+
|
|
4
|
+
// Import Phase 6 rules for autofix
|
|
5
|
+
import interactionPatternsRules from '../rules/interaction-patterns/index.js';
|
|
6
|
+
import accessibilityRules from '../rules/accessibility/index.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Audit command - Detailed analysis with autofix
|
|
10
|
+
*
|
|
11
|
+
* Provides detailed analysis of violations with autofix suggestions.
|
|
12
|
+
* Can automatically fix certain violations when --fix flag is used.
|
|
13
|
+
*
|
|
14
|
+
* For simple report-only validation, use the validate command.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Autofix strategies for each rule
|
|
19
|
+
*/
|
|
20
|
+
const AUTOFIXES = {
|
|
21
|
+
'no-utility-on-components': {
|
|
22
|
+
canAutoFix: true,
|
|
23
|
+
fix: (content, violation) => {
|
|
24
|
+
// Remove utility classes from PrimeReact components
|
|
25
|
+
// This is a simplified version - real implementation would use AST parsing
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
const line = lines[violation.line - 1];
|
|
28
|
+
|
|
29
|
+
// Extract utility classes to remove
|
|
30
|
+
const utilityMatch = violation.message.match(/classes? "([^"]+)"/);
|
|
31
|
+
if (utilityMatch) {
|
|
32
|
+
const utilities = utilityMatch[1].split(', ');
|
|
33
|
+
|
|
34
|
+
let fixedLine = line;
|
|
35
|
+
utilities.forEach(utility => {
|
|
36
|
+
// Remove the utility class
|
|
37
|
+
fixedLine = fixedLine.replace(new RegExp(`\\b${utility}\\b\\s*`, 'g'), '');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Clean up empty className
|
|
41
|
+
fixedLine = fixedLine.replace(/className=["'`]\s*["'`]/g, '');
|
|
42
|
+
|
|
43
|
+
lines[violation.line - 1] = fixedLine;
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return content;
|
|
48
|
+
},
|
|
49
|
+
explanation: 'Removes PrimeFlex utility classes from PrimeReact components. The theme handles all component styling.'
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
'no-tailwind': {
|
|
53
|
+
canAutoFix: true,
|
|
54
|
+
fix: (content, violation) => {
|
|
55
|
+
// Remove Tailwind classes
|
|
56
|
+
const lines = content.split('\n');
|
|
57
|
+
const line = lines[violation.line - 1];
|
|
58
|
+
|
|
59
|
+
const tailwindMatch = violation.message.match(/classes? "([^"]+)"/);
|
|
60
|
+
if (tailwindMatch) {
|
|
61
|
+
const tailwindClasses = tailwindMatch[1].split(', ');
|
|
62
|
+
|
|
63
|
+
let fixedLine = line;
|
|
64
|
+
tailwindClasses.forEach(cls => {
|
|
65
|
+
fixedLine = fixedLine.replace(new RegExp(`\\b${cls}\\b\\s*`, 'g'), '');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Clean up empty className
|
|
69
|
+
fixedLine = fixedLine.replace(/className=["'`]\s*["'`]/g, '');
|
|
70
|
+
|
|
71
|
+
lines[violation.line - 1] = fixedLine;
|
|
72
|
+
return lines.join('\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return content;
|
|
76
|
+
},
|
|
77
|
+
explanation: 'Removes Tailwind CSS classes. Use PrimeFlex for layout or semantic tokens for design.'
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
'no-hardcoded-colors': {
|
|
81
|
+
canAutoFix: false,
|
|
82
|
+
suggestions: [
|
|
83
|
+
'Replace with semantic token: var(--surface-neutral-primary) for backgrounds',
|
|
84
|
+
'Replace with semantic token: var(--text-neutral-default) for text',
|
|
85
|
+
'Replace with semantic token: var(--border-neutral-default) for borders',
|
|
86
|
+
'Consult .ai/yggdrasil/semantic-token-intent.md for complete token catalog'
|
|
87
|
+
],
|
|
88
|
+
explanation: 'Cannot auto-fix - requires semantic understanding of color intent. Use semantic tokens based on the element\'s purpose.'
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
'semantic-tokens-only': {
|
|
92
|
+
canAutoFix: false,
|
|
93
|
+
suggestions: [
|
|
94
|
+
'var(--blue-500) → var(--surface-brand-primary) or var(--text-state-interactive)',
|
|
95
|
+
'var(--green-500) → var(--surface-context-success) or var(--text-context-success)',
|
|
96
|
+
'var(--red-500) → var(--surface-context-danger) or var(--text-context-danger)',
|
|
97
|
+
'var(--gray-100) → var(--surface-neutral-secondary) or var(--text-neutral-subdued)',
|
|
98
|
+
'Consult .ai/yggdrasil/semantic-token-intent.md for complete mapping'
|
|
99
|
+
],
|
|
100
|
+
explanation: 'Cannot auto-fix - requires understanding of token purpose. Foundation tokens are for theme definition only.'
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
'valid-spacing': {
|
|
104
|
+
canAutoFix: true,
|
|
105
|
+
fix: (content, violation) => {
|
|
106
|
+
const lines = content.split('\n');
|
|
107
|
+
const line = lines[violation.line - 1];
|
|
108
|
+
|
|
109
|
+
// Extract off-grid value and nearest suggestion
|
|
110
|
+
const valueMatch = violation.message.match(/(\d+)px/);
|
|
111
|
+
const suggestionMatch = violation.suggestion.match(/(\d+)px/);
|
|
112
|
+
|
|
113
|
+
if (valueMatch && suggestionMatch) {
|
|
114
|
+
const oldValue = valueMatch[1];
|
|
115
|
+
const newValue = suggestionMatch[1];
|
|
116
|
+
|
|
117
|
+
// Replace the value
|
|
118
|
+
const fixedLine = line.replace(
|
|
119
|
+
new RegExp(`${oldValue}px`, 'g'),
|
|
120
|
+
`${newValue}px`
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
lines[violation.line - 1] = fixedLine;
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Handle invalid PrimeFlex classes
|
|
128
|
+
const classMatch = violation.message.match(/([pm][trblxy]?-\d+)/);
|
|
129
|
+
if (classMatch) {
|
|
130
|
+
const invalidClass = classMatch[1];
|
|
131
|
+
const number = parseInt(invalidClass.match(/\d+/)[0], 10);
|
|
132
|
+
const nearestValid = Math.min(8, Math.round(number / 4) * 4 / 4);
|
|
133
|
+
const validClass = invalidClass.replace(/\d+/, nearestValid);
|
|
134
|
+
|
|
135
|
+
const fixedLine = line.replace(invalidClass, validClass);
|
|
136
|
+
lines[violation.line - 1] = fixedLine;
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return content;
|
|
141
|
+
},
|
|
142
|
+
explanation: 'Rounds spacing values to nearest 4px grid value (0, 4, 8, 12, 16, 20, 24, 28, 32px).'
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Phase 6 Autofixes: Dynamically add from rule definitions
|
|
146
|
+
...Object.fromEntries(
|
|
147
|
+
Object.entries({ ...interactionPatternsRules, ...accessibilityRules }).map(([key, rule]) => [
|
|
148
|
+
key,
|
|
149
|
+
{
|
|
150
|
+
canAutoFix: typeof rule.autofix === 'function',
|
|
151
|
+
fix: (content, violation) => {
|
|
152
|
+
if (rule.autofix) {
|
|
153
|
+
const result = rule.autofix(content, violation);
|
|
154
|
+
return result.fixed ? result.content : content;
|
|
155
|
+
}
|
|
156
|
+
return content;
|
|
157
|
+
},
|
|
158
|
+
explanation: rule.autofix ? `${rule.description} (Phase 6 rule)` : 'Manual fix required'
|
|
159
|
+
}
|
|
160
|
+
])
|
|
161
|
+
)
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate detailed audit report
|
|
166
|
+
*/
|
|
167
|
+
function generateAuditReport(results) {
|
|
168
|
+
const report = {
|
|
169
|
+
summary: {
|
|
170
|
+
totalFiles: 0,
|
|
171
|
+
filesWithViolations: 0,
|
|
172
|
+
totalViolations: 0,
|
|
173
|
+
errors: 0,
|
|
174
|
+
warnings: 0,
|
|
175
|
+
autoFixable: 0
|
|
176
|
+
},
|
|
177
|
+
violations: [],
|
|
178
|
+
recommendations: []
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
Object.entries(results).forEach(([filePath, fileResults]) => {
|
|
182
|
+
report.summary.totalFiles++;
|
|
183
|
+
|
|
184
|
+
if (Object.keys(fileResults).length > 0 && !fileResults.error) {
|
|
185
|
+
report.summary.filesWithViolations++;
|
|
186
|
+
|
|
187
|
+
Object.entries(fileResults).forEach(([ruleId, result]) => {
|
|
188
|
+
result.violations.forEach(violation => {
|
|
189
|
+
report.summary.totalViolations++;
|
|
190
|
+
|
|
191
|
+
if (result.severity === 'error') {
|
|
192
|
+
report.summary.errors++;
|
|
193
|
+
} else {
|
|
194
|
+
report.summary.warnings++;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const autofix = AUTOFIXES[ruleId];
|
|
198
|
+
if (autofix?.canAutoFix) {
|
|
199
|
+
report.summary.autoFixable++;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
report.violations.push({
|
|
203
|
+
file: filePath,
|
|
204
|
+
rule: ruleId,
|
|
205
|
+
ruleName: result.rule,
|
|
206
|
+
severity: result.severity,
|
|
207
|
+
line: violation.line,
|
|
208
|
+
column: violation.column,
|
|
209
|
+
message: violation.message,
|
|
210
|
+
suggestion: violation.suggestion,
|
|
211
|
+
autoFixable: autofix?.canAutoFix || false,
|
|
212
|
+
autoFixExplanation: autofix?.explanation,
|
|
213
|
+
manualFixSuggestions: autofix?.suggestions
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Generate recommendations
|
|
221
|
+
const violationsByRule = {};
|
|
222
|
+
report.violations.forEach(v => {
|
|
223
|
+
violationsByRule[v.rule] = (violationsByRule[v.rule] || 0) + 1;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const sortedRules = Object.entries(violationsByRule)
|
|
227
|
+
.sort((a, b) => b[1] - a[1])
|
|
228
|
+
.slice(0, 3);
|
|
229
|
+
|
|
230
|
+
if (sortedRules.length > 0) {
|
|
231
|
+
report.recommendations.push({
|
|
232
|
+
priority: 'high',
|
|
233
|
+
title: 'Focus on Most Common Violations',
|
|
234
|
+
description: `Top violations: ${sortedRules.map(([rule, count]) => `${rule} (${count})`).join(', ')}`
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (report.summary.autoFixable > 0) {
|
|
239
|
+
report.recommendations.push({
|
|
240
|
+
priority: 'high',
|
|
241
|
+
title: 'Run Autofix',
|
|
242
|
+
description: `${report.summary.autoFixable} violations can be automatically fixed. Run: yggdrasil audit --fix`
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (violationsByRule['no-hardcoded-colors'] || violationsByRule['semantic-tokens-only']) {
|
|
247
|
+
report.recommendations.push({
|
|
248
|
+
priority: 'medium',
|
|
249
|
+
title: 'Review Semantic Token Guide',
|
|
250
|
+
description: 'Read .ai/yggdrasil/semantic-token-intent.md for token selection guidance'
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return report;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Format audit report
|
|
259
|
+
*/
|
|
260
|
+
function formatAuditReport(report, format = 'cli') {
|
|
261
|
+
if (format === 'json') {
|
|
262
|
+
return JSON.stringify(report, null, 2);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (format === 'markdown') {
|
|
266
|
+
let md = '# Yggdrasil Design System Audit Report\n\n';
|
|
267
|
+
md += '## Summary\n\n';
|
|
268
|
+
md += `- **Total Files Scanned:** ${report.summary.totalFiles}\n`;
|
|
269
|
+
md += `- **Files with Violations:** ${report.summary.filesWithViolations}\n`;
|
|
270
|
+
md += `- **Total Violations:** ${report.summary.totalViolations}\n`;
|
|
271
|
+
md += `- **Errors:** ${report.summary.errors}\n`;
|
|
272
|
+
md += `- **Warnings:** ${report.summary.warnings}\n`;
|
|
273
|
+
md += `- **Auto-Fixable:** ${report.summary.autoFixable}\n\n`;
|
|
274
|
+
|
|
275
|
+
if (report.recommendations.length > 0) {
|
|
276
|
+
md += '## Recommendations\n\n';
|
|
277
|
+
report.recommendations.forEach(rec => {
|
|
278
|
+
const icon = rec.priority === 'high' ? '🔴' : '🟡';
|
|
279
|
+
md += `${icon} **${rec.title}**\n`;
|
|
280
|
+
md += ` ${rec.description}\n\n`;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
md += '## Violations\n\n';
|
|
285
|
+
report.violations.forEach(v => {
|
|
286
|
+
const icon = v.severity === 'error' ? '❌' : '⚠️';
|
|
287
|
+
md += `${icon} **${v.ruleName}** in \`${v.file}:${v.line}\`\n`;
|
|
288
|
+
md += ` ${v.message}\n`;
|
|
289
|
+
md += ` 💡 ${v.suggestion}\n`;
|
|
290
|
+
if (v.autoFixable) {
|
|
291
|
+
md += ` ✨ Auto-fixable\n`;
|
|
292
|
+
}
|
|
293
|
+
md += '\n';
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return md;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// CLI format
|
|
300
|
+
let output = '\n';
|
|
301
|
+
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
|
|
302
|
+
output += '📊 Yggdrasil Design System Audit Report\n';
|
|
303
|
+
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n';
|
|
304
|
+
|
|
305
|
+
output += '📈 Summary:\n';
|
|
306
|
+
output += ` Total Files Scanned: ${report.summary.totalFiles}\n`;
|
|
307
|
+
output += ` Files with Violations: ${report.summary.filesWithViolations}\n`;
|
|
308
|
+
output += ` Total Violations: ${report.summary.totalViolations}\n`;
|
|
309
|
+
output += ` ❌ Errors: ${report.summary.errors}\n`;
|
|
310
|
+
output += ` ⚠️ Warnings: ${report.summary.warnings}\n`;
|
|
311
|
+
output += ` ✨ Auto-Fixable: ${report.summary.autoFixable}\n\n`;
|
|
312
|
+
|
|
313
|
+
if (report.recommendations.length > 0) {
|
|
314
|
+
output += '💡 Recommendations:\n\n';
|
|
315
|
+
report.recommendations.forEach(rec => {
|
|
316
|
+
const icon = rec.priority === 'high' ? '🔴' : '🟡';
|
|
317
|
+
output += ` ${icon} ${rec.title}\n`;
|
|
318
|
+
output += ` ${rec.description}\n\n`;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (report.violations.length > 0) {
|
|
323
|
+
output += '📋 Violations by File:\n\n';
|
|
324
|
+
|
|
325
|
+
const violationsByFile = {};
|
|
326
|
+
report.violations.forEach(v => {
|
|
327
|
+
if (!violationsByFile[v.file]) {
|
|
328
|
+
violationsByFile[v.file] = [];
|
|
329
|
+
}
|
|
330
|
+
violationsByFile[v.file].push(v);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
Object.entries(violationsByFile).forEach(([file, violations]) => {
|
|
334
|
+
output += `📄 ${file}\n`;
|
|
335
|
+
|
|
336
|
+
violations.forEach(v => {
|
|
337
|
+
const icon = v.severity === 'error' ? '❌' : '⚠️';
|
|
338
|
+
const fixIcon = v.autoFixable ? ' ✨' : '';
|
|
339
|
+
output += ` ${icon}${fixIcon} ${v.ruleName} (line ${v.line})\n`;
|
|
340
|
+
output += ` ${v.message}\n`;
|
|
341
|
+
output += ` 💡 ${v.suggestion}\n`;
|
|
342
|
+
|
|
343
|
+
if (v.autoFixable) {
|
|
344
|
+
output += ` ✨ ${v.autoFixExplanation}\n`;
|
|
345
|
+
} else if (v.manualFixSuggestions) {
|
|
346
|
+
output += ` 📝 Manual fix suggestions:\n`;
|
|
347
|
+
v.manualFixSuggestions.forEach(suggestion => {
|
|
348
|
+
output += ` • ${suggestion}\n`;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
output += '\n';
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n';
|
|
357
|
+
|
|
358
|
+
return output;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Apply autofixes to files
|
|
363
|
+
*/
|
|
364
|
+
function applyAutofixes(results) {
|
|
365
|
+
const fixed = {};
|
|
366
|
+
|
|
367
|
+
Object.entries(results).forEach(([filePath, fileResults]) => {
|
|
368
|
+
let content = readFileSync(filePath, 'utf8');
|
|
369
|
+
let modified = false;
|
|
370
|
+
|
|
371
|
+
Object.entries(fileResults).forEach(([ruleId, result]) => {
|
|
372
|
+
const autofix = AUTOFIXES[ruleId];
|
|
373
|
+
|
|
374
|
+
if (autofix?.canAutoFix) {
|
|
375
|
+
result.violations.forEach(violation => {
|
|
376
|
+
const newContent = autofix.fix(content, violation);
|
|
377
|
+
if (newContent !== content) {
|
|
378
|
+
content = newContent;
|
|
379
|
+
modified = true;
|
|
380
|
+
|
|
381
|
+
if (!fixed[filePath]) {
|
|
382
|
+
fixed[filePath] = [];
|
|
383
|
+
}
|
|
384
|
+
fixed[filePath].push({
|
|
385
|
+
rule: result.rule,
|
|
386
|
+
line: violation.line,
|
|
387
|
+
message: violation.message
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
if (modified) {
|
|
395
|
+
writeFileSync(filePath, content, 'utf8');
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
return fixed;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Main audit command
|
|
404
|
+
*/
|
|
405
|
+
export async function auditCommand(options = {}) {
|
|
406
|
+
const fix = options.fix || false;
|
|
407
|
+
const format = options.format || 'cli';
|
|
408
|
+
|
|
409
|
+
console.log(`
|
|
410
|
+
🌳 Yggdrasil Design System Audit
|
|
411
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
412
|
+
|
|
413
|
+
🔍 Running comprehensive analysis...
|
|
414
|
+
`);
|
|
415
|
+
|
|
416
|
+
// Run validation
|
|
417
|
+
const results = await validateCommand({ ...options, format: 'json' });
|
|
418
|
+
|
|
419
|
+
// Generate audit report
|
|
420
|
+
const report = generateAuditReport(results);
|
|
421
|
+
|
|
422
|
+
// Apply fixes if requested
|
|
423
|
+
if (fix && report.summary.autoFixable > 0) {
|
|
424
|
+
console.log('✨ Applying automatic fixes...\n');
|
|
425
|
+
const fixed = applyAutofixes(results);
|
|
426
|
+
|
|
427
|
+
console.log(`✅ Fixed ${Object.keys(fixed).length} files:\n`);
|
|
428
|
+
Object.entries(fixed).forEach(([file, fixes]) => {
|
|
429
|
+
console.log(` 📄 ${file}`);
|
|
430
|
+
fixes.forEach(f => {
|
|
431
|
+
console.log(` ✓ ${f.rule} (line ${f.line})`);
|
|
432
|
+
});
|
|
433
|
+
console.log('');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Re-run validation to show remaining violations
|
|
437
|
+
console.log('🔍 Re-validating after fixes...\n');
|
|
438
|
+
const newResults = await validateCommand({ ...options, format: 'json' });
|
|
439
|
+
const newReport = generateAuditReport(newResults);
|
|
440
|
+
|
|
441
|
+
console.log(formatAuditReport(newReport, format));
|
|
442
|
+
} else {
|
|
443
|
+
console.log(formatAuditReport(report, format));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return report;
|
|
447
|
+
}
|