design-pact 0.2.1 → 0.3.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.
Files changed (36) hide show
  1. package/README.md +17 -1
  2. package/SKILL.md +17 -5
  3. package/dist/cli.js +712 -33
  4. package/package.json +2 -3
  5. package/web/404.html +1 -1
  6. package/web/__next.__PAGE__.txt +4 -4
  7. package/web/__next._full.txt +12 -12
  8. package/web/__next._head.txt +4 -4
  9. package/web/__next._index.txt +4 -4
  10. package/web/__next._tree.txt +2 -2
  11. package/web/_next/static/chunks/06~f4cvc3b8e~.js +5 -0
  12. package/web/_next/static/chunks/0dbhjjzl8qfwv.js +1 -0
  13. package/web/_next/static/chunks/0ge~kyq7lzl~5.js +187 -0
  14. package/web/_next/static/chunks/0ht900cau6_ur.js +31 -0
  15. package/web/_next/static/chunks/0nn9jq1cov9ax.css +3 -0
  16. package/web/_next/static/chunks/0pe3f11zk1rcv.js +1 -0
  17. package/web/_next/static/chunks/{turbopack-0y3s-y83i3.06.js → turbopack-0jm8qs4dffmjl.js} +1 -1
  18. package/web/_not-found/__next._full.txt +10 -10
  19. package/web/_not-found/__next._head.txt +4 -4
  20. package/web/_not-found/__next._index.txt +4 -4
  21. package/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  22. package/web/_not-found/__next._not-found.txt +3 -3
  23. package/web/_not-found/__next._tree.txt +2 -2
  24. package/web/_not-found.html +1 -1
  25. package/web/_not-found.txt +10 -10
  26. package/web/index.html +1 -1
  27. package/web/index.txt +12 -12
  28. package/web/_next/static/chunks/0-l9p_pd2v836.js +0 -5
  29. package/web/_next/static/chunks/012lv2-viu5_..js +0 -187
  30. package/web/_next/static/chunks/0rp7qr3afex.u.js +0 -31
  31. package/web/_next/static/chunks/0uyeui9y_zv_0.css +0 -3
  32. package/web/_next/static/chunks/0x72peuimhbw-.js +0 -1
  33. package/web/_next/static/chunks/15356_01bt_3u.js +0 -1
  34. /package/web/_next/static/{PuB4TNuzsXn-CMEz1oKNJ → tIX_HvPOULd9j1yucL1Pb}/_buildManifest.js +0 -0
  35. /package/web/_next/static/{PuB4TNuzsXn-CMEz1oKNJ → tIX_HvPOULd9j1yucL1Pb}/_clientMiddlewareManifest.js +0 -0
  36. /package/web/_next/static/{PuB4TNuzsXn-CMEz1oKNJ → tIX_HvPOULd9j1yucL1Pb}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -890,21 +890,21 @@ var require_react_development = __commonJS({
890
890
  );
891
891
  actScopeDepth = prevActScopeDepth;
892
892
  }
893
- function recursivelyFlushAsyncActWork(returnValue, resolve3, reject) {
893
+ function recursivelyFlushAsyncActWork(returnValue, resolve4, reject) {
894
894
  var queue = ReactSharedInternals.actQueue;
895
895
  if (null !== queue)
896
896
  if (0 !== queue.length)
897
897
  try {
898
898
  flushActQueue(queue);
899
899
  enqueueTask(function() {
900
- return recursivelyFlushAsyncActWork(returnValue, resolve3, reject);
900
+ return recursivelyFlushAsyncActWork(returnValue, resolve4, reject);
901
901
  });
902
902
  return;
903
903
  } catch (error) {
904
904
  ReactSharedInternals.thrownErrors.push(error);
905
905
  }
906
906
  else ReactSharedInternals.actQueue = null;
907
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve3(returnValue);
907
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve4(returnValue);
908
908
  }
909
909
  function flushActQueue(queue) {
910
910
  if (!isFlushing) {
@@ -1091,7 +1091,7 @@ var require_react_development = __commonJS({
1091
1091
  ));
1092
1092
  });
1093
1093
  return {
1094
- then: function(resolve3, reject) {
1094
+ then: function(resolve4, reject) {
1095
1095
  didAwaitActCall = true;
1096
1096
  thenable.then(
1097
1097
  function(returnValue) {
@@ -1101,7 +1101,7 @@ var require_react_development = __commonJS({
1101
1101
  flushActQueue(queue), enqueueTask(function() {
1102
1102
  return recursivelyFlushAsyncActWork(
1103
1103
  returnValue,
1104
- resolve3,
1104
+ resolve4,
1105
1105
  reject
1106
1106
  );
1107
1107
  });
@@ -1115,7 +1115,7 @@ var require_react_development = __commonJS({
1115
1115
  ReactSharedInternals.thrownErrors.length = 0;
1116
1116
  reject(_thrownError);
1117
1117
  }
1118
- } else resolve3(returnValue);
1118
+ } else resolve4(returnValue);
1119
1119
  },
1120
1120
  function(error) {
1121
1121
  popActScope(prevActQueue, prevActScopeDepth);
@@ -1137,15 +1137,15 @@ var require_react_development = __commonJS({
1137
1137
  if (0 < ReactSharedInternals.thrownErrors.length)
1138
1138
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
1139
1139
  return {
1140
- then: function(resolve3, reject) {
1140
+ then: function(resolve4, reject) {
1141
1141
  didAwaitActCall = true;
1142
1142
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
1143
1143
  return recursivelyFlushAsyncActWork(
1144
1144
  returnValue$jscomp$0,
1145
- resolve3,
1145
+ resolve4,
1146
1146
  reject
1147
1147
  );
1148
- })) : resolve3(returnValue$jscomp$0);
1148
+ })) : resolve4(returnValue$jscomp$0);
1149
1149
  }
1150
1150
  };
1151
1151
  };
@@ -1451,8 +1451,8 @@ var require_react = __commonJS({
1451
1451
  });
1452
1452
 
1453
1453
  // src/cli.ts
1454
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
1455
- import { join as join3, resolve as resolve2 } from "path";
1454
+ import { readFileSync as readFileSync3, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
1455
+ import { join as join3, resolve as resolve3 } from "path";
1456
1456
 
1457
1457
  // src/locale.ts
1458
1458
  var envLang = (process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || "").toLowerCase();
@@ -1468,11 +1468,11 @@ function fence(md, lang) {
1468
1468
  return m ? m[1] : null;
1469
1469
  }
1470
1470
  function parseDesignSystem(md) {
1471
- if (!/^---\s*\ndesign-system:/m.test(md) && !md.includes("# Design system")) {
1471
+ if (!/^---\s*\ndesign-pact:/m.test(md) && !md.includes("# Design system")) {
1472
1472
  throw new Error(
1473
1473
  t(
1474
- "This does not look like a design.md (missing the design-system frontmatter / Design system heading).",
1475
- "\u8FD9\u770B\u8D77\u6765\u4E0D\u662F design.md\uFF08\u7F3A\u5C11 design-system frontmatter / Design system \u6807\u9898\uFF09\u3002"
1474
+ "This does not look like a design.md (missing the design-pact frontmatter / Design system heading).",
1475
+ "\u8FD9\u770B\u8D77\u6765\u4E0D\u662F design.md\uFF08\u7F3A\u5C11 design-pact frontmatter / Design system \u6807\u9898\uFF09\u3002"
1476
1476
  )
1477
1477
  );
1478
1478
  }
@@ -1497,6 +1497,33 @@ function parseDesignSystem(md) {
1497
1497
  import { converter, formatHex, parse, formatCss } from "culori";
1498
1498
  var toOklch = converter("oklch");
1499
1499
  var toRgb = converter("rgb");
1500
+ function hexToOklch(hex) {
1501
+ const c = toOklch(parse(hex));
1502
+ if (!c) return { mode: "oklch", l: 0, c: 0, h: 0 };
1503
+ return { ...c, h: c.h ?? 0 };
1504
+ }
1505
+ function oklchToHex(c) {
1506
+ const rgb = toRgb(c);
1507
+ if (!rgb) return "#000000";
1508
+ const clamp2 = (v) => Math.max(0, Math.min(1, v));
1509
+ return formatHex({ ...rgb, r: clamp2(rgb.r), g: clamp2(rgb.g), b: clamp2(rgb.b) }) || "#000000";
1510
+ }
1511
+ function relativeLuminance(hex) {
1512
+ const rgb = toRgb(parse(hex));
1513
+ if (!rgb) return 0;
1514
+ const lin = (v) => v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
1515
+ return 0.2126 * lin(rgb.r) + 0.7152 * lin(rgb.g) + 0.0722 * lin(rgb.b);
1516
+ }
1517
+ function contrastRatio(a, b) {
1518
+ const la = relativeLuminance(a);
1519
+ const lb = relativeLuminance(b);
1520
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
1521
+ return (hi + 0.05) / (lo + 0.05);
1522
+ }
1523
+ function oklchString(hex) {
1524
+ const c = hexToOklch(hex);
1525
+ return formatCss(c) || "";
1526
+ }
1500
1527
 
1501
1528
  // ../../lib/scales.ts
1502
1529
  var SPACING_STEPS = [
@@ -1596,6 +1623,7 @@ function buildOpacityScale(base) {
1596
1623
  }
1597
1624
 
1598
1625
  // ../../lib/tokens-core.ts
1626
+ var SEMANTIC_KINDS = ["success", "warning", "error", "info"];
1599
1627
  var defaultTypography = {
1600
1628
  base: 16,
1601
1629
  ratio: 1.25,
@@ -1635,6 +1663,95 @@ function buildScale(t2) {
1635
1663
  }
1636
1664
 
1637
1665
  // ../../lib/export.ts
1666
+ var OPACITY_USAGE = {
1667
+ hover: "tint/overlay on hover (e.g. button or row background)",
1668
+ pressed: "active/pressed feedback",
1669
+ focus: "focus ring or focused-row background",
1670
+ disabled: "opacity of disabled controls",
1671
+ overlay: "modal/drawer backdrop scrim"
1672
+ };
1673
+ function w3cTokens(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic) {
1674
+ const darkById = new Map((darkColors ?? []).map((c) => [c.id, c.displayHex]));
1675
+ const colorGroup = {};
1676
+ for (const c of colors) {
1677
+ const key = c.role === "unassigned" ? c.id : c.role;
1678
+ const ext = {
1679
+ proportion: c.proportion,
1680
+ oklch: oklchString(c.displayHex)
1681
+ };
1682
+ const dark = darkById.get(c.id);
1683
+ if (dark) ext.dark = dark;
1684
+ colorGroup[key] = {
1685
+ $value: c.displayHex,
1686
+ $type: "color",
1687
+ $extensions: { "design-pact": ext }
1688
+ };
1689
+ }
1690
+ const fontSizes = {};
1691
+ for (const s of buildScale(typography)) {
1692
+ fontSizes[s.name] = { $value: `${s.rem}rem`, $type: "dimension" };
1693
+ }
1694
+ const spacingGroup = {};
1695
+ for (const s of buildSpacing(spacing.base)) {
1696
+ spacingGroup[s.name] = { $value: `${s.px}px`, $type: "dimension" };
1697
+ }
1698
+ const radiusGroup = {};
1699
+ for (const r of buildRadius(radius.base)) {
1700
+ radiusGroup[r.name] = { $value: `${r.px}px`, $type: "dimension" };
1701
+ }
1702
+ const shadowGroup = {};
1703
+ for (const level of ["sm", "md", "lg"]) {
1704
+ shadowGroup[level] = { $value: shadowToCss(shadow[level]), $type: "shadow" };
1705
+ }
1706
+ const motionGroup = {};
1707
+ for (const d of buildDurations(motion.base)) {
1708
+ motionGroup[`duration-${d.name}`] = { $value: `${d.ms}ms`, $type: "duration" };
1709
+ }
1710
+ motionGroup["easing"] = { $value: EASING_PRESETS[motion.easing], $type: "cubicBezier" };
1711
+ const borderGroup = {};
1712
+ for (const b of buildBorderScale(border.base)) {
1713
+ borderGroup[b.name] = { $value: `${b.px}px`, $type: "dimension" };
1714
+ }
1715
+ const opacityGroup = {};
1716
+ for (const o of buildOpacityScale(opacity.base)) {
1717
+ opacityGroup[o.name] = { $value: o.value, $type: "number" };
1718
+ }
1719
+ const semanticGroup = semantic ? Object.fromEntries(
1720
+ SEMANTIC_KINDS.map((k) => [
1721
+ k,
1722
+ {
1723
+ $value: semantic[k],
1724
+ $type: "color",
1725
+ ...darkSemantic ? { $extensions: { "design-pact": { dark: darkSemantic[k] } } } : {}
1726
+ }
1727
+ ])
1728
+ ) : void 0;
1729
+ return {
1730
+ color: colorGroup,
1731
+ ...semanticGroup ? { semantic: semanticGroup } : {},
1732
+ typography: {
1733
+ fontFamily: {
1734
+ body: { $value: typography.fontFamily, $type: "fontFamily" },
1735
+ heading: { $value: typography.headingFamily, $type: "fontFamily" }
1736
+ },
1737
+ fontSize: fontSizes,
1738
+ fontWeight: { $value: typography.fontWeight, $type: "fontWeight" },
1739
+ fontWeightHeading: { $value: headingWeight(typography.fontWeight), $type: "fontWeight" },
1740
+ fontWeightBold: { $value: boldWeight(typography.fontWeight), $type: "fontWeight" },
1741
+ lineHeight: { $value: typography.lineHeight, $type: "number" },
1742
+ letterSpacing: { $value: `${typography.letterSpacing}em`, $type: "dimension" },
1743
+ $extensions: {
1744
+ "design-pact": { base: typography.base, ratio: typography.ratio }
1745
+ }
1746
+ },
1747
+ spacing: spacingGroup,
1748
+ borderRadius: radiusGroup,
1749
+ borderWidth: borderGroup,
1750
+ shadow: shadowGroup,
1751
+ motion: motionGroup,
1752
+ opacity: opacityGroup
1753
+ };
1754
+ }
1638
1755
  function tailwindConfig(colors, typography, spacing, radius, shadow, motion, border, opacity) {
1639
1756
  const colorEntries = colors.map((c) => ` ${c.role === "unassigned" ? c.id : c.role}: "${c.displayHex}",`).join("\n");
1640
1757
  const sizeEntries = buildScale(typography).map((s) => ` "${s.name}": "${s.rem}rem",`).join("\n");
@@ -1694,6 +1811,205 @@ ${opacityEntries}
1694
1811
  };
1695
1812
  `;
1696
1813
  }
1814
+ function cssVars(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic) {
1815
+ const colorLines = colors.map((c) => ` --color-${c.role === "unassigned" ? c.id : c.role}: ${c.displayHex};`).join("\n");
1816
+ const semanticLines = semantic ? "\n" + SEMANTIC_KINDS.map((k) => ` --color-${k}: ${semantic[k]};`).join("\n") : "";
1817
+ const sizeLines = buildScale(typography).map((s) => ` --font-size-${s.name}: ${s.rem}rem;`).join("\n");
1818
+ const spacingLines = buildSpacing(spacing.base).map((s) => ` --spacing-${s.name}: ${s.px}px;`).join("\n");
1819
+ const radiusLines = buildRadius(radius.base).map((r) => ` --radius-${r.name}: ${r.px}px;`).join("\n");
1820
+ const shadowLines = ["sm", "md", "lg"].map((level) => ` --shadow-${level}: ${shadowToCss(shadow[level])};`).join("\n");
1821
+ const durationLines = buildDurations(motion.base).map((d) => ` --duration-${d.name}: ${d.ms}ms;`).join("\n");
1822
+ const borderLines = buildBorderScale(border.base).map((b) => ` --border-${b.name}: ${b.px}px;`).join("\n");
1823
+ const opacityLines = buildOpacityScale(opacity.base).map((o) => ` --opacity-${o.name}: ${o.value};`).join("\n");
1824
+ const darkSemanticLines = darkColors && darkColors.length > 0 && darkSemantic ? "\n" + SEMANTIC_KINDS.map((k) => ` --color-${k}: ${darkSemantic[k]};`).join("\n") : "";
1825
+ const darkBlock = darkColors && darkColors.length > 0 ? `
1826
+ @media (prefers-color-scheme: dark) {
1827
+ :root {
1828
+ ${darkColors.map((c) => ` --color-${c.role === "unassigned" ? c.id : c.role}: ${c.displayHex};`).join("\n")}${darkSemanticLines}
1829
+ }
1830
+ }
1831
+ ` : "";
1832
+ return `:root {
1833
+ ${colorLines}${semanticLines}
1834
+ --font-family-body: ${typography.fontFamily};
1835
+ --font-family-heading: ${typography.headingFamily};
1836
+ --font-weight: ${typography.fontWeight};
1837
+ --font-weight-heading: ${headingWeight(typography.fontWeight)};
1838
+ --font-weight-bold: ${boldWeight(typography.fontWeight)};
1839
+ --line-height: ${typography.lineHeight};
1840
+ --letter-spacing: ${typography.letterSpacing}em;
1841
+ ${sizeLines}
1842
+ ${spacingLines}
1843
+ ${radiusLines}
1844
+ ${borderLines}
1845
+ ${shadowLines}
1846
+ ${durationLines}
1847
+ --easing-standard: ${EASING_PRESETS[motion.easing]};
1848
+ ${opacityLines}
1849
+ }
1850
+ ${darkBlock}`;
1851
+ }
1852
+ function aiPrompt(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic) {
1853
+ const sorted = [...colors].sort((a, b) => b.proportion - a.proportion);
1854
+ const palette = sorted.map(
1855
+ (c) => `- ${c.role === "unassigned" ? c.id : c.role}: ${c.displayHex} (${(c.proportion * 100).toFixed(1)}% of source image)`
1856
+ ).join("\n");
1857
+ const scale = buildScale(typography).map((s) => `- ${s.name}: ${s.rem}rem (${s.px}px)`).join("\n");
1858
+ const dominant = sorted[0];
1859
+ const accent = sorted.find((c) => c.role === "accent") ?? sorted[1];
1860
+ const primary = sorted.find((c) => c.role === "primary") ?? sorted[1];
1861
+ const rootBlock = cssVars(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic);
1862
+ const semanticSection = semantic ? `
1863
+ ## Status colors
1864
+ Use these for feedback only \u2014 never as brand/UI surface colors. \`--color-success\` ${semantic.success} (confirmations), \`--color-warning\` ${semantic.warning} (cautions), \`--color-error\` ${semantic.error} (errors/destructive), \`--color-info\` ${semantic.info} (informational). Reference via \`var(--color-\u2026)\`.${darkSemantic ? " In dark mode the `@media` block above overrides these with the dark-background variants \u2014 same convention as the brand colors." : ""}
1865
+ ` : "";
1866
+ const darkSection = darkColors && darkColors.length > 0 ? `
1867
+ ## Dark mode
1868
+ The \`@media (prefers-color-scheme: dark)\` block above overrides COLOR variables only \u2014 every other token (type, spacing, radius, shadow, motion) stays identical in dark mode. Do not hand-pick dark colors or adjust non-color tokens for dark mode; the block is authoritative.
1869
+ ` : "";
1870
+ return `# Design system
1871
+
1872
+ ## Machine-readable tokens \u2014 COPY THIS BLOCK VERBATIM
1873
+ Paste the \`:root\` below into your \`<style>\` exactly as written, then reference
1874
+ every value through \`var(--\u2026)\`. This block is the single source of truth.
1875
+
1876
+ Hard rules:
1877
+ - Do NOT redefine, round, rescale, or re-derive any value. Copy it character for character.
1878
+ - Do NOT introduce colors, fonts, font-sizes, spacing, radii, or shadows that are not declared here.
1879
+ - If you need a lighter/darker shade of a color, derive it in OKLCH by changing lightness only \u2014 keep hue and chroma fixed. Do not invent a new hue.
1880
+ - Every length in your CSS must be a \`var(--\u2026)\` reference. Do NOT convert a \`px\` token to \`rem\` (or any other unit) \u2014 reference it as-is. Spacing, radius, and border vars are in \`px\`; font sizes are in \`rem\`; keep each as declared.
1881
+
1882
+ \`\`\`css
1883
+ ${rootBlock}\`\`\`
1884
+
1885
+ The prose below explains the intent behind these tokens. When prose and the \`:root\` block disagree, the block wins.
1886
+
1887
+ ---
1888
+
1889
+ When generating UI from this token set, respect the following proportions and roles. The percentages reflect how much of the source design each color occupies \u2014 use them as a guide for surface area in the output.
1890
+
1891
+ ## Color palette
1892
+ ${palette}
1893
+
1894
+ The dominant background should occupy roughly ${(dominant.proportion * 100).toFixed(0)}% of the layout.
1895
+
1896
+ Bind colors to roles exactly as the design tool renders them \u2014 do NOT swap primary and accent:
1897
+
1898
+ - **primary (${primary.displayHex})** is the main interactive fill: primary buttons / CTAs, active nav item, key links, the logo mark. This is the button color.
1899
+ - **accent (${accent.displayHex})** is for SECONDARY emphasis only: badges/chips, chart & graphic accents, small highlights \u2014 typically ${Math.min(15, Math.round(accent.proportion * 100))}% or less of any screen. Do not use accent as the primary button fill.
1900
+ - **muted** for secondary/placeholder text, **border** for hairlines and dividers.
1901
+
1902
+ ## Typography
1903
+ Base size ${typography.base}px, modular scale ratio ${typography.ratio}.
1904
+
1905
+ ${scale}
1906
+
1907
+ Body family: ${typography.fontFamily}
1908
+ Heading family: ${typography.headingFamily}
1909
+ Line-height: ${typography.lineHeight}, letter-spacing: ${typography.letterSpacing}em. There are three explicit font weights \u2014 bind them by role, and never let the browser default an element's weight:
1910
+
1911
+ - **\`--font-weight\` (${typography.fontWeight})** \u2192 body text, labels, and any non-heading copy.
1912
+ - **\`--font-weight-heading\` (${headingWeight(typography.fontWeight)})** \u2192 all headings h1\u2013h5. Set this on every heading; do NOT leave headings at the body weight.
1913
+ - **\`--font-weight-bold\` (${boldWeight(typography.fontWeight)})** \u2192 emphasis only: primary CTA labels, \`<strong>\`, badges.
1914
+
1915
+ ## Spacing
1916
+ Base unit ${spacing.base}px. Use this 8-step scale for padding/margin/gap:
1917
+
1918
+ ${buildSpacing(spacing.base).map((s) => `- ${s.name}: ${s.px}px`).join("\n")}
1919
+
1920
+ Bind padding & gaps to these steps exactly as the design tool renders them \u2014
1921
+ padding is written as \`vertical horizontal\`:
1922
+
1923
+ - **Primary / prominent CTA button** \u2192 \`--spacing-sm\` \`--spacing-lg\` (e.g. \`padding: var(--spacing-sm) var(--spacing-lg)\`) \u2014 a taller, comfortable target.
1924
+ - **Compact buttons / inputs / chips** \u2192 \`--spacing-xxs\` \`--spacing-sm\`.
1925
+ - **List rows / nav items / card header & footer** \u2192 \`--spacing-xs\` \`--spacing-md\`.
1926
+ - **Card / panel / modal body** \u2192 \`--spacing-md\` on all sides.
1927
+ - **Gap between sibling controls, grid/flex gaps** \u2192 \`--spacing-sm\`.
1928
+ - **Vertical rhythm between page sections** \u2192 \`--spacing-xl\` \u2026 \`--spacing-section\`.
1929
+
1930
+ Only use listed values; never improvise an intermediate gap.
1931
+
1932
+ ## Border radius
1933
+ Base ${radius.base}px. Use:
1934
+
1935
+ ${buildRadius(radius.base).map((r) => `- ${r.name}: ${r.name === "full" ? "9999px (pill)" : `${r.px}px`}`).join("\n")}
1936
+
1937
+ Bind components to these steps exactly as the design tool renders them \u2014 do NOT
1938
+ pick a step freely (e.g. do not make buttons pill-shaped unless \`--radius-md\`
1939
+ resolves to a pill):
1940
+
1941
+ - **Buttons / controls / nav items** \u2192 \`--radius-md\`
1942
+ - **Inputs / text fields / selects** \u2192 \`--radius-sm\`
1943
+ - **Cards / panels / modals / larger surfaces** \u2192 \`--radius-lg\`
1944
+ - **Badges / chips / avatars / toggles (intentionally pill)** \u2192 \`--radius-full\`
1945
+ - Bigger containers may use \`--radius-xl\`; \`--radius-full\` is reserved for
1946
+ circles/pills, never for standard buttons or cards.
1947
+
1948
+ ## Shadow / elevation
1949
+ ${shadow.advanced ? "Per-level custom values" : `Intensity ${shadow.intensity.toFixed(2)}`}.
1950
+
1951
+ ${["sm", "md", "lg"].map((level) => `- ${level}: ${shadowToCss(shadow[level])}`).join("\n")}
1952
+
1953
+ Bind each level to an elevation exactly as the design tool renders them \u2014 do NOT
1954
+ pick a level freely:
1955
+
1956
+ - **sm** \u2192 resting surfaces: cards, panels, subtle raise (the default for a raised element).
1957
+ - **md** \u2192 hovered / lifted state: a card or button on hover, raised popovers.
1958
+ - **lg** \u2192 floating overlays: modals, drawers, dropdowns, menus above the page.
1959
+
1960
+ Flat elements (nav bar, page background, inline controls) get NO shadow. On hover, step a card up one level (sm \u2192 md), don't jump straight to lg.
1961
+
1962
+ ## Border width
1963
+ ${buildBorderScale(border.base).map((b) => `- ${b.name}: ${b.px}px`).join("\n")}
1964
+
1965
+ Bind each width as the design tool renders them:
1966
+
1967
+ - **default** \u2192 all resting borders: card outlines, input borders, dividers.
1968
+ - **strong** \u2192 focused / active / selected emphasis (e.g. an input grows from \`default\` to \`strong\` on focus). Do not use \`strong\` for ordinary resting borders.
1969
+
1970
+ ## Opacity / transparency
1971
+ These drive interactive states \u2014 wire them up, don't leave them unused. Apply each via \`var(--opacity-\u2026)\` (or an rgba/overlay at that alpha):
1972
+
1973
+ ${buildOpacityScale(opacity.base).map((o) => `- ${o.name} (${(o.value * 100).toFixed(1)}%): ${OPACITY_USAGE[o.name] ?? "interactive state"}`).join("\n")}
1974
+
1975
+ Every clickable element (buttons, links, cards) should show a hover state and a disabled state using these values. Use \`overlay\` for scrims behind modals/drawers.
1976
+
1977
+ ## Motion / Animation
1978
+ Base duration ${motion.base}ms, easing: ${motion.easing} (${EASING_PRESETS[motion.easing]}).
1979
+
1980
+ ${buildDurations(motion.base).map((d) => `- ${d.name}: ${d.ms}ms`).join("\n")}
1981
+
1982
+ Use \`--duration-normal\` as the default transition. Prefer \`--duration-fast\` for hover states and micro-interactions. Reserve \`--duration-page\` for route/page-level transitions.
1983
+ ${darkSection}${semanticSection}
1984
+ ## Output guidance
1985
+ - Maintain the proportional relationships above when laying out a page.
1986
+ - Do not introduce new hues. If you need additional shades, derive them by adjusting OKLCH lightness only, keeping hue and chroma fixed.
1987
+ - Respect the modular scale \u2014 pick from the listed sizes rather than introducing new ones.
1988
+ - Use only the listed spacing values; do not improvise intermediate gaps.
1989
+ - ${SCALE_STEPS.length} type sizes is intentional. Use semantic mapping: h1-h5 for headings, body for paragraphs, small/caption for metadata.
1990
+ `;
1991
+ }
1992
+ function designSystemMarkdown(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic) {
1993
+ const prompt = aiPrompt(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic);
1994
+ const tokens = w3cTokens(colors, typography, spacing, radius, shadow, motion, border, opacity, darkColors, semantic, darkSemantic);
1995
+ const generated = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1996
+ return `---
1997
+ design-pact: 1
1998
+ generated: ${generated}
1999
+ ---
2000
+
2001
+ ${prompt}
2002
+ ---
2003
+
2004
+ ## Machine-readable tokens (W3C Design Tokens)
2005
+
2006
+ Humans and AI can read the prose above. The JSON below lets tools (the companion CLI) reconstruct the design system precisely and deterministically, and convert it to CSS / Tailwind / etc.
2007
+
2008
+ \`\`\`json
2009
+ ${JSON.stringify(tokens, null, 2)}
2010
+ \`\`\`
2011
+ `;
2012
+ }
1697
2013
 
1698
2014
  // ../../node_modules/zustand/esm/vanilla.mjs
1699
2015
  var createStoreImpl = (createState) => {
@@ -1949,7 +2265,7 @@ var useLang = create()(
1949
2265
  // skipHydration: the static export prerenders with the "en" default, then
1950
2266
  // StoreHydration rehydrates from localStorage on the client (same pattern
1951
2267
  // as the tokens store) so there is no hydration mismatch.
1952
- { name: "design-system-lang", skipHydration: true }
2268
+ { name: "design-pact-lang", skipHydration: true }
1953
2269
  )
1954
2270
  );
1955
2271
  function trg(en, zh) {
@@ -2000,7 +2316,7 @@ function parseW3CTokens(jsonText) {
2000
2316
  role: ROLES.has(key) ? key : "unassigned",
2001
2317
  name: key
2002
2318
  });
2003
- const dark = asObj(asObj(asObj(node)?.["$extensions"])?.["design-system"])?.dark;
2319
+ const dark = asObj(asObj(asObj(node)?.["$extensions"])?.["design-pact"])?.dark;
2004
2320
  darkHexes.push(typeof dark === "string" && HEX_RE.test(dark) ? dark.toLowerCase() : void 0);
2005
2321
  }
2006
2322
  if (colors.length === 0) throw new Error(trg("No recognizable colors in the color group", "color \u5206\u7EC4\u91CC\u6CA1\u6709\u53EF\u8BC6\u522B\u7684\u989C\u8272"));
@@ -2009,7 +2325,7 @@ function parseW3CTokens(jsonText) {
2009
2325
  const typo = asObj(root.typography);
2010
2326
  if (typo) {
2011
2327
  const t2 = {};
2012
- const ext = asObj(asObj(typo.$extensions)?.["design-system"]);
2328
+ const ext = asObj(asObj(typo.$extensions)?.["design-pact"]);
2013
2329
  const base = num(ext?.base);
2014
2330
  const ratio = num(ext?.ratio);
2015
2331
  if (base) t2.base = base;
@@ -2117,18 +2433,18 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2117
2433
  async function cmdInit(args) {
2118
2434
  const global = args.includes("--global");
2119
2435
  const base = global ? join(homedir(), ".claude") : join(process.cwd(), ".claude");
2120
- const dir = join(base, "skills", "design-system");
2436
+ const dir = join(base, "skills", "design-pact");
2121
2437
  if (!existsSync(SKILL_SRC)) {
2122
2438
  console.error("\u2717 " + t("SKILL.md missing from the package (run bundle before publishing).", "\u5305\u5185\u7F3A\u5C11 SKILL.md\uFF08\u53D1\u5E03\u524D\u9700\u5148\u8FD0\u884C bundle\uFF09\u3002"));
2123
2439
  process.exit(1);
2124
2440
  }
2125
2441
  await mkdir(dir, { recursive: true });
2126
2442
  await copyFile(SKILL_SRC, join(dir, "SKILL.md"));
2127
- console.log(t(`\u2713 Installed the design-system skill \u2192 ${join(dir, "SKILL.md")}`, `\u2713 \u5DF2\u5B89\u88C5 design-system skill \u2192 ${join(dir, "SKILL.md")}`));
2443
+ console.log(t(`\u2713 Installed the design-pact skill \u2192 ${join(dir, "SKILL.md")}`, `\u2713 \u5DF2\u5B89\u88C5 design-pact skill \u2192 ${join(dir, "SKILL.md")}`));
2128
2444
  console.log(
2129
2445
  global ? t(" (global: available in every project)", " \uFF08\u5168\u5C40\uFF1A\u5BF9\u6240\u6709\u9879\u76EE\u53EF\u7528\uFF09") : t(" (project-level: this project only; add --global to install into ~/.claude)", " \uFF08\u9879\u76EE\u7EA7\uFF1A\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF0C\u52A0 --global \u53EF\u88C5\u5230 ~/.claude\uFF09")
2130
2446
  );
2131
- console.log("\n" + t('Next: in Claude Code / Cursor, say "use the design-system skill".', "\u4E0B\u4E00\u6B65\uFF1A\u5728 Claude Code / Cursor \u91CC\u8BF4\u300C\u7528 design-system skill\u300D\u3002"));
2447
+ console.log("\n" + t('Next: in Claude Code / Cursor, say "use the design-pact skill".', "\u4E0B\u4E00\u6B65\uFF1A\u5728 Claude Code / Cursor \u91CC\u8BF4\u300C\u7528 design-pact skill\u300D\u3002"));
2132
2448
  console.log(t("It clarifies direction, proposes palettes, and opens the local studio via `npx design-pact open`.", "\u5B83\u4F1A\u95EE\u6E05\u65B9\u5411\u3001\u4EA7\u51FA\u914D\u8272\uFF0C\u5E76\u7528 `npx design-pact open` \u5728\u672C\u5730\u6253\u5F00\u914D\u8272\u5DE5\u5177\u3002"));
2133
2449
  }
2134
2450
  function fileForUrl(urlPath) {
@@ -2151,21 +2467,21 @@ function serveStatic(port) {
2151
2467
  res.writeHead(200, { "content-type": MIME[extname(file)] || "application/octet-stream" });
2152
2468
  createReadStream(file).pipe(res);
2153
2469
  });
2154
- return new Promise((resolve3, reject) => {
2470
+ return new Promise((resolve4, reject) => {
2155
2471
  server.once("error", reject);
2156
- server.listen(port, () => resolve3());
2472
+ server.listen(port, () => resolve4());
2157
2473
  });
2158
2474
  }
2159
2475
  function probe(port) {
2160
- return new Promise((resolve3) => {
2476
+ return new Promise((resolve4) => {
2161
2477
  const req = httpGet({ host: "localhost", port, path: "/", timeout: 700 }, (r) => {
2162
2478
  r.resume();
2163
- resolve3(true);
2479
+ resolve4(true);
2164
2480
  });
2165
- req.on("error", () => resolve3(false));
2481
+ req.on("error", () => resolve4(false));
2166
2482
  req.on("timeout", () => {
2167
2483
  req.destroy();
2168
- resolve3(false);
2484
+ resolve4(false);
2169
2485
  });
2170
2486
  });
2171
2487
  }
@@ -2230,7 +2546,7 @@ function tokenHexes(w3c) {
2230
2546
  if (obj.$type === "color" && typeof obj.$value === "string") {
2231
2547
  const hex = normalizeHex(obj.$value);
2232
2548
  if (hex) out.add(hex);
2233
- const dark = obj.$extensions?.["design-system"]?.dark;
2549
+ const dark = obj.$extensions?.["design-pact"]?.dark;
2234
2550
  if (typeof dark === "string") {
2235
2551
  const dh = normalizeHex(dark);
2236
2552
  if (dh) out.add(dh);
@@ -2387,19 +2703,348 @@ function reportCheck(result) {
2387
2703
  return 1;
2388
2704
  }
2389
2705
 
2706
+ // src/import.ts
2707
+ import { readFileSync as readFileSync2 } from "fs";
2708
+ import { basename, resolve as resolve2 } from "path";
2709
+
2710
+ // ../../lib/darkMode.ts
2711
+ var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
2712
+ function deriveOppositeHex(hex, role) {
2713
+ const c = hexToOklch(hex);
2714
+ const flipped = 1 - c.l;
2715
+ let l;
2716
+ let chroma = c.c;
2717
+ switch (role) {
2718
+ case "background":
2719
+ l = c.l >= 0.5 ? clamp(flipped, 0.14, 0.24) : clamp(flipped, 0.92, 0.985);
2720
+ chroma = Math.min(c.c, 0.04);
2721
+ break;
2722
+ case "foreground":
2723
+ l = c.l < 0.5 ? clamp(flipped, 0.85, 0.97) : clamp(flipped, 0.12, 0.3);
2724
+ break;
2725
+ case "border":
2726
+ l = c.l >= 0.5 ? clamp(flipped, 0.25, 0.38) : clamp(flipped, 0.7, 0.85);
2727
+ chroma = Math.min(c.c, 0.04);
2728
+ break;
2729
+ case "muted":
2730
+ l = clamp(flipped, 0.55, 0.75);
2731
+ break;
2732
+ case "primary":
2733
+ case "accent":
2734
+ l = c.l < 0.55 ? clamp(c.l + 0.18, 0.55, 0.8) : c.l;
2735
+ break;
2736
+ default:
2737
+ l = clamp(flipped, 0.2, 0.9);
2738
+ }
2739
+ return oklchToHex({ mode: "oklch", l, c: chroma, h: c.h ?? 0 });
2740
+ }
2741
+
2742
+ // ../../lib/semantic.ts
2743
+ var HUE = {
2744
+ success: 150,
2745
+ warning: 75,
2746
+ error: 27,
2747
+ info: 255
2748
+ };
2749
+ var CHROMA = {
2750
+ success: 0.15,
2751
+ warning: 0.16,
2752
+ error: 0.19,
2753
+ info: 0.16
2754
+ };
2755
+ function deriveSemantic(bgHex) {
2756
+ const bg = hexToOklch(bgHex);
2757
+ const darkBg = bg.l < 0.5;
2758
+ const out = {};
2759
+ for (const kind of SEMANTIC_KINDS) {
2760
+ const h = HUE[kind];
2761
+ const c = CHROMA[kind];
2762
+ let l = darkBg ? 0.72 : 0.58;
2763
+ let hex = oklchToHex({ mode: "oklch", l, c, h });
2764
+ for (let i = 0; i < 16 && contrastRatio(hex, bgHex) < 3.2; i++) {
2765
+ l = darkBg ? Math.min(0.92, l + 0.03) : Math.max(0.38, l - 0.03);
2766
+ hex = oklchToHex({ mode: "oklch", l, c, h });
2767
+ }
2768
+ out[kind] = hex;
2769
+ }
2770
+ return out;
2771
+ }
2772
+
2773
+ // src/import.ts
2774
+ var ROLES2 = ["background", "foreground", "primary", "accent", "muted", "border"];
2775
+ var NAME_HINTS = [
2776
+ ["background", /\b(?:background|bg|surface|canvas|page)\b/i],
2777
+ ["foreground", /\b(?:foreground|fg|text|ink|body)\b/i],
2778
+ ["primary", /\b(?:primary|brand|main|cta)\b/i],
2779
+ ["accent", /\b(?:accent|highlight|secondary)\b/i],
2780
+ ["muted", /\b(?:muted|gr[ae]y|neutral|subtle|placeholder)\b/i],
2781
+ ["border", /\b(?:border|divider|outline|stroke|input|ring)\b/i]
2782
+ ];
2783
+ var COLOR_LITERAL = /(#[0-9a-fA-F]{3,8}\b|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*[\d.]+\s*)?\))/g;
2784
+ function literalToHex(raw) {
2785
+ if (raw.startsWith("#")) return normalizeHex(raw);
2786
+ const m = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})/.exec(raw);
2787
+ if (!m) return null;
2788
+ const [r, g, b] = [Number(m[1]), Number(m[2]), Number(m[3])];
2789
+ if ([r, g, b].some((v) => v > 255)) return null;
2790
+ return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("");
2791
+ }
2792
+ function lineName(line, literalIndex) {
2793
+ const before = line.slice(0, literalIndex);
2794
+ const m = /([$@\w-]+)\s*:\s*["']?$/.exec(before);
2795
+ return m ? m[1] : "";
2796
+ }
2797
+ var CSS_PROPERTIES = /* @__PURE__ */ new Set([
2798
+ "background",
2799
+ "background-color",
2800
+ "color",
2801
+ "border",
2802
+ "border-color",
2803
+ "outline",
2804
+ "outline-color",
2805
+ "fill",
2806
+ "stroke",
2807
+ "caret-color",
2808
+ "accent-color"
2809
+ ]);
2810
+ function scanSignals(text, weight, into) {
2811
+ for (const line of text.split("\n")) {
2812
+ for (const m of line.matchAll(COLOR_LITERAL)) {
2813
+ const hex = literalToHex(m[0]);
2814
+ if (!hex) continue;
2815
+ const sig = into.get(hex) ?? { hex, count: 0, hints: {} };
2816
+ sig.count += weight;
2817
+ const name = lineName(line, m.index ?? 0);
2818
+ if (name) {
2819
+ const hintWeight = CSS_PROPERTIES.has(name.toLowerCase()) ? 1 : weight * 2;
2820
+ const normalized = name.replace(/([a-z])([A-Z])/g, "$1-$2");
2821
+ for (const [role, re] of NAME_HINTS) {
2822
+ if (re.test(normalized)) sig.hints[role] = (sig.hints[role] ?? 0) + hintWeight;
2823
+ }
2824
+ }
2825
+ into.set(hex, sig);
2826
+ }
2827
+ }
2828
+ }
2829
+ var RADIUS_RE = /(?:border-radius|borderRadius|--radius[\w-]*)["']?\s*:\s*["']?(\d{1,2})px/g;
2830
+ var SPACING_VAR_RE = /--spacing-[\w-]*\s*:\s*(\d{1,2}(?:\.\d+)?)px/g;
2831
+ var FONT_FAMILY_RE = /font-family\s*:\s*([^;}"']{3,120})/i;
2832
+ var FONT_SIZE_RE = /(?:html|body|:root)[^{]*\{[^}]*?font-size\s*:\s*(\d{2})px/;
2833
+ var isTailwindConfig = (f) => /^tailwind\.config\.(js|cjs|mjs|ts)$/.test(basename(f));
2834
+ function pick(arr, score) {
2835
+ let best;
2836
+ let bestScore = -Infinity;
2837
+ for (const t2 of arr) {
2838
+ const s = score(t2);
2839
+ if (s > bestScore) {
2840
+ bestScore = s;
2841
+ best = t2;
2842
+ }
2843
+ }
2844
+ return bestScore > 0 ? best : void 0;
2845
+ }
2846
+ var hueDist = (a, b) => {
2847
+ const d = Math.abs(a - b) % 360;
2848
+ return d > 180 ? 360 - d : d;
2849
+ };
2850
+ function blend(aHex, bHex, t2) {
2851
+ const a = hexToOklch(aHex);
2852
+ const b = hexToOklch(bHex);
2853
+ return oklchToHex({
2854
+ mode: "oklch",
2855
+ l: a.l + (b.l - a.l) * t2,
2856
+ c: a.c + (b.c - a.c) * t2,
2857
+ h: a.h ?? b.h ?? 0
2858
+ });
2859
+ }
2860
+ function assignRoles(signals) {
2861
+ const used = /* @__PURE__ */ new Set();
2862
+ const out = /* @__PURE__ */ new Map();
2863
+ const take = (role, hex, provenance) => {
2864
+ out.set(role, { hex, provenance });
2865
+ used.add(hex);
2866
+ };
2867
+ const free = () => signals.filter((s) => !used.has(s.hex));
2868
+ for (const role of ROLES2) {
2869
+ const named = pick(free().filter((s) => (s.hints[role] ?? 0) > 0), (s) => (s.hints[role] ?? 0) * 1e6 + s.count);
2870
+ if (named) take(role, named.hex, "named");
2871
+ }
2872
+ const ok = (s) => hexToOklch(s.hex);
2873
+ if (!out.has("background")) {
2874
+ const bg = pick(free(), (s) => {
2875
+ const c = ok(s);
2876
+ return c.l >= 0.85 || c.l <= 0.2 ? s.count : 0;
2877
+ });
2878
+ if (bg) take("background", bg.hex, "heuristic");
2879
+ else take("background", "#ffffff", "derived");
2880
+ }
2881
+ const bgHex = out.get("background").hex;
2882
+ const bgDark = hexToOklch(bgHex).l < 0.5;
2883
+ if (!out.has("foreground")) {
2884
+ const fg = pick(free(), (s) => contrastRatio(s.hex, bgHex) >= 4.5 ? s.count * (1 + contrastRatio(s.hex, bgHex) / 21) : 0);
2885
+ if (fg) take("foreground", fg.hex, "heuristic");
2886
+ else take("foreground", bgDark ? "#e6e8ec" : "#1a1a1a", "derived");
2887
+ }
2888
+ const fgHex = out.get("foreground").hex;
2889
+ if (!out.has("primary")) {
2890
+ const p = pick(free(), (s) => {
2891
+ const c = ok(s);
2892
+ return c.c >= 0.09 && contrastRatio(s.hex, bgHex) >= 2 ? s.count : 0;
2893
+ });
2894
+ if (p) take("primary", p.hex, "heuristic");
2895
+ else take("primary", "#2f6df6", "derived");
2896
+ }
2897
+ const primaryHue = hexToOklch(out.get("primary").hex).h ?? 0;
2898
+ if (!out.has("accent")) {
2899
+ const a = pick(free(), (s) => {
2900
+ const c = ok(s);
2901
+ return c.c >= 0.09 && hueDist(c.h ?? 0, primaryHue) >= 40 ? s.count : 0;
2902
+ });
2903
+ if (a) take("accent", a.hex, "heuristic");
2904
+ else {
2905
+ const p = hexToOklch(out.get("primary").hex);
2906
+ take("accent", oklchToHex({ ...p, h: ((p.h ?? 0) + 60) % 360 }), "derived");
2907
+ }
2908
+ }
2909
+ if (!out.has("muted")) {
2910
+ const m = pick(free(), (s) => {
2911
+ const c = ok(s);
2912
+ return c.c < 0.08 && c.l >= 0.3 && c.l <= 0.78 ? s.count : 0;
2913
+ });
2914
+ if (m) take("muted", m.hex, "heuristic");
2915
+ else take("muted", blend(bgHex, fgHex, 0.55), "derived");
2916
+ }
2917
+ if (!out.has("border")) {
2918
+ const bgL = hexToOklch(bgHex).l;
2919
+ const b = pick(free(), (s) => {
2920
+ const c = ok(s);
2921
+ return c.c < 0.08 && Math.abs(c.l - bgL) > 0.02 && Math.abs(c.l - bgL) < 0.25 ? s.count : 0;
2922
+ });
2923
+ if (b) take("border", b.hex, "heuristic");
2924
+ else take("border", blend(bgHex, fgHex, 0.12), "derived");
2925
+ }
2926
+ return ROLES2.map((role) => ({ role, ...out.get(role) }));
2927
+ }
2928
+ var mode = (values) => {
2929
+ const counts = /* @__PURE__ */ new Map();
2930
+ for (const v of values) counts.set(v, (counts.get(v) ?? 0) + 1);
2931
+ return pick([...counts.keys()], (k) => counts.get(k));
2932
+ };
2933
+ function scanProject(targets) {
2934
+ const signals = /* @__PURE__ */ new Map();
2935
+ const radii = [];
2936
+ const spacings = [];
2937
+ let fontFamily;
2938
+ let fontSizeBase;
2939
+ let filesScanned = 0;
2940
+ for (const target of targets) {
2941
+ for (const file of collectFiles(resolve2(target))) {
2942
+ let text;
2943
+ try {
2944
+ text = readFileSync2(file, "utf8");
2945
+ } catch {
2946
+ continue;
2947
+ }
2948
+ filesScanned++;
2949
+ const isCss = /\.(css|scss|sass|less)$/.test(file);
2950
+ scanSignals(text, isTailwindConfig(file) ? 5 : isCss ? 3 : 1, signals);
2951
+ for (const m of text.matchAll(RADIUS_RE)) {
2952
+ const px = Number(m[1]);
2953
+ if (px > 0 && px <= 24) radii.push(px);
2954
+ }
2955
+ for (const m of text.matchAll(SPACING_VAR_RE)) {
2956
+ const px = Number(m[1]);
2957
+ if (px >= 2 && px <= 8) spacings.push(px);
2958
+ }
2959
+ if (!fontFamily && (isCss || isTailwindConfig(file))) {
2960
+ const m = FONT_FAMILY_RE.exec(text);
2961
+ if (m) fontFamily = m[1].trim().replace(/["']/g, "").replace(/\s+/g, " ");
2962
+ }
2963
+ if (!fontSizeBase) {
2964
+ const m = FONT_SIZE_RE.exec(text);
2965
+ if (m) {
2966
+ const px = Number(m[1]);
2967
+ if (px >= 12 && px <= 22) fontSizeBase = px;
2968
+ }
2969
+ }
2970
+ }
2971
+ }
2972
+ const all = [...signals.values()].filter((s) => s.count >= 2 || Object.keys(s.hints).length > 0);
2973
+ all.sort((a, b) => b.count - a.count);
2974
+ return {
2975
+ colors: assignRoles(all.slice(0, 64)),
2976
+ radiusBase: mode(radii),
2977
+ spacingBase: spacings.length > 0 ? Math.min(...spacings) : void 0,
2978
+ fontFamily,
2979
+ fontSizeBase,
2980
+ filesScanned
2981
+ };
2982
+ }
2983
+ var PROPORTION = {
2984
+ background: 0.55,
2985
+ foreground: 0.2,
2986
+ primary: 0.1,
2987
+ accent: 0.06,
2988
+ muted: 0.05,
2989
+ border: 0.04
2990
+ };
2991
+ function draftToMarkdown(draft) {
2992
+ const detectedDark = hexToOklch(draft.colors.find((c) => c.role === "background").hex).l < 0.5;
2993
+ const face = (colors, flip) => colors.map(({ role, hex }, i) => {
2994
+ const display = flip ? deriveOppositeHex(hex, role) : hex;
2995
+ return {
2996
+ id: `c${i + 1}`,
2997
+ hex: display,
2998
+ baseHex: display,
2999
+ proportion: PROPORTION[role],
3000
+ role,
3001
+ displayHex: display
3002
+ };
3003
+ });
3004
+ const base = face(draft.colors, detectedDark);
3005
+ const dark = face(draft.colors, !detectedDark);
3006
+ const bg = base.find((c) => c.role === "background").displayHex;
3007
+ const darkBg = dark.find((c) => c.role === "background").displayHex;
3008
+ const typography = {
3009
+ ...defaultTypography,
3010
+ ...draft.fontSizeBase ? { base: draft.fontSizeBase } : {},
3011
+ ...draft.fontFamily ? { fontFamily: draft.fontFamily, headingFamily: draft.fontFamily } : {}
3012
+ };
3013
+ return designSystemMarkdown(
3014
+ base,
3015
+ typography,
3016
+ draft.spacingBase ? { base: draft.spacingBase } : defaultSpacing,
3017
+ draft.radiusBase ? { base: draft.radiusBase } : defaultRadius,
3018
+ defaultShadow,
3019
+ defaultMotion,
3020
+ defaultBorder,
3021
+ defaultOpacity,
3022
+ dark,
3023
+ deriveSemantic(bg),
3024
+ deriveSemantic(darkBg)
3025
+ );
3026
+ }
3027
+ function studioQuery(draft) {
3028
+ const hexes = draft.colors.map((c) => c.hex.slice(1)).join("-");
3029
+ return `p=${hexes}~${encodeURIComponent("Imported")}~${encodeURIComponent("Derived from your codebase")}`;
3030
+ }
3031
+
2390
3032
  // src/cli.ts
2391
3033
  var FORMATS = ["css", "tailwind", "w3c"];
2392
3034
  function fail(msg) {
2393
3035
  console.error(`\u2717 ${msg}`);
2394
3036
  process.exit(1);
2395
3037
  }
3038
+ var BOOL_FLAGS = /* @__PURE__ */ new Set(["force", "global"]);
2396
3039
  function parseArgs(argv) {
2397
3040
  const positional = [];
2398
3041
  const opts = {};
2399
3042
  for (let i = 0; i < argv.length; i++) {
2400
3043
  const a = argv[i];
2401
- if (a.startsWith("--")) opts[a.slice(2)] = argv[++i] ?? "";
2402
- else positional.push(a);
3044
+ if (a.startsWith("--")) {
3045
+ const name = a.slice(2);
3046
+ opts[name] = BOOL_FLAGS.has(name) ? "true" : argv[++i] ?? "";
3047
+ } else positional.push(a);
2403
3048
  }
2404
3049
  return { positional, opts };
2405
3050
  }
@@ -2412,7 +3057,7 @@ function readMd(file) {
2412
3057
  )
2413
3058
  );
2414
3059
  try {
2415
- return readFileSync2(resolve2(file), "utf8");
3060
+ return readFileSync3(resolve3(file), "utf8");
2416
3061
  } catch {
2417
3062
  return fail(t(`Cannot read file: ${file}`, `\u8BFB\u4E0D\u5230\u6587\u4EF6\uFF1A${file}`));
2418
3063
  }
@@ -2421,7 +3066,7 @@ function cmdInspect(file) {
2421
3066
  const { w3c } = parseDesignSystem(readMd(file));
2422
3067
  const color = w3c.color ?? {};
2423
3068
  const typo = w3c.typography ?? {};
2424
- const ext = typo.$extensions?.["design-system"] ?? {};
3069
+ const ext = typo.$extensions?.["design-pact"] ?? {};
2425
3070
  console.log(t("Design system summary", "\u8BBE\u8BA1\u7CFB\u7EDF\u6458\u8981"));
2426
3071
  console.log(t(" Colors:", " \u989C\u8272\uFF1A"));
2427
3072
  for (const [role, v] of Object.entries(color)) console.log(` ${role.padEnd(12)} ${v.$value}`);
@@ -2436,7 +3081,7 @@ function cmdAdd(file, opts) {
2436
3081
  if (fmt !== "all" && !FORMATS.includes(fmt))
2437
3082
  fail(t(`Unknown format: ${fmt} (css|tailwind|w3c|all)`, `\u672A\u77E5\u683C\u5F0F\uFF1A${fmt}\uFF08css|tailwind|w3c|all\uFF09`));
2438
3083
  const want = fmt === "all" ? FORMATS : [fmt];
2439
- const outDir = resolve2(opts.out || ".");
3084
+ const outDir = resolve3(opts.out || ".");
2440
3085
  mkdirSync(outDir, { recursive: true });
2441
3086
  const written = [];
2442
3087
  const write = (name, content) => {
@@ -2471,6 +3116,36 @@ async function main() {
2471
3116
  const result = runCheck(ds.w3c, targets.length > 0 ? targets : ["."], allow);
2472
3117
  process.exit(reportCheck(result));
2473
3118
  }
3119
+ case "import": {
3120
+ const targets = positional.length > 0 ? positional : ["."];
3121
+ const outPath = resolve3(opts.out || "design.md");
3122
+ if (existsSync2(outPath) && !opts.force)
3123
+ fail(t(`${outPath} already exists \u2014 pass --force to overwrite.`, `${outPath} \u5DF2\u5B58\u5728\u2014\u2014\u7528 --force \u8986\u76D6\u3002`));
3124
+ const draft = scanProject(targets);
3125
+ writeFileSync(outPath, draftToMarkdown(draft), "utf8");
3126
+ console.log(t(
3127
+ `\u2713 Draft design.md written to ${outPath} (${draft.filesScanned} file(s) scanned)`,
3128
+ `\u2713 design.md \u8349\u7A3F\u5DF2\u5199\u5165 ${outPath}\uFF08\u626B\u63CF ${draft.filesScanned} \u4E2A\u6587\u4EF6\uFF09`
3129
+ ));
3130
+ console.log(t(" Detected palette:", " \u8BC6\u522B\u51FA\u7684\u8272\u677F\uFF1A"));
3131
+ const provenanceLabel = {
3132
+ named: ["from a named variable", "\u6765\u81EA\u547D\u540D\u53D8\u91CF"],
3133
+ heuristic: ["by usage heuristic", "\u6309\u4F7F\u7528\u9891\u7387\u63A8\u65AD"],
3134
+ derived: ["derived (no signal found)", "\u6D3E\u751F\uFF08\u672A\u627E\u5230\u4FE1\u53F7\uFF09"]
3135
+ };
3136
+ for (const c of draft.colors) {
3137
+ const [en, zh] = provenanceLabel[c.provenance];
3138
+ console.log(` ${c.role.padEnd(12)} ${c.hex} ${t(en, zh)}`);
3139
+ }
3140
+ const q = studioQuery(draft);
3141
+ console.log("\n" + t(
3142
+ "Review it visually before adopting \u2014 heuristic/derived roles deserve a look:",
3143
+ "\u91C7\u7528\u524D\u5EFA\u8BAE\u5148\u5230 studio \u91CC\u8FC7\u76EE\u2014\u2014\u63A8\u65AD/\u6D3E\u751F\u7684\u89D2\u8272\u503C\u5F97\u786E\u8BA4\uFF1A"
3144
+ ));
3145
+ console.log(` https://design-pact.vercel.app/?${q}`);
3146
+ console.log(t(` or locally: npx design-pact open "${q}"`, ` \u6216\u672C\u5730\u6253\u5F00\uFF1Anpx design-pact open "${q}"`));
3147
+ return;
3148
+ }
2474
3149
  case void 0:
2475
3150
  case "-h":
2476
3151
  case "--help":
@@ -2501,6 +3176,10 @@ async function main() {
2501
3176
  ' check <file> [paths\u2026] [--allow "#hex,#hex"] find color literals outside the contract',
2502
3177
  ' check <file> [paths\u2026] [--allow "#hex,#hex"] \u627E\u51FA\u5951\u7EA6\u5916\u7684\u989C\u8272\u5B57\u9762\u91CF'
2503
3178
  ),
3179
+ t(
3180
+ " import [paths\u2026] [--out design.md] [--force] derive a draft design.md from existing code",
3181
+ " import [paths\u2026] [--out design.md] [--force] \u4ECE\u73B0\u6709\u4EE3\u7801\u53CD\u63A8 design.md \u8349\u7A3F"
3182
+ ),
2504
3183
  "",
2505
3184
  t(
2506
3185
  'design.md is exported from the studio ("Download design.md").',
@@ -2510,7 +3189,7 @@ async function main() {
2510
3189
  );
2511
3190
  return;
2512
3191
  default:
2513
- fail(t(`Unknown command: ${cmd} (init|open|add|inspect|check)`, `\u672A\u77E5\u547D\u4EE4\uFF1A${cmd}\uFF08init|open|add|inspect|check\uFF09`));
3192
+ fail(t(`Unknown command: ${cmd} (init|open|add|inspect|check|import)`, `\u672A\u77E5\u547D\u4EE4\uFF1A${cmd}\uFF08init|open|add|inspect|check|import\uFF09`));
2514
3193
  }
2515
3194
  }
2516
3195
  main().catch((e) => fail(e instanceof Error ? e.message : String(e)));