design-system-enforcer 1.0.3 → 1.0.4
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/dist/cli.cjs +381 -80
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +381 -80
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +71 -71
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs8 from "fs";
|
|
5
|
+
import path8 from "path";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
-
import
|
|
7
|
+
import pc3 from "picocolors";
|
|
8
8
|
|
|
9
9
|
// src/config/loader.ts
|
|
10
10
|
import fs3 from "fs";
|
|
@@ -2420,12 +2420,283 @@ function generateMarkdownRules(config) {
|
|
|
2420
2420
|
`;
|
|
2421
2421
|
}
|
|
2422
2422
|
|
|
2423
|
+
// src/doctor/index.ts
|
|
2424
|
+
function computeDoctorScore(result) {
|
|
2425
|
+
const categoryViolations = {
|
|
2426
|
+
typography: 0,
|
|
2427
|
+
geometry: 0,
|
|
2428
|
+
components: 0,
|
|
2429
|
+
colors: 0,
|
|
2430
|
+
wrapping: 0,
|
|
2431
|
+
tables: 0
|
|
2432
|
+
};
|
|
2433
|
+
const fileCountMap = {};
|
|
2434
|
+
result.violations.forEach((v) => {
|
|
2435
|
+
fileCountMap[v.file] = (fileCountMap[v.file] || 0) + 1;
|
|
2436
|
+
if (v.rule.startsWith("font-") || v.rule.includes("text-size") || v.rule.includes("uppercase")) {
|
|
2437
|
+
categoryViolations.typography++;
|
|
2438
|
+
} else if (v.rule.includes("wrapping")) {
|
|
2439
|
+
categoryViolations.wrapping++;
|
|
2440
|
+
} else if (v.rule.startsWith("control-") || v.rule.startsWith("corner-radius")) {
|
|
2441
|
+
categoryViolations.geometry++;
|
|
2442
|
+
} else if (v.rule.includes("color") || v.rule.includes("gradient") || v.rule.includes("blur")) {
|
|
2443
|
+
categoryViolations.colors++;
|
|
2444
|
+
} else if (v.rule.startsWith("table-")) {
|
|
2445
|
+
categoryViolations.tables++;
|
|
2446
|
+
} else {
|
|
2447
|
+
categoryViolations.components++;
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
const filesCount = Math.max(1, result.scannedFilesCount || 1);
|
|
2451
|
+
function calcScore(violations) {
|
|
2452
|
+
const penaltyPerViolation = 100 / (filesCount * 2);
|
|
2453
|
+
return Math.max(0, Math.round(100 - violations * penaltyPerViolation));
|
|
2454
|
+
}
|
|
2455
|
+
const categories = [
|
|
2456
|
+
{ name: "Typography Scale", category: "typography", violations: categoryViolations.typography, score: calcScore(categoryViolations.typography) },
|
|
2457
|
+
{ name: "Geometry & Radii", category: "geometry", violations: categoryViolations.geometry, score: calcScore(categoryViolations.geometry) },
|
|
2458
|
+
{ name: "Wrap Integrity", category: "wrapping", violations: categoryViolations.wrapping, score: calcScore(categoryViolations.wrapping) },
|
|
2459
|
+
{ name: "Component Inventory", category: "components", violations: categoryViolations.components, score: calcScore(categoryViolations.components) },
|
|
2460
|
+
{ name: "Color Purity", category: "colors", violations: categoryViolations.colors, score: calcScore(categoryViolations.colors) },
|
|
2461
|
+
{ name: "Table Invariants", category: "tables", violations: categoryViolations.tables, score: calcScore(categoryViolations.tables) }
|
|
2462
|
+
];
|
|
2463
|
+
const totalScore = Math.round(categories.reduce((acc, c) => acc + c.score, 0) / categories.length);
|
|
2464
|
+
let grade = "F";
|
|
2465
|
+
if (totalScore >= 98) grade = "A+";
|
|
2466
|
+
else if (totalScore >= 90) grade = "A";
|
|
2467
|
+
else if (totalScore >= 80) grade = "B";
|
|
2468
|
+
else if (totalScore >= 70) grade = "C";
|
|
2469
|
+
else if (totalScore >= 60) grade = "D";
|
|
2470
|
+
const fileBreakdown = Object.entries(fileCountMap).map(([file, violations]) => ({ file, violations })).sort((a, b) => b.violations - a.violations).slice(0, 5);
|
|
2471
|
+
return {
|
|
2472
|
+
overallScore: totalScore,
|
|
2473
|
+
grade,
|
|
2474
|
+
scannedFiles: filesCount,
|
|
2475
|
+
totalViolations: result.violations.length,
|
|
2476
|
+
categories,
|
|
2477
|
+
fileBreakdown
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// src/doctor/formatter.ts
|
|
2482
|
+
import pc2 from "picocolors";
|
|
2483
|
+
function renderProgressBar(percentage, width = 20) {
|
|
2484
|
+
const filledChars = Math.round(percentage / 100 * width);
|
|
2485
|
+
const emptyChars = width - filledChars;
|
|
2486
|
+
let colorFn = pc2.green;
|
|
2487
|
+
if (percentage < 70) colorFn = pc2.red;
|
|
2488
|
+
else if (percentage < 90) colorFn = pc2.yellow;
|
|
2489
|
+
const bar = colorFn("\u2588".repeat(filledChars)) + pc2.dim("\u2591".repeat(emptyChars));
|
|
2490
|
+
return `${bar} ${colorFn(percentage.toString().padStart(3) + "%")}`;
|
|
2491
|
+
}
|
|
2492
|
+
function formatDoctorOutput(report) {
|
|
2493
|
+
const lines = [];
|
|
2494
|
+
lines.push("");
|
|
2495
|
+
lines.push(pc2.bold(pc2.cyan("========================================================")));
|
|
2496
|
+
lines.push(pc2.bold(pc2.cyan(" DESIGN SYSTEM ENFORCER - HEALTH REPORT ")));
|
|
2497
|
+
lines.push(pc2.bold(pc2.cyan("========================================================")));
|
|
2498
|
+
lines.push("");
|
|
2499
|
+
let gradeColor = pc2.green;
|
|
2500
|
+
if (report.grade === "B") gradeColor = pc2.cyan;
|
|
2501
|
+
else if (report.grade === "C") gradeColor = pc2.yellow;
|
|
2502
|
+
else if (report.grade === "D" || report.grade === "F") gradeColor = pc2.red;
|
|
2503
|
+
lines.push(
|
|
2504
|
+
` ${pc2.bold("Overall Score:")} ${gradeColor(pc2.bold(report.overallScore + "/100"))} [${gradeColor(pc2.bold(report.grade))}]`
|
|
2505
|
+
);
|
|
2506
|
+
lines.push(` ${pc2.bold("Scanned Files:")} ${pc2.white(report.scannedFiles)}`);
|
|
2507
|
+
lines.push(` ${pc2.bold("Total Issues:")} ${report.totalViolations === 0 ? pc2.green("0") : pc2.red(report.totalViolations)}`);
|
|
2508
|
+
lines.push("");
|
|
2509
|
+
lines.push(pc2.bold(" Category Compliance:"));
|
|
2510
|
+
report.categories.forEach((cat) => {
|
|
2511
|
+
const paddedName = cat.name.padEnd(22, " ");
|
|
2512
|
+
const issueCount = cat.violations > 0 ? pc2.dim(`(${cat.violations} issues)`) : "";
|
|
2513
|
+
lines.push(` ${pc2.dim(paddedName)} ${renderProgressBar(cat.score)} ${issueCount}`);
|
|
2514
|
+
});
|
|
2515
|
+
lines.push("");
|
|
2516
|
+
if (report.fileBreakdown.length > 0) {
|
|
2517
|
+
lines.push(pc2.bold(pc2.yellow(" Top Offending Files:")));
|
|
2518
|
+
report.fileBreakdown.forEach((item, idx) => {
|
|
2519
|
+
lines.push(` ${idx + 1}. ${pc2.cyan(item.file)}: ${pc2.red(item.violations + " violations")}`);
|
|
2520
|
+
});
|
|
2521
|
+
lines.push("");
|
|
2522
|
+
lines.push(pc2.dim(" Run 'dse check' for detailed line-by-line violation reports."));
|
|
2523
|
+
lines.push(pc2.dim(" Run 'dse fix' to automatically remediate common violations."));
|
|
2524
|
+
} else {
|
|
2525
|
+
lines.push(pc2.bold(pc2.green(" \u2714 All design tokens and component invariants are 100% compliant!")));
|
|
2526
|
+
}
|
|
2527
|
+
lines.push("");
|
|
2528
|
+
return lines.join("\n");
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
// src/fixer/index.ts
|
|
2532
|
+
import fs6 from "fs";
|
|
2533
|
+
import path6 from "path";
|
|
2534
|
+
async function runDseFix(config, cwd = process.cwd()) {
|
|
2535
|
+
const scanResult = await runDseScan(config, cwd);
|
|
2536
|
+
const violationsByFile = {};
|
|
2537
|
+
scanResult.violations.forEach((v) => {
|
|
2538
|
+
if (!violationsByFile[v.file]) {
|
|
2539
|
+
violationsByFile[v.file] = [];
|
|
2540
|
+
}
|
|
2541
|
+
violationsByFile[v.file].push(v);
|
|
2542
|
+
});
|
|
2543
|
+
const details = [];
|
|
2544
|
+
let totalModifications = 0;
|
|
2545
|
+
for (const [relPath, violations] of Object.entries(violationsByFile)) {
|
|
2546
|
+
const fullPath = path6.resolve(cwd, relPath);
|
|
2547
|
+
if (!fs6.existsSync(fullPath)) continue;
|
|
2548
|
+
let content = fs6.readFileSync(fullPath, "utf-8");
|
|
2549
|
+
const originalContent = content;
|
|
2550
|
+
const fileFixes = [];
|
|
2551
|
+
if (violations.some((v) => v.rule === "no-wrapping-compact-elements")) {
|
|
2552
|
+
const before = content;
|
|
2553
|
+
content = content.replace(/(<(?:Button|IconButton|Chip|Badge)\b[^>]*className=["'`][^"'`]*?)\b(?:whitespace-normal|whitespace-pre-wrap|flex-wrap)\b([^"'`]*?["'`])/g, (match, prefix, suffix) => {
|
|
2554
|
+
return `${prefix}whitespace-nowrap shrink-0${suffix}`;
|
|
2555
|
+
});
|
|
2556
|
+
if (before !== content) {
|
|
2557
|
+
fileFixes.push("Replaced wrapping classes with 'whitespace-nowrap shrink-0' on compact controls");
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
if (violations.some((v) => v.rule === "no-raw-html-elements" && v.message.includes("<button>"))) {
|
|
2561
|
+
const before = content;
|
|
2562
|
+
content = content.replace(/<button(\s|>)/g, "<Button$1");
|
|
2563
|
+
content = content.replace(/<\/button>/g, "</Button>");
|
|
2564
|
+
if (before !== content) {
|
|
2565
|
+
if (!content.includes("import { Button }") && !content.includes("import Button") && !relPath.includes("components/ui/button")) {
|
|
2566
|
+
content = `import { Button } from "@/components/ui/button";
|
|
2567
|
+
` + content;
|
|
2568
|
+
}
|
|
2569
|
+
fileFixes.push("Upgraded raw <button> to <Button>");
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
if (violations.some((v) => v.rule === "no-raw-html-elements" && v.message.includes("<select>"))) {
|
|
2573
|
+
const before = content;
|
|
2574
|
+
content = content.replace(/<select(\s|>)/g, "<Dropdown$1");
|
|
2575
|
+
content = content.replace(/<\/select>/g, "</Dropdown>");
|
|
2576
|
+
if (before !== content) {
|
|
2577
|
+
if (!content.includes("import { Dropdown }") && !content.includes("import Dropdown") && !relPath.includes("components/ui/dropdown")) {
|
|
2578
|
+
content = `import { Dropdown } from "@/components/ui/dropdown";
|
|
2579
|
+
` + content;
|
|
2580
|
+
}
|
|
2581
|
+
fileFixes.push("Upgraded raw <select> to <Dropdown>");
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
if (violations.some((v) => v.rule === "no-raw-html-elements" && v.message.includes("<textarea>"))) {
|
|
2585
|
+
const before = content;
|
|
2586
|
+
content = content.replace(/<textarea(\s|>)/g, "<Textarea$1");
|
|
2587
|
+
content = content.replace(/<\/textarea>/g, "</Textarea>");
|
|
2588
|
+
if (before !== content) {
|
|
2589
|
+
if (!content.includes("import { Textarea }") && !content.includes("import Textarea") && !relPath.includes("components/ui/textarea")) {
|
|
2590
|
+
content = `import { Textarea } from "@/components/ui/textarea";
|
|
2591
|
+
` + content;
|
|
2592
|
+
}
|
|
2593
|
+
fileFixes.push("Upgraded raw <textarea> to <Textarea>");
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
if (violations.some((v) => v.rule === "no-arbitrary-text-size")) {
|
|
2597
|
+
const before = content;
|
|
2598
|
+
content = content.replace(/\btext-\[(?:13|13\.5|14|14\.5|15|15\.5)px\]\b/g, "text-sm");
|
|
2599
|
+
content = content.replace(/\btext-\[(?:11|11\.5|12|12\.5)px\]\b/g, "text-xs");
|
|
2600
|
+
content = content.replace(/\btext-\[(?:16|17)px\]\b/g, "text-base");
|
|
2601
|
+
if (before !== content) {
|
|
2602
|
+
fileFixes.push("Standardized arbitrary font sizes to compliant token scales");
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
if (violations.some((v) => v.rule === "no-backdrop-blur")) {
|
|
2606
|
+
const before = content;
|
|
2607
|
+
content = content.replace(/\bbackdrop-blur(?:-(?:none|sm|md|lg|xl|2xl|3xl))?\b/g, "");
|
|
2608
|
+
if (before !== content) {
|
|
2609
|
+
fileFixes.push("Removed non-compliant backdrop-blur classes");
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
if (content !== originalContent) {
|
|
2613
|
+
fs6.writeFileSync(fullPath, content, "utf-8");
|
|
2614
|
+
totalModifications += fileFixes.length;
|
|
2615
|
+
details.push({
|
|
2616
|
+
file: relPath,
|
|
2617
|
+
fixes: fileFixes
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
return {
|
|
2622
|
+
filesFixed: details.length,
|
|
2623
|
+
totalModifications,
|
|
2624
|
+
details
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// src/guardrails/index.ts
|
|
2629
|
+
import fs7 from "fs";
|
|
2630
|
+
import path7 from "path";
|
|
2631
|
+
function generateAgentInstructions(config) {
|
|
2632
|
+
const allowedRadii = config.tokens?.geometry?.allowedRadii?.join(", ") || "0px, 8px, 12px, 16px, 9999px";
|
|
2633
|
+
const maxControlHeight = config.tokens?.geometry?.maxControlHeight || 36;
|
|
2634
|
+
const bannedColors = config.tokens?.colors?.bannedColorFamilies?.join(", ") || "slate, gray, zinc, stone, etc.";
|
|
2635
|
+
return `<!-- BEGIN:DSE-AGENT-RULES -->
|
|
2636
|
+
# DESIGN SYSTEM ENFORCER (DSE) - MANDATORY RULES
|
|
2637
|
+
|
|
2638
|
+
You MUST adhere to these design system invariants for all UI generation:
|
|
2639
|
+
|
|
2640
|
+
1. COMPONENT INVARIANTS:
|
|
2641
|
+
- NEVER use raw HTML: <button>, <select>, <textarea>, <table>, <input type="checkbox">, <input type="radio">.
|
|
2642
|
+
- ALWAYS use standard primitives: <Button>, <Dropdown>, <Textarea>, <Table>, <Checkbox>, <Chip>.
|
|
2643
|
+
- Never create pseudo-chips using raw <div> or <span> with background fills and borders. Use <Chip>.
|
|
2644
|
+
|
|
2645
|
+
2. WRAP INTEGRITY (COMPACT ELEMENTS):
|
|
2646
|
+
- Interactive compact elements (<Button>, <Chip>, <Badge>, <TableHead>, tabs) MUST NOT wrap text.
|
|
2647
|
+
- ALWAYS include 'whitespace-nowrap shrink-0' on compact interactive components.
|
|
2648
|
+
|
|
2649
|
+
3. GEOMETRY & RADII:
|
|
2650
|
+
- Standard control height for buttons and inputs is strictly max ${maxControlHeight}px ('h-[36px]' or 'h-9').
|
|
2651
|
+
- Allowed corner radii tokens: ${allowedRadii}. Never use random arbitrary radii.
|
|
2652
|
+
|
|
2653
|
+
4. COLOR SCALE & PURITY:
|
|
2654
|
+
- Strictly neutral palette for base surfaces and borders ('neutral-100' through 'neutral-900').
|
|
2655
|
+
- Banned arbitrary color families: ${bannedColors}.
|
|
2656
|
+
- Never use arbitrary inline hex colors ('#1a2b3c') in utility classes. Use design system CSS variables ('var(--primary)').
|
|
2657
|
+
|
|
2658
|
+
5. BEFORE FINISHING UI EDITS:
|
|
2659
|
+
- Run 'npx dse check' to verify zero design system violations.
|
|
2660
|
+
<!-- END:DSE-AGENT-RULES -->
|
|
2661
|
+
`;
|
|
2662
|
+
}
|
|
2663
|
+
function installPreCommitHook(cwd = process.cwd()) {
|
|
2664
|
+
const gitDir = path7.resolve(cwd, ".git");
|
|
2665
|
+
if (!fs7.existsSync(gitDir)) {
|
|
2666
|
+
throw new Error("No .git repository found in this directory.");
|
|
2667
|
+
}
|
|
2668
|
+
const hooksDir = path7.resolve(gitDir, "hooks");
|
|
2669
|
+
if (!fs7.existsSync(hooksDir)) {
|
|
2670
|
+
fs7.mkdirSync(hooksDir, { recursive: true });
|
|
2671
|
+
}
|
|
2672
|
+
const hookPath = path7.resolve(hooksDir, "pre-commit");
|
|
2673
|
+
const hookScript = `#!/usr/bin/env sh
|
|
2674
|
+
# Design System Enforcer (DSE) Pre-Commit Hook
|
|
2675
|
+
echo "
|
|
2676
|
+
\u{1F50D} Running Design System Enforcer pre-commit check..."
|
|
2677
|
+
|
|
2678
|
+
if command -v npx >/dev/null 2>&1; then
|
|
2679
|
+
npx dse check --strict
|
|
2680
|
+
RESULT=$?
|
|
2681
|
+
if [ $RESULT -ne 0 ]; then
|
|
2682
|
+
echo "
|
|
2683
|
+
\u274C Commit rejected: Design System violations found."
|
|
2684
|
+
echo "\u{1F4A1} Run 'npx dse fix' or 'npx dse check' to review issues.
|
|
2685
|
+
"
|
|
2686
|
+
exit 1
|
|
2687
|
+
fi
|
|
2688
|
+
fi
|
|
2689
|
+
`;
|
|
2690
|
+
fs7.writeFileSync(hookPath, hookScript, { mode: 493 });
|
|
2691
|
+
return true;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2423
2694
|
// src/cli.ts
|
|
2424
2695
|
var program = new Command();
|
|
2425
|
-
program.name("dse").description("Design System Enforcer (DSE) - AST-based Linter and Scaffold Engine").version("1.0.
|
|
2696
|
+
program.name("dse").description("Design System Enforcer (DSE) - AST-based Linter and Scaffold Engine").version("1.0.4");
|
|
2426
2697
|
program.command("check [targetDir]", { isDefault: true }).description("Scan codebase and enforce Design System rules").option("-c, --config <path>", "Path to custom dse.config.js / dse.config.json").option("-f, --format <format>", "Output format: 'stylish' | 'json'", "stylish").option("--strict", "Treat warnings as errors", false).action(async (targetDirArg, options) => {
|
|
2427
2698
|
try {
|
|
2428
|
-
const cwd = targetDirArg ?
|
|
2699
|
+
const cwd = targetDirArg ? path8.resolve(process.cwd(), targetDirArg) : process.cwd();
|
|
2429
2700
|
const config = await loadConfig(cwd, options.config);
|
|
2430
2701
|
const result = await runDseScan(config, cwd);
|
|
2431
2702
|
if (options.format === "json") {
|
|
@@ -2438,76 +2709,125 @@ program.command("check [targetDir]", { isDefault: true }).description("Scan code
|
|
|
2438
2709
|
process.exit(1);
|
|
2439
2710
|
}
|
|
2440
2711
|
} catch (err) {
|
|
2441
|
-
console.error(
|
|
2712
|
+
console.error(pc3.red(`
|
|
2713
|
+
\u2716 Error running scan: ${err.message}`));
|
|
2714
|
+
process.exit(1);
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
program.command("doctor [targetDir]").description("Perform a visual Design System health audit and compute compliance score").option("-c, --config <path>", "Path to custom dse.config.js / dse.config.json").action(async (targetDirArg, options) => {
|
|
2718
|
+
try {
|
|
2719
|
+
const cwd = targetDirArg ? path8.resolve(process.cwd(), targetDirArg) : process.cwd();
|
|
2720
|
+
const config = await loadConfig(cwd, options.config);
|
|
2721
|
+
const result = await runDseScan(config, cwd);
|
|
2722
|
+
const report = computeDoctorScore(result);
|
|
2723
|
+
console.log(formatDoctorOutput(report));
|
|
2724
|
+
} catch (err) {
|
|
2725
|
+
console.error(pc3.red(`
|
|
2726
|
+
\u2716 Error generating health report: ${err.message}`));
|
|
2727
|
+
process.exit(1);
|
|
2728
|
+
}
|
|
2729
|
+
});
|
|
2730
|
+
program.command("fix [targetDir]").description("Automatically remediate common Design System violations in place").option("-c, --config <path>", "Path to custom dse.config.js / dse.config.json").action(async (targetDirArg, options) => {
|
|
2731
|
+
try {
|
|
2732
|
+
const cwd = targetDirArg ? path8.resolve(process.cwd(), targetDirArg) : process.cwd();
|
|
2733
|
+
const config = await loadConfig(cwd, options.config);
|
|
2734
|
+
console.log(pc3.cyan("\n\u{1F527} Running Design System Enforcer Auto-Fixer...\n"));
|
|
2735
|
+
const fixReport = await runDseFix(config, cwd);
|
|
2736
|
+
if (fixReport.filesFixed === 0) {
|
|
2737
|
+
console.log(pc3.green("\u2714 No auto-fixable violations found. Your codebase is clean!"));
|
|
2738
|
+
} else {
|
|
2739
|
+
console.log(pc3.bold(pc3.green(`\u2714 Successfully fixed ${fixReport.totalModifications} issues across ${fixReport.filesFixed} files:
|
|
2740
|
+
`)));
|
|
2741
|
+
fixReport.details.forEach((item) => {
|
|
2742
|
+
console.log(` ${pc3.bold(pc3.cyan(item.file))}:`);
|
|
2743
|
+
item.fixes.forEach((f) => console.log(` - ${pc3.dim(f)}`));
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
console.log("");
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
console.error(pc3.red(`
|
|
2749
|
+
\u2716 Error during auto-fix: ${err.message}`));
|
|
2442
2750
|
process.exit(1);
|
|
2443
2751
|
}
|
|
2444
2752
|
});
|
|
2445
|
-
program.command("
|
|
2753
|
+
program.command("detect [targetDir]").description("Inspect project architecture, framework, libraries, and design tokens autonomously").action((targetDirArg) => {
|
|
2754
|
+
const cwd = targetDirArg ? path8.resolve(process.cwd(), targetDirArg) : process.cwd();
|
|
2755
|
+
const info = detectProject(cwd);
|
|
2756
|
+
console.log(pc3.bold(pc3.cyan("\n\u{1F50D} DSE Autonomous Project Diagnostics\n")));
|
|
2757
|
+
console.log(` ${pc3.bold("Target Directory:")} ${info.root}`);
|
|
2758
|
+
console.log(` ${pc3.bold("Framework:")} ${pc3.green(info.framework)}`);
|
|
2759
|
+
console.log(` ${pc3.bold("Monorepo:")} ${info.isMonorepo ? pc3.yellow("Yes") : "No"}`);
|
|
2760
|
+
if (info.isMonorepo && info.monorepoWorkspaces.length > 0) {
|
|
2761
|
+
console.log(` ${pc3.bold("Workspaces:")} ${info.monorepoWorkspaces.join(", ")}`);
|
|
2762
|
+
}
|
|
2763
|
+
console.log(` ${pc3.bold("Libraries:")} ${info.libraries.length ? pc3.magenta(info.libraries.join(", ")) : "None"}`);
|
|
2764
|
+
console.log(` ${pc3.bold("Tailwind CSS:")} ${info.hasTailwind ? pc3.green(info.tailwindVersion || "Yes") : "No"}`);
|
|
2765
|
+
console.log(` ${pc3.bold("TypeScript:")} ${info.hasTypeScript ? "Yes" : "No"}`);
|
|
2766
|
+
console.log(` ${pc3.bold("Tokens File:")} ${info.tokensFile ? pc3.cyan(info.tokensFile) : pc3.dim("None (using preset defaults)")}`);
|
|
2767
|
+
console.log(` ${pc3.bold("Default Preset:")} ${pc3.yellow(info.preset)}`);
|
|
2768
|
+
console.log(` ${pc3.bold("Auto Includes:")} ${info.suggestedIncludes.join(", ")}`);
|
|
2769
|
+
console.log(` ${pc3.bold("Auto Ignores:")} ${info.suggestedIgnores.slice(0, 5).join(", ")} (+${Math.max(0, info.suggestedIgnores.length - 5)} more)`);
|
|
2770
|
+
console.log("");
|
|
2771
|
+
});
|
|
2772
|
+
program.command("export-agent").description("Export ultra-compact prompt rules for Cursor, Claude, Antigravity, or Copilot").option("-w, --write <file>", "Target file to write/append into (e.g. .cursorrules, AGENTS.md)").action(async (options) => {
|
|
2446
2773
|
const cwd = process.cwd();
|
|
2447
|
-
|
|
2448
|
-
const
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2774
|
+
const config = await loadConfig(cwd);
|
|
2775
|
+
const instructions = generateAgentInstructions(config);
|
|
2776
|
+
if (options.write) {
|
|
2777
|
+
const targetPath = path8.resolve(cwd, options.write);
|
|
2778
|
+
if (fs8.existsSync(targetPath)) {
|
|
2779
|
+
let existing = fs8.readFileSync(targetPath, "utf-8");
|
|
2780
|
+
const startTag = "<!-- BEGIN:DSE-AGENT-RULES -->";
|
|
2781
|
+
const endTag = "<!-- END:DSE-AGENT-RULES -->";
|
|
2782
|
+
if (existing.includes(startTag) && existing.includes(endTag)) {
|
|
2783
|
+
const regex = new RegExp(`${startTag}[\\s\\S]*?${endTag}`, "g");
|
|
2784
|
+
existing = existing.replace(regex, instructions.trim());
|
|
2785
|
+
} else {
|
|
2786
|
+
existing = `${existing.trim()}
|
|
2787
|
+
|
|
2788
|
+
${instructions.trim()}
|
|
2789
|
+
`;
|
|
2461
2790
|
}
|
|
2462
|
-
|
|
2463
|
-
console.
|
|
2791
|
+
fs8.writeFileSync(targetPath, existing, "utf-8");
|
|
2792
|
+
console.log(pc3.green(`\u2714 Updated AI guardrails in ${options.write}`));
|
|
2793
|
+
} else {
|
|
2794
|
+
fs8.writeFileSync(targetPath, instructions, "utf-8");
|
|
2795
|
+
console.log(pc3.green(`\u2714 Created ${options.write} with AI guardrails`));
|
|
2464
2796
|
}
|
|
2465
|
-
}
|
|
2466
|
-
if (options.withPrimitives) {
|
|
2467
|
-
console.log(pc2.cyan("Scaffolding primitives and theme tokens..."));
|
|
2468
|
-
const created = scaffoldAllPrimitives(cwd);
|
|
2469
|
-
created.forEach((file) => {
|
|
2470
|
-
console.log(pc2.green("\u2714 Created:"), pc2.dim(path6.relative(cwd, file)));
|
|
2471
|
-
});
|
|
2472
2797
|
} else {
|
|
2473
|
-
console.log(
|
|
2474
|
-
pc2.dim("\nTip: Run 'npx dse scaffold' to generate rule-compliant base primitives and tokens anytime.")
|
|
2475
|
-
);
|
|
2798
|
+
console.log(instructions);
|
|
2476
2799
|
}
|
|
2477
|
-
console.log(pc2.bold(pc2.green("\n\u2714 DSE setup complete! Run 'npm run dse' to verify your project.\n")));
|
|
2478
2800
|
});
|
|
2479
|
-
program.command("
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
);
|
|
2801
|
+
program.command("hook [action]").description("Install or manage git pre-commit hooks to block non-compliant commits").action((action) => {
|
|
2802
|
+
if (action === "install" || !action) {
|
|
2803
|
+
try {
|
|
2804
|
+
installPreCommitHook(process.cwd());
|
|
2805
|
+
console.log(pc3.bold(pc3.green("\n\u2714 Successfully installed pre-commit hook in .git/hooks/pre-commit!")));
|
|
2806
|
+
console.log(pc3.dim(" Every git commit will now be checked with 'dse check --strict'.\n"));
|
|
2807
|
+
} catch (err) {
|
|
2808
|
+
console.error(pc3.red(`
|
|
2809
|
+
\u2716 Could not install hook: ${err.message}
|
|
2810
|
+
`));
|
|
2490
2811
|
}
|
|
2491
2812
|
}
|
|
2492
|
-
console.log("");
|
|
2493
2813
|
});
|
|
2494
|
-
program.command("
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2814
|
+
program.command("init").description("Initialize DSE configuration in current repository").option("-p, --preset <name>", "Preset: berserk | recommended | minimal | python", "berserk").action((options) => {
|
|
2815
|
+
scaffoldConfig(process.cwd(), options.preset);
|
|
2816
|
+
});
|
|
2817
|
+
program.command("scaffold-tokens").description("Generate official design system tokens TypeScript/CSS file").action(() => {
|
|
2818
|
+
scaffoldTokens(process.cwd());
|
|
2819
|
+
});
|
|
2820
|
+
program.command("scaffold-primitives").description("Scaffold all canonical zero-compromise design system UI primitives").action(() => {
|
|
2821
|
+
scaffoldAllPrimitives(process.cwd());
|
|
2502
2822
|
});
|
|
2503
2823
|
program.command("export-rules").description("Export rules in Markdown format for AI coding assistants (AGENTS.md, .cursorrules, CLAUDE.md)").option("-w, --write <file>", "Write directly to target file (e.g. AGENTS.md)").action(async (options) => {
|
|
2504
2824
|
const cwd = process.cwd();
|
|
2505
2825
|
const config = await loadConfig(cwd);
|
|
2506
2826
|
const rulesMd = generateMarkdownRules(config);
|
|
2507
2827
|
if (options.write) {
|
|
2508
|
-
const targetFile =
|
|
2509
|
-
if (
|
|
2510
|
-
let existing =
|
|
2828
|
+
const targetFile = path8.resolve(cwd, options.write);
|
|
2829
|
+
if (fs8.existsSync(targetFile)) {
|
|
2830
|
+
let existing = fs8.readFileSync(targetFile, "utf-8");
|
|
2511
2831
|
const startTag = "<!-- BEGIN:design-system-enforcer-rules -->";
|
|
2512
2832
|
const endTag = "<!-- END:design-system-enforcer-rules -->";
|
|
2513
2833
|
if (existing.includes(startTag) && existing.includes(endTag)) {
|
|
@@ -2519,34 +2839,15 @@ program.command("export-rules").description("Export rules in Markdown format for
|
|
|
2519
2839
|
${rulesMd.trim()}
|
|
2520
2840
|
`;
|
|
2521
2841
|
}
|
|
2522
|
-
|
|
2523
|
-
console.log(
|
|
2842
|
+
fs8.writeFileSync(targetFile, existing, "utf-8");
|
|
2843
|
+
console.log(pc3.green(`\u2714 Updated rules in ${options.write}`));
|
|
2524
2844
|
} else {
|
|
2525
|
-
|
|
2526
|
-
console.log(
|
|
2845
|
+
fs8.writeFileSync(targetFile, rulesMd, "utf-8");
|
|
2846
|
+
console.log(pc3.green(`\u2714 Created ${options.write} with design system rules`));
|
|
2527
2847
|
}
|
|
2528
2848
|
} else {
|
|
2529
2849
|
console.log(rulesMd);
|
|
2530
2850
|
}
|
|
2531
2851
|
});
|
|
2532
|
-
program.command("detect [targetDir]").description("Inspect project architecture, framework, libraries, and design tokens autonomously").action((targetDirArg) => {
|
|
2533
|
-
const cwd = targetDirArg ? path6.resolve(process.cwd(), targetDirArg) : process.cwd();
|
|
2534
|
-
const info = detectProject(cwd);
|
|
2535
|
-
console.log(pc2.bold(pc2.cyan("\n\u{1F50D} DSE Autonomous Project Diagnostics\n")));
|
|
2536
|
-
console.log(` ${pc2.bold("Target Directory:")} ${info.root}`);
|
|
2537
|
-
console.log(` ${pc2.bold("Framework:")} ${pc2.green(info.framework)}`);
|
|
2538
|
-
console.log(` ${pc2.bold("Monorepo:")} ${info.isMonorepo ? pc2.yellow("Yes") : "No"}`);
|
|
2539
|
-
if (info.isMonorepo && info.monorepoWorkspaces.length > 0) {
|
|
2540
|
-
console.log(` ${pc2.bold("Workspaces:")} ${info.monorepoWorkspaces.join(", ")}`);
|
|
2541
|
-
}
|
|
2542
|
-
console.log(` ${pc2.bold("Libraries:")} ${info.libraries.length ? pc2.magenta(info.libraries.join(", ")) : "None"}`);
|
|
2543
|
-
console.log(` ${pc2.bold("Tailwind CSS:")} ${info.hasTailwind ? pc2.green(info.tailwindVersion || "Yes") : "No"}`);
|
|
2544
|
-
console.log(` ${pc2.bold("TypeScript:")} ${info.hasTypeScript ? "Yes" : "No"}`);
|
|
2545
|
-
console.log(` ${pc2.bold("Tokens File:")} ${info.tokensFile ? pc2.cyan(info.tokensFile) : pc2.dim("None (using preset defaults)")}`);
|
|
2546
|
-
console.log(` ${pc2.bold("Default Preset:")} ${pc2.yellow(info.preset)}`);
|
|
2547
|
-
console.log(` ${pc2.bold("Auto Includes:")} ${info.suggestedIncludes.join(", ")}`);
|
|
2548
|
-
console.log(` ${pc2.bold("Auto Ignores:")} ${info.suggestedIgnores.slice(0, 5).join(", ")} (+${Math.max(0, info.suggestedIgnores.length - 5)} more)`);
|
|
2549
|
-
console.log("");
|
|
2550
|
-
});
|
|
2551
2852
|
program.parse(process.argv);
|
|
2552
2853
|
//# sourceMappingURL=cli.js.map
|