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.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import fs6 from "fs";
5
- import path6 from "path";
4
+ import fs8 from "fs";
5
+ import path8 from "path";
6
6
  import { Command } from "commander";
7
- import pc2 from "picocolors";
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.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 ? path6.resolve(process.cwd(), targetDirArg) : process.cwd();
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(pc2.red("Error running DSE scan:"), err);
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("init").description("Initialize DSE configuration and optionally scaffold base primitives").option("--preset <name>", "Preset to use: 'berserk' | 'recommended' | 'python'").option("--with-primitives", "Scaffold full base primitives and tokens suite", false).action((options) => {
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
- console.log(pc2.bold(pc2.cyan("\nInitializing Design System Enforcer (DSE)...")));
2448
- const isPythonProject = fs6.existsSync(path6.resolve(cwd, "requirements.txt")) || fs6.existsSync(path6.resolve(cwd, "pyproject.toml")) || fs6.existsSync(path6.resolve(cwd, ".streamlit"));
2449
- const preset = options.preset || (isPythonProject ? "python" : "berserk");
2450
- const configPath = scaffoldConfig(cwd, preset);
2451
- console.log(pc2.green("\u2714 Created config:"), pc2.bold(path6.relative(cwd, configPath)), pc2.dim(`(preset: ${preset})`));
2452
- const pkgPath = path6.resolve(cwd, "package.json");
2453
- if (fs6.existsSync(pkgPath)) {
2454
- try {
2455
- const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
2456
- pkg.scripts = pkg.scripts || {};
2457
- if (!pkg.scripts.dse) {
2458
- pkg.scripts.dse = "dse";
2459
- fs6.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2460
- console.log(pc2.green("\u2714 Added script 'dse' to package.json"));
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
- } catch (err) {
2463
- console.warn(pc2.yellow("\u26A0 Could not update package.json:"), err);
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("add <components...>").description("Scaffold specific rule-compliant design system primitives (button, chip, input, table, tabs, card, checkbox, dropdown, skeleton)").action((components) => {
2480
- const cwd = process.cwd();
2481
- console.log(pc2.bold(pc2.cyan("\nScaffolding requested components...")));
2482
- for (const comp of components) {
2483
- const filePath = scaffoldComponent(comp, cwd);
2484
- if (filePath) {
2485
- console.log(pc2.green("\u2714 Created:"), pc2.bold(path6.relative(cwd, filePath)));
2486
- } else {
2487
- console.log(
2488
- pc2.yellow(`\u26A0 Unknown component '${comp}'. Available: ${Object.keys(COMPONENT_TEMPLATES).join(", ")}`)
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("scaffold").description("Scaffold all base primitives and HSL tokens into src/components/ui/ and src/lib/").action(() => {
2495
- const cwd = process.cwd();
2496
- console.log(pc2.bold(pc2.cyan("\nScaffolding full design system suite...")));
2497
- const created = scaffoldAllPrimitives(cwd);
2498
- created.forEach((file) => {
2499
- console.log(pc2.green("\u2714 Created:"), pc2.bold(path6.relative(cwd, file)));
2500
- });
2501
- console.log(pc2.bold(pc2.green("\n\u2714 All design system primitives scaffolded successfully!\n")));
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 = path6.resolve(cwd, options.write);
2509
- if (fs6.existsSync(targetFile)) {
2510
- let existing = fs6.readFileSync(targetFile, "utf-8");
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
- fs6.writeFileSync(targetFile, existing, "utf-8");
2523
- console.log(pc2.green(`\u2714 Updated rules in ${options.write}`));
2842
+ fs8.writeFileSync(targetFile, existing, "utf-8");
2843
+ console.log(pc3.green(`\u2714 Updated rules in ${options.write}`));
2524
2844
  } else {
2525
- fs6.writeFileSync(targetFile, rulesMd, "utf-8");
2526
- console.log(pc2.green(`\u2714 Created ${options.write} with design system rules`));
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