@nurix/etna 0.19.11 → 0.19.13

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 (494) hide show
  1. package/README.md +6 -4
  2. package/data/harness-index.json +1 -1
  3. package/data/harnesses/code/agents/maintenance.md +1 -1
  4. package/data/harnesses/code/harness.json +1 -1
  5. package/data/harnesses/code/rules/frontend.md +5 -5
  6. package/data/harnesses/code/skills/design/SKILL.md +70 -50
  7. package/data/harnesses/code/skills/design/components/charts.md +76 -0
  8. package/data/harnesses/code/skills/design/components/collections.md +132 -0
  9. package/data/harnesses/code/skills/design/components/feedback.md +47 -0
  10. package/data/harnesses/code/skills/design/components/hooks.md +35 -0
  11. package/data/harnesses/code/skills/design/components/index.md +143 -0
  12. package/data/harnesses/code/skills/design/components/inputs.md +327 -0
  13. package/data/harnesses/code/skills/design/components/layout.md +52 -0
  14. package/data/harnesses/code/skills/design/components/media.md +66 -0
  15. package/data/harnesses/code/skills/design/components/motion.md +82 -0
  16. package/data/harnesses/code/skills/design/components/navigation.md +29 -0
  17. package/data/harnesses/code/skills/design/components/overlays.md +72 -0
  18. package/data/harnesses/code/skills/design/components/setup.md +40 -0
  19. package/data/harnesses/code/skills/design/components/typography.md +46 -0
  20. package/data/harnesses/code/skills/design/components/utils.md +37 -0
  21. package/data/harnesses/code/skills/design/data/blueprints/browse.md +38 -0
  22. package/data/harnesses/code/skills/design/data/blueprints/creation-flow.md +38 -0
  23. package/data/harnesses/code/skills/design/data/blueprints/dashboard.md +38 -0
  24. package/data/harnesses/code/skills/design/data/blueprints/grid.md +38 -0
  25. package/data/harnesses/code/skills/design/data/blueprints/queue.md +38 -0
  26. package/data/harnesses/code/skills/design/data/blueprints/record-detail.md +38 -0
  27. package/data/harnesses/code/skills/design/data/blueprints/settings.md +38 -0
  28. package/data/harnesses/code/skills/design/data/chart-styling/Readme.md +33 -0
  29. package/data/harnesses/code/skills/design/data/chart-styling/carbon-accessible-alert-semantic.md +19 -0
  30. package/data/harnesses/code/skills/design/data/chart-styling/carbon-monochrome-sequential-trend.md +19 -0
  31. package/data/harnesses/code/skills/design/data/chart-styling/datadog-classic-six-aligned.md +19 -0
  32. package/data/harnesses/code/skills/design/data/chart-styling/datawrapper-tiered-gray-hierarchy.md +19 -0
  33. package/data/harnesses/code/skills/design/data/chart-styling/econ-red-rule-horizontal.md +19 -0
  34. package/data/harnesses/code/skills/design/data/chart-styling/financial-diverging-threshold-overlay.md +19 -0
  35. package/data/harnesses/code/skills/design/data/chart-styling/ft-vocabulary-ranked-muted.md +19 -0
  36. package/data/harnesses/code/skills/design/data/chart-styling/grafana-dark-threshold-bands.md +19 -0
  37. package/data/harnesses/code/skills/design/data/chart-styling/material-rounded-explicit-label.md +19 -0
  38. package/data/harnesses/code/skills/design/data/chart-styling/nyt-ghost-grid-direct-label.md +19 -0
  39. package/data/harnesses/code/skills/design/data/chart-styling/plot-currentcolor-inherit.md +19 -0
  40. package/data/harnesses/code/skills/design/data/chart-styling/saas-gradient-glow-area.md +19 -0
  41. package/data/harnesses/code/skills/design/data/chart-styling/shadcn-composable-css-vars.md +19 -0
  42. package/data/harnesses/code/skills/design/data/charts/census.csv +58 -0
  43. package/data/harnesses/code/skills/design/data/checklists/browse.md +21 -0
  44. package/data/harnesses/code/skills/design/data/checklists/creation-flow.md +20 -0
  45. package/data/harnesses/code/skills/design/data/checklists/dashboard.md +21 -0
  46. package/data/harnesses/code/skills/design/data/checklists/grid.md +21 -0
  47. package/data/harnesses/code/skills/design/data/checklists/queue.md +21 -0
  48. package/data/harnesses/code/skills/design/data/checklists/record-detail.md +21 -0
  49. package/data/harnesses/code/skills/design/data/checklists/settings.md +21 -0
  50. package/data/harnesses/code/skills/design/data/chrome/board-column.md +68 -0
  51. package/data/harnesses/code/skills/design/data/chrome/data-table.md +63 -0
  52. package/data/harnesses/code/skills/design/data/chrome/side-panel.md +60 -0
  53. package/data/harnesses/code/skills/design/data/chrome/sidebar.md +78 -0
  54. package/data/harnesses/code/skills/design/data/chrome/stat-cards.md +62 -0
  55. package/data/harnesses/code/skills/design/data/chrome/topbar.md +65 -0
  56. package/data/harnesses/code/skills/design/data/color/accent-presets.md +90 -0
  57. package/data/harnesses/code/skills/design/data/color/aesop-earth-mute.md +91 -0
  58. package/data/harnesses/code/skills/design/data/color/arc-peach-play.md +90 -0
  59. package/data/harnesses/code/skills/design/data/color/attio-hunter-teal.md +90 -0
  60. package/data/harnesses/code/skills/design/data/color/carbon-cyan-signal.md +90 -0
  61. package/data/harnesses/code/skills/design/data/color/clay-parchment.md +90 -0
  62. package/data/harnesses/code/skills/design/data/color/duolingo-feather.md +90 -0
  63. package/data/harnesses/code/skills/design/data/color/geist-mono-signal.md +90 -0
  64. package/data/harnesses/code/skills/design/data/color/linear-graphite-violet.md +90 -0
  65. package/data/harnesses/code/skills/design/data/color/mercury-onyx-cobalt.md +90 -0
  66. package/data/harnesses/code/skills/design/data/color/notion-paper-ink.md +90 -0
  67. package/data/harnesses/code/skills/design/data/color/radix-slate-iris.md +90 -0
  68. package/data/harnesses/code/skills/design/data/color/raycast-void-spectrum.md +90 -0
  69. package/data/harnesses/code/skills/design/data/color/spectrum-celery-fuchsia.md +90 -0
  70. package/data/harnesses/code/skills/design/data/color/stripe-blurple-ruby.md +90 -0
  71. package/data/harnesses/code/skills/design/data/color/superhuman-cod-picton.md +89 -0
  72. package/data/harnesses/code/skills/design/data/corpora/operations-review/companies.csv +15 -0
  73. package/data/harnesses/code/skills/design/data/corpora/operations-review/corpus.md +87 -0
  74. package/data/harnesses/code/skills/design/data/corpora/operations-review/people.csv +25 -0
  75. package/data/harnesses/code/skills/design/data/corpora/procurement-approvals/companies.csv +15 -0
  76. package/data/harnesses/code/skills/design/data/corpora/procurement-approvals/corpus.md +83 -0
  77. package/data/harnesses/code/skills/design/data/corpora/procurement-approvals/people.csv +25 -0
  78. package/data/harnesses/code/skills/design/data/corpora/revenue-analytics/companies.csv +15 -0
  79. package/data/harnesses/code/skills/design/data/corpora/revenue-analytics/corpus.md +89 -0
  80. package/data/harnesses/code/skills/design/data/corpora/revenue-analytics/people.csv +25 -0
  81. package/data/harnesses/code/skills/design/data/elevation/ant-design-directional-shadow.md +19 -0
  82. package/data/harnesses/code/skills/design/data/elevation/apple-liquid-glass-material.md +18 -0
  83. package/data/harnesses/code/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +20 -0
  84. package/data/harnesses/code/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +20 -0
  85. package/data/harnesses/code/skills/design/data/elevation/github-primer-sparing-shadow.md +19 -0
  86. package/data/harnesses/code/skills/design/data/elevation/ibm-carbon-layer-and-border.md +19 -0
  87. package/data/harnesses/code/skills/design/data/elevation/linear-dark-surface-ladder.md +17 -0
  88. package/data/harnesses/code/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +19 -0
  89. package/data/harnesses/code/skills/design/data/elevation/material-3-tonal-elevation.md +21 -0
  90. package/data/harnesses/code/skills/design/data/elevation/neobrutalist-hard-offset.md +19 -0
  91. package/data/harnesses/code/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +18 -0
  92. package/data/harnesses/code/skills/design/data/elevation/notion-warm-minimal-hairline.md +17 -0
  93. package/data/harnesses/code/skills/design/data/elevation/polaris-elevation-inset-bevel.md +21 -0
  94. package/data/harnesses/code/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +19 -0
  95. package/data/harnesses/code/skills/design/data/elevation/shadcn-neutral-derived-radius.md +19 -0
  96. package/data/harnesses/code/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +18 -0
  97. package/data/harnesses/code/skills/design/data/elevation/tailwind-default-utility-scale.md +19 -0
  98. package/data/harnesses/code/skills/design/data/elevation/vercel-geist-shadow-as-border.md +20 -0
  99. package/data/harnesses/code/skills/design/data/gradients/amber-attention-fill.md +16 -0
  100. package/data/harnesses/code/skills/design/data/gradients/cloud-shadow-wash.md +16 -0
  101. package/data/harnesses/code/skills/design/data/gradients/confident-lavender-wash.md +16 -0
  102. package/data/harnesses/code/skills/design/data/gradients/ember-ship-flare.md +18 -0
  103. package/data/harnesses/code/skills/design/data/gradients/full-metal-wash.md +16 -0
  104. package/data/harnesses/code/skills/design/data/gradients/indigo-ruby-cta-band.md +18 -0
  105. package/data/harnesses/code/skills/design/data/gradients/linear-graphite-wash.md +18 -0
  106. package/data/harnesses/code/skills/design/data/gradients/marble-quartz-wash.md +16 -0
  107. package/data/harnesses/code/skills/design/data/gradients/midnight-aurora-panel.md +16 -0
  108. package/data/harnesses/code/skills/design/data/gradients/neutral-volume-fill.md +16 -0
  109. package/data/harnesses/code/skills/design/data/gradients/revenue-uptrend-fill.md +16 -0
  110. package/data/harnesses/code/skills/design/data/gradients/risk-decline-fill.md +16 -0
  111. package/data/harnesses/code/skills/design/data/gradients/signal-blue-develop.md +18 -0
  112. package/data/harnesses/code/skills/design/data/gradients/slate-mist-wash.md +16 -0
  113. package/data/harnesses/code/skills/design/data/gradients/stripe-atmosphere-mesh.md +18 -0
  114. package/data/harnesses/code/skills/design/data/gradients/teal-signal-accent.md +16 -0
  115. package/data/harnesses/code/skills/design/data/gradients/violet-magenta-promo.md +16 -0
  116. package/data/harnesses/code/skills/design/data/iconography.md +199 -0
  117. package/data/harnesses/code/skills/design/data/motion/accordion-expand.md +18 -0
  118. package/data/harnesses/code/skills/design/data/motion/destructive-hold-confirm.md +18 -0
  119. package/data/harnesses/code/skills/design/data/motion/drag-dismiss-momentum.md +18 -0
  120. package/data/harnesses/code/skills/design/data/motion/empty-state-arrive.md +18 -0
  121. package/data/harnesses/code/skills/design/data/motion/hover-lift.md +18 -0
  122. package/data/harnesses/code/skills/design/data/motion/modal-center-arrive.md +18 -0
  123. package/data/harnesses/code/skills/design/data/motion/number-ticker-roll.md +18 -0
  124. package/data/harnesses/code/skills/design/data/motion/onboarding-bounce.md +18 -0
  125. package/data/harnesses/code/skills/design/data/motion/panel-slide.md +18 -0
  126. package/data/harnesses/code/skills/design/data/motion/press-feedback.md +18 -0
  127. package/data/harnesses/code/skills/design/data/motion/progress-linear-loop.md +18 -0
  128. package/data/harnesses/code/skills/design/data/motion/route-crossfade.md +18 -0
  129. package/data/harnesses/code/skills/design/data/motion/rubber-band-overscroll.md +18 -0
  130. package/data/harnesses/code/skills/design/data/motion/skeleton-shimmer.md +18 -0
  131. package/data/harnesses/code/skills/design/data/motion/stagger-arrive.md +18 -0
  132. package/data/harnesses/code/skills/design/data/motion/toast-enter-stack.md +18 -0
  133. package/data/harnesses/code/skills/design/data/motion/tooltip-chain-instant.md +18 -0
  134. package/data/harnesses/code/skills/design/data/motion/trigger-anchored-reveal.md +18 -0
  135. package/data/harnesses/code/skills/design/data/tells.csv +34 -0
  136. package/data/harnesses/code/skills/design/data/typography/anton-work-sans.md +17 -0
  137. package/data/harnesses/code/skills/design/data/typography/archivo-black-archivo.md +17 -0
  138. package/data/harnesses/code/skills/design/data/typography/bitter-karla.md +17 -0
  139. package/data/harnesses/code/skills/design/data/typography/dm-serif-dm-sans.md +17 -0
  140. package/data/harnesses/code/skills/design/data/typography/fraunces-inter.md +17 -0
  141. package/data/harnesses/code/skills/design/data/typography/geist-geist-mono.md +17 -0
  142. package/data/harnesses/code/skills/design/data/typography/hubot-sans-mona-sans.md +17 -0
  143. package/data/harnesses/code/skills/design/data/typography/ibm-plex-sans-operator-single-family.md +17 -0
  144. package/data/harnesses/code/skills/design/data/typography/instrument-serif-instrument-sans.md +17 -0
  145. package/data/harnesses/code/skills/design/data/typography/inter-operator-single-family.md +17 -0
  146. package/data/harnesses/code/skills/design/data/typography/libre-baskerville-libre-franklin.md +17 -0
  147. package/data/harnesses/code/skills/design/data/typography/newsreader-inter.md +17 -0
  148. package/data/harnesses/code/skills/design/data/typography/playfair-display-source-sans.md +17 -0
  149. package/data/harnesses/code/skills/design/data/typography/public-sans-operator-single-family.md +17 -0
  150. package/data/harnesses/code/skills/design/data/typography/sohne-inter-fintech.md +17 -0
  151. package/data/harnesses/code/skills/design/data/typography/source-serif-source-sans.md +17 -0
  152. package/data/harnesses/code/skills/design/data/typography/space-grotesk-work-sans-space-mono.md +17 -0
  153. package/data/harnesses/code/skills/design/data/typography/spectral-manrope.md +17 -0
  154. package/data/harnesses/code/skills/design/data/voices/bright-collaborator.md +29 -0
  155. package/data/harnesses/code/skills/design/data/voices/direct-operator.md +29 -0
  156. package/data/harnesses/code/skills/design/data/voices/earnest-helper.md +29 -0
  157. package/data/harnesses/code/skills/design/data/voices/editorial-composed.md +29 -0
  158. package/data/harnesses/code/skills/design/data/voices/quiet-concierge.md +29 -0
  159. package/data/harnesses/code/skills/design/data/voices/warm-guide.md +29 -0
  160. package/data/harnesses/code/skills/design/design-md/SCHEMA.md +179 -0
  161. package/data/harnesses/code/skills/design/design-md/index.csv +67 -0
  162. package/data/harnesses/code/skills/design/design-md/library/airbnb.md +207 -0
  163. package/data/harnesses/code/skills/design/design-md/library/airtable.md +205 -0
  164. package/data/harnesses/code/skills/design/design-md/library/anthropic-claude.md +203 -0
  165. package/data/harnesses/code/skills/design/design-md/library/apple.md +206 -0
  166. package/data/harnesses/code/skills/design/design-md/library/binance.md +210 -0
  167. package/data/harnesses/code/skills/design/design-md/library/bmw.md +210 -0
  168. package/data/harnesses/code/skills/design/design-md/library/cal.md +209 -0
  169. package/data/harnesses/code/skills/design/design-md/library/clay.md +204 -0
  170. package/data/harnesses/code/skills/design/design-md/library/clickhouse.md +205 -0
  171. package/data/harnesses/code/skills/design/design-md/library/cohere.md +205 -0
  172. package/data/harnesses/code/skills/design/design-md/library/coinbase.md +198 -0
  173. package/data/harnesses/code/skills/design/design-md/library/composio.md +198 -0
  174. package/data/harnesses/code/skills/design/design-md/library/cursor.md +197 -0
  175. package/data/harnesses/code/skills/design/design-md/library/elevenlabs.md +199 -0
  176. package/data/harnesses/code/skills/design/design-md/library/expo.md +200 -0
  177. package/data/harnesses/code/skills/design/design-md/library/figma.md +199 -0
  178. package/data/harnesses/code/skills/design/design-md/library/framer.md +202 -0
  179. package/data/harnesses/code/skills/design/design-md/library/hashicorp.md +205 -0
  180. package/data/harnesses/code/skills/design/design-md/library/ibm.md +205 -0
  181. package/data/harnesses/code/skills/design/design-md/library/intercom.md +204 -0
  182. package/data/harnesses/code/skills/design/design-md/library/kraken.md +200 -0
  183. package/data/harnesses/code/skills/design/design-md/library/linear-app.md +198 -0
  184. package/data/harnesses/code/skills/design/design-md/library/lovable.md +199 -0
  185. package/data/harnesses/code/skills/design/design-md/library/meta.md +204 -0
  186. package/data/harnesses/code/skills/design/design-md/library/minimax.md +204 -0
  187. package/data/harnesses/code/skills/design/design-md/library/mintlify.md +210 -0
  188. package/data/harnesses/code/skills/design/design-md/library/miro.md +209 -0
  189. package/data/harnesses/code/skills/design/design-md/library/mistral-ai.md +201 -0
  190. package/data/harnesses/code/skills/design/design-md/library/mongodb.md +201 -0
  191. package/data/harnesses/code/skills/design/design-md/library/notion.md +204 -0
  192. package/data/harnesses/code/skills/design/design-md/library/nvidia.md +215 -0
  193. package/data/harnesses/code/skills/design/design-md/library/ollama.md +213 -0
  194. package/data/harnesses/code/skills/design/design-md/library/opencode-ai.md +216 -0
  195. package/data/harnesses/code/skills/design/design-md/library/posthog.md +218 -0
  196. package/data/harnesses/code/skills/design/design-md/library/raycast.md +214 -0
  197. package/data/harnesses/code/skills/design/design-md/library/replicate.md +202 -0
  198. package/data/harnesses/code/skills/design/design-md/library/resend.md +203 -0
  199. package/data/harnesses/code/skills/design/design-md/library/revolut.md +203 -0
  200. package/data/harnesses/code/skills/design/design-md/library/runwayml.md +203 -0
  201. package/data/harnesses/code/skills/design/design-md/library/sanity.md +200 -0
  202. package/data/harnesses/code/skills/design/design-md/library/sentry.md +201 -0
  203. package/data/harnesses/code/skills/design/design-md/library/shopify.md +202 -0
  204. package/data/harnesses/code/skills/design/design-md/library/slack.md +202 -0
  205. package/data/harnesses/code/skills/design/design-md/library/spacex.md +206 -0
  206. package/data/harnesses/code/skills/design/design-md/library/spotify.md +206 -0
  207. package/data/harnesses/code/skills/design/design-md/library/stripe.md +196 -0
  208. package/data/harnesses/code/skills/design/design-md/library/supabase.md +196 -0
  209. package/data/harnesses/code/skills/design/design-md/library/superhuman.md +194 -0
  210. package/data/harnesses/code/skills/design/design-md/library/tesla.md +198 -0
  211. package/data/harnesses/code/skills/design/design-md/library/theverge.md +199 -0
  212. package/data/harnesses/code/skills/design/design-md/library/together-ai.md +193 -0
  213. package/data/harnesses/code/skills/design/design-md/library/uber.md +195 -0
  214. package/data/harnesses/code/skills/design/design-md/library/vercel.md +195 -0
  215. package/data/harnesses/code/skills/design/design-md/library/voltagent.md +197 -0
  216. package/data/harnesses/code/skills/design/design-md/library/warp.md +196 -0
  217. package/data/harnesses/code/skills/design/design-md/library/webflow.md +197 -0
  218. package/data/harnesses/code/skills/design/design-md/library/wired.md +197 -0
  219. package/data/harnesses/code/skills/design/design-md/library/wise.md +198 -0
  220. package/data/harnesses/code/skills/design/design-md/library/x-ai.md +199 -0
  221. package/data/harnesses/code/skills/design/design-md/library/zapier.md +198 -0
  222. package/data/harnesses/code/skills/design/design-md/nurix.md +214 -0
  223. package/data/harnesses/code/skills/design/design-md/variants/editorial-luxury.md +184 -0
  224. package/data/harnesses/code/skills/design/design-md/variants/expressive-saas.md +216 -0
  225. package/data/harnesses/code/skills/design/design-md/variants/quiet-premium.md +211 -0
  226. package/data/harnesses/code/skills/design/design-md/variants/terse-dense-operator.md +212 -0
  227. package/data/harnesses/code/skills/design/design-md/variants/warm-approachable.md +216 -0
  228. package/data/harnesses/code/skills/design/references/compose.md +23 -0
  229. package/data/harnesses/code/skills/design/references/content.md +28 -0
  230. package/data/harnesses/code/skills/design/references/emission.md +21 -0
  231. package/data/harnesses/code/skills/design/references/existing-apps.md +27 -0
  232. package/data/harnesses/code/skills/design/references/intake.md +36 -0
  233. package/data/harnesses/code/skills/design/references/object-pass.md +24 -0
  234. package/data/harnesses/code/skills/design/references/ship-gate.md +29 -0
  235. package/data/harnesses/code/skills/design/references/ux-patterns/Readme.md +195 -0
  236. package/data/harnesses/code/skills/design/references/ux-patterns/ai-layer.md +49 -0
  237. package/data/harnesses/code/skills/design/references/ux-patterns/attributes-lifecycle.md +43 -0
  238. package/data/harnesses/code/skills/design/references/ux-patterns/container-choice.md +44 -0
  239. package/data/harnesses/code/skills/design/references/ux-patterns/creation-paths.md +46 -0
  240. package/data/harnesses/code/skills/design/references/ux-patterns/dashboards.md +42 -0
  241. package/data/harnesses/code/skills/design/references/ux-patterns/modes.md +42 -0
  242. package/data/harnesses/code/skills/design/references/ux-patterns/object-model.md +46 -0
  243. package/data/harnesses/code/skills/design/references/ux-patterns/page-archetype.md +48 -0
  244. package/data/harnesses/code/skills/design/references/ux-patterns/promotion-demotion.md +37 -0
  245. package/data/harnesses/code/skills/design/references/ux-patterns/settings.md +42 -0
  246. package/data/harnesses/code/skills/design/references/ux-patterns/standard-pages.md +46 -0
  247. package/data/harnesses/code/skills/design/references/ux-patterns/state-matrix.md +41 -0
  248. package/data/harnesses/code/skills/design/references/ux-patterns/table-mechanics.md +48 -0
  249. package/data/harnesses/code/skills/design/references/ux-patterns/verbs.md +45 -0
  250. package/data/harnesses/code/skills/design/references/ux-patterns/viewer-context.md +40 -0
  251. package/data/harnesses/code/skills/design/scripts/README-validator.md +27 -0
  252. package/data/harnesses/code/skills/design/scripts/select-chart.mjs +596 -0
  253. package/data/harnesses/code/skills/design/scripts/validate-design-md.mjs +1198 -0
  254. package/data/harnesses/discussion/agents/discussion-baseline.md +1 -1
  255. package/data/harnesses/discussion/harness.json +1 -1
  256. package/data/harnesses/etna/agents/maintenance.md +1 -1
  257. package/data/harnesses/etna/harness.json +1 -1
  258. package/data/harnesses/hr/CLAUDE.md +1 -1
  259. package/data/harnesses/hr/harness.json +1 -1
  260. package/package.json +1 -1
  261. package/data/harnesses/code/skills/design/data/catalog/rules.csv +0 -137
  262. package/data/harnesses/code/skills/design/data/combinations/Readme.md +0 -33
  263. package/data/harnesses/code/skills/design/data/combinations/accent-presets.md +0 -113
  264. package/data/harnesses/code/skills/design/data/combinations/charts.csv +0 -33
  265. package/data/harnesses/code/skills/design/data/combinations/fonts-known.json +0 -448
  266. package/data/harnesses/code/skills/design/data/combinations/icons.csv +0 -157
  267. package/data/harnesses/code/skills/design/data/combinations/landing.csv +0 -38
  268. package/data/harnesses/code/skills/design/data/combinations/lucide-names.json +0 -2001
  269. package/data/harnesses/code/skills/design/data/combinations/manifest.json +0 -330
  270. package/data/harnesses/code/skills/design/data/combinations/motion.csv +0 -10
  271. package/data/harnesses/code/skills/design/data/combinations/palettes.csv +0 -92
  272. package/data/harnesses/code/skills/design/data/combinations/product-router.csv +0 -80
  273. package/data/harnesses/code/skills/design/data/combinations/styles.csv +0 -67
  274. package/data/harnesses/code/skills/design/data/combinations/typography.csv +0 -64
  275. package/data/harnesses/code/skills/design/data/design-md/SCHEMA.md +0 -106
  276. package/data/harnesses/code/skills/design/data/design-md/library/airbnb.md +0 -154
  277. package/data/harnesses/code/skills/design/data/design-md/library/airtable.md +0 -151
  278. package/data/harnesses/code/skills/design/data/design-md/library/anthropic-claude.md +0 -139
  279. package/data/harnesses/code/skills/design/data/design-md/library/apple.md +0 -139
  280. package/data/harnesses/code/skills/design/data/design-md/library/binance.md +0 -140
  281. package/data/harnesses/code/skills/design/data/design-md/library/bmw.md +0 -140
  282. package/data/harnesses/code/skills/design/data/design-md/library/cal.md +0 -140
  283. package/data/harnesses/code/skills/design/data/design-md/library/clay.md +0 -139
  284. package/data/harnesses/code/skills/design/data/design-md/library/clickhouse.md +0 -139
  285. package/data/harnesses/code/skills/design/data/design-md/library/cohere.md +0 -147
  286. package/data/harnesses/code/skills/design/data/design-md/library/coinbase.md +0 -156
  287. package/data/harnesses/code/skills/design/data/design-md/library/composio.md +0 -138
  288. package/data/harnesses/code/skills/design/data/design-md/library/cursor.md +0 -138
  289. package/data/harnesses/code/skills/design/data/design-md/library/elevenlabs.md +0 -137
  290. package/data/harnesses/code/skills/design/data/design-md/library/expo.md +0 -148
  291. package/data/harnesses/code/skills/design/data/design-md/library/figma.md +0 -141
  292. package/data/harnesses/code/skills/design/data/design-md/library/framer.md +0 -156
  293. package/data/harnesses/code/skills/design/data/design-md/library/hashicorp.md +0 -140
  294. package/data/harnesses/code/skills/design/data/design-md/library/ibm.md +0 -153
  295. package/data/harnesses/code/skills/design/data/design-md/library/intercom.md +0 -158
  296. package/data/harnesses/code/skills/design/data/design-md/library/kraken.md +0 -156
  297. package/data/harnesses/code/skills/design/data/design-md/library/linear-app.md +0 -122
  298. package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +0 -140
  299. package/data/harnesses/code/skills/design/data/design-md/library/meta.md +0 -139
  300. package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +0 -139
  301. package/data/harnesses/code/skills/design/data/design-md/library/mintlify.md +0 -139
  302. package/data/harnesses/code/skills/design/data/design-md/library/miro.md +0 -153
  303. package/data/harnesses/code/skills/design/data/design-md/library/mistral-ai.md +0 -140
  304. package/data/harnesses/code/skills/design/data/design-md/library/mongodb.md +0 -155
  305. package/data/harnesses/code/skills/design/data/design-md/library/notion.md +0 -140
  306. package/data/harnesses/code/skills/design/data/design-md/library/nvidia.md +0 -141
  307. package/data/harnesses/code/skills/design/data/design-md/library/ollama.md +0 -145
  308. package/data/harnesses/code/skills/design/data/design-md/library/opencode-ai.md +0 -142
  309. package/data/harnesses/code/skills/design/data/design-md/library/posthog.md +0 -152
  310. package/data/harnesses/code/skills/design/data/design-md/library/raycast.md +0 -137
  311. package/data/harnesses/code/skills/design/data/design-md/library/replicate.md +0 -140
  312. package/data/harnesses/code/skills/design/data/design-md/library/resend.md +0 -170
  313. package/data/harnesses/code/skills/design/data/design-md/library/revolut.md +0 -145
  314. package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +0 -137
  315. package/data/harnesses/code/skills/design/data/design-md/library/sanity.md +0 -168
  316. package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +0 -163
  317. package/data/harnesses/code/skills/design/data/design-md/library/shopify.md +0 -147
  318. package/data/harnesses/code/skills/design/data/design-md/library/slack.md +0 -158
  319. package/data/harnesses/code/skills/design/data/design-md/library/spacex.md +0 -171
  320. package/data/harnesses/code/skills/design/data/design-md/library/spotify.md +0 -166
  321. package/data/harnesses/code/skills/design/data/design-md/library/stripe.md +0 -160
  322. package/data/harnesses/code/skills/design/data/design-md/library/supabase.md +0 -173
  323. package/data/harnesses/code/skills/design/data/design-md/library/superhuman.md +0 -145
  324. package/data/harnesses/code/skills/design/data/design-md/library/tesla.md +0 -153
  325. package/data/harnesses/code/skills/design/data/design-md/library/theverge.md +0 -167
  326. package/data/harnesses/code/skills/design/data/design-md/library/together-ai.md +0 -140
  327. package/data/harnesses/code/skills/design/data/design-md/library/uber.md +0 -140
  328. package/data/harnesses/code/skills/design/data/design-md/library/vercel.md +0 -140
  329. package/data/harnesses/code/skills/design/data/design-md/library/voltagent.md +0 -130
  330. package/data/harnesses/code/skills/design/data/design-md/library/warp.md +0 -140
  331. package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +0 -140
  332. package/data/harnesses/code/skills/design/data/design-md/library/wired.md +0 -139
  333. package/data/harnesses/code/skills/design/data/design-md/library/wise.md +0 -137
  334. package/data/harnesses/code/skills/design/data/design-md/library/x-ai.md +0 -138
  335. package/data/harnesses/code/skills/design/data/design-md/library/zapier.md +0 -158
  336. package/data/harnesses/code/skills/design/data/design-md/nurix.md +0 -138
  337. package/data/harnesses/code/skills/design/data/design-tokens/SCHEMA.md +0 -695
  338. package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.resolver.json +0 -20
  339. package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.tokens.json +0 -1272
  340. package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.resolver.json +0 -20
  341. package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.tokens.json +0 -1268
  342. package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.resolver.json +0 -20
  343. package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.tokens.json +0 -1273
  344. package/data/harnesses/code/skills/design/data/design-tokens/apple/design.resolver.json +0 -20
  345. package/data/harnesses/code/skills/design/data/design-tokens/apple/design.tokens.json +0 -1262
  346. package/data/harnesses/code/skills/design/data/design-tokens/binance/design.resolver.json +0 -20
  347. package/data/harnesses/code/skills/design/data/design-tokens/binance/design.tokens.json +0 -1257
  348. package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.resolver.json +0 -20
  349. package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.tokens.json +0 -1270
  350. package/data/harnesses/code/skills/design/data/design-tokens/cal/design.resolver.json +0 -20
  351. package/data/harnesses/code/skills/design/data/design-tokens/cal/design.tokens.json +0 -1270
  352. package/data/harnesses/code/skills/design/data/design-tokens/clay/design.resolver.json +0 -20
  353. package/data/harnesses/code/skills/design/data/design-tokens/clay/design.tokens.json +0 -1261
  354. package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.resolver.json +0 -20
  355. package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.tokens.json +0 -1268
  356. package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.resolver.json +0 -20
  357. package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.tokens.json +0 -1281
  358. package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.resolver.json +0 -20
  359. package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.tokens.json +0 -1262
  360. package/data/harnesses/code/skills/design/data/design-tokens/composio/design.resolver.json +0 -20
  361. package/data/harnesses/code/skills/design/data/design-tokens/composio/design.tokens.json +0 -1271
  362. package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.resolver.json +0 -20
  363. package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.tokens.json +0 -1276
  364. package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.resolver.json +0 -20
  365. package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.tokens.json +0 -1293
  366. package/data/harnesses/code/skills/design/data/design-tokens/expo/design.resolver.json +0 -20
  367. package/data/harnesses/code/skills/design/data/design-tokens/expo/design.tokens.json +0 -1270
  368. package/data/harnesses/code/skills/design/data/design-tokens/figma/design.resolver.json +0 -20
  369. package/data/harnesses/code/skills/design/data/design-tokens/figma/design.tokens.json +0 -1276
  370. package/data/harnesses/code/skills/design/data/design-tokens/framer/design.resolver.json +0 -20
  371. package/data/harnesses/code/skills/design/data/design-tokens/framer/design.tokens.json +0 -1267
  372. package/data/harnesses/code/skills/design/data/design-tokens/goldens.json +0 -251
  373. package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.resolver.json +0 -20
  374. package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.tokens.json +0 -1273
  375. package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.resolver.json +0 -20
  376. package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.tokens.json +0 -1286
  377. package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.resolver.json +0 -20
  378. package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.tokens.json +0 -1290
  379. package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.resolver.json +0 -20
  380. package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.tokens.json +0 -1270
  381. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.resolver.json +0 -23
  382. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.dark.json +0 -599
  383. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.json +0 -1272
  384. package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.resolver.json +0 -20
  385. package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.tokens.json +0 -1267
  386. package/data/harnesses/code/skills/design/data/design-tokens/meta/design.resolver.json +0 -20
  387. package/data/harnesses/code/skills/design/data/design-tokens/meta/design.tokens.json +0 -1289
  388. package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.resolver.json +0 -20
  389. package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.tokens.json +0 -1258
  390. package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.resolver.json +0 -20
  391. package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.tokens.json +0 -1284
  392. package/data/harnesses/code/skills/design/data/design-tokens/miro/design.resolver.json +0 -20
  393. package/data/harnesses/code/skills/design/data/design-tokens/miro/design.tokens.json +0 -1291
  394. package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.resolver.json +0 -20
  395. package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.tokens.json +0 -1263
  396. package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.resolver.json +0 -20
  397. package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.tokens.json +0 -1270
  398. package/data/harnesses/code/skills/design/data/design-tokens/notion/design.resolver.json +0 -20
  399. package/data/harnesses/code/skills/design/data/design-tokens/notion/design.tokens.json +0 -1261
  400. package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.resolver.json +0 -20
  401. package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.tokens.json +0 -1209
  402. package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.resolver.json +0 -20
  403. package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.tokens.json +0 -1265
  404. package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.resolver.json +0 -20
  405. package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.tokens.json +0 -1253
  406. package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.resolver.json +0 -20
  407. package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.tokens.json +0 -1268
  408. package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.resolver.json +0 -20
  409. package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.tokens.json +0 -1261
  410. package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.resolver.json +0 -20
  411. package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.tokens.json +0 -1263
  412. package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.resolver.json +0 -20
  413. package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.tokens.json +0 -1266
  414. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.resolver.json +0 -23
  415. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.dark.json +0 -623
  416. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.json +0 -1276
  417. package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.resolver.json +0 -20
  418. package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.tokens.json +0 -1264
  419. package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.resolver.json +0 -20
  420. package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.tokens.json +0 -1268
  421. package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.resolver.json +0 -20
  422. package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.tokens.json +0 -1270
  423. package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.resolver.json +0 -20
  424. package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.tokens.json +0 -1288
  425. package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.resolver.json +0 -20
  426. package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.tokens.json +0 -1272
  427. package/data/harnesses/code/skills/design/data/design-tokens/slack/design.resolver.json +0 -20
  428. package/data/harnesses/code/skills/design/data/design-tokens/slack/design.tokens.json +0 -1265
  429. package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.resolver.json +0 -20
  430. package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.tokens.json +0 -1287
  431. package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.resolver.json +0 -20
  432. package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.tokens.json +0 -1286
  433. package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.resolver.json +0 -20
  434. package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.tokens.json +0 -1285
  435. package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.resolver.json +0 -20
  436. package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.tokens.json +0 -1287
  437. package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.resolver.json +0 -20
  438. package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.tokens.json +0 -1278
  439. package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.resolver.json +0 -20
  440. package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.tokens.json +0 -1269
  441. package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.resolver.json +0 -20
  442. package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.tokens.json +0 -1285
  443. package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.resolver.json +0 -20
  444. package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.tokens.json +0 -1303
  445. package/data/harnesses/code/skills/design/data/design-tokens/uber/design.resolver.json +0 -20
  446. package/data/harnesses/code/skills/design/data/design-tokens/uber/design.tokens.json +0 -1274
  447. package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.resolver.json +0 -20
  448. package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.tokens.json +0 -1296
  449. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.resolver.json +0 -23
  450. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.dark.json +0 -612
  451. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.json +0 -1282
  452. package/data/harnesses/code/skills/design/data/design-tokens/warp/design.resolver.json +0 -20
  453. package/data/harnesses/code/skills/design/data/design-tokens/warp/design.tokens.json +0 -1287
  454. package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.resolver.json +0 -20
  455. package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.tokens.json +0 -1268
  456. package/data/harnesses/code/skills/design/data/design-tokens/wired/design.resolver.json +0 -20
  457. package/data/harnesses/code/skills/design/data/design-tokens/wired/design.tokens.json +0 -1276
  458. package/data/harnesses/code/skills/design/data/design-tokens/wise/design.resolver.json +0 -20
  459. package/data/harnesses/code/skills/design/data/design-tokens/wise/design.tokens.json +0 -1273
  460. package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.resolver.json +0 -20
  461. package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.tokens.json +0 -1296
  462. package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.resolver.json +0 -20
  463. package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.tokens.json +0 -1286
  464. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/command-center.tsx +0 -249
  465. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/create-edit.tsx +0 -249
  466. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/dashboard.tsx +0 -225
  467. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/entity-detail.tsx +0 -249
  468. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/list-detail.tsx +0 -224
  469. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/log-stream.tsx +0 -249
  470. package/data/harnesses/code/skills/design/data/operator-pack/fixtures.md +0 -88
  471. package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +0 -132
  472. package/data/harnesses/code/skills/design/data/operator-pack/trust-rubric.md +0 -76
  473. package/data/harnesses/code/skills/design/references/create.md +0 -173
  474. package/data/harnesses/code/skills/design/references/diagnosis.md +0 -108
  475. package/data/harnesses/code/skills/design/references/hard-rules.md +0 -177
  476. package/data/harnesses/code/skills/design/references/improve.md +0 -116
  477. package/data/harnesses/code/skills/design/references/interaction.md +0 -77
  478. package/data/harnesses/code/skills/design/references/introspect.md +0 -100
  479. package/data/harnesses/code/skills/design/references/lifecycle.md +0 -73
  480. package/data/harnesses/code/skills/design/references/personality.md +0 -575
  481. package/data/harnesses/code/skills/design/references/structure.md +0 -159
  482. package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +0 -1312
  483. package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +0 -1498
  484. package/data/harnesses/code/skills/design/scripts/generate-fixtures.mjs +0 -551
  485. package/data/harnesses/code/skills/design/scripts/lib/checks.mjs +0 -1092
  486. package/data/harnesses/code/skills/design/scripts/lib/golden.mjs +0 -26
  487. package/data/harnesses/code/skills/design/scripts/lib/vocabulary.mjs +0 -65
  488. package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +0 -538
  489. package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +0 -312
  490. package/data/harnesses/code/skills/design/scripts/render-rung.mjs +0 -890
  491. package/data/harnesses/code/skills/design/scripts/scaffold.mjs +0 -769
  492. package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +0 -675
  493. package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +0 -164
  494. package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +0 -439
@@ -1,1312 +0,0 @@
1
- #!/usr/bin/env node
2
- // skills/design/scripts/convert-design-md.mjs — mechanical design.md → design-tokens converter (schema design-tokens/v2, DTCG 2025.10): parses each source's frontmatter + two machine blocks + §3 type ramp, emits design.tokens.json / design.tokens.dark.json / design.resolver.json per data/design-tokens/SCHEMA.md (running the v1→v2 upgrade transform over each converted entry), self-validates (schema rules 2–10 + the v2 groups), offers --upgrade-v2 to run the same idempotent transform over existing artifact directories, and --strip to remove the machine blocks (and their orphaned prose references) from source files once the byte-match gate passes.
3
- //
4
- // Usage:
5
- // node convert-design-md.mjs convert every entry (nurix.md + library/*.md), then validate
6
- // node convert-design-md.mjs --only <slug> convert/validate a single entry
7
- // node convert-design-md.mjs --validate validate existing artifacts only (no conversion)
8
- // node convert-design-md.mjs --upgrade-v2 idempotent v1→v2 migration over data/design-tokens/*/, then validate
9
- // node convert-design-md.mjs --report <file> also write the JSON conversion report to <file>
10
- // node convert-design-md.mjs --strip <file...> remove both machine blocks + orphaned prose from the named
11
- // .md files IN PLACE (explicit paths required — never implicit)
12
-
13
- import { mkdirSync, readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
14
- import { basename, dirname, join, resolve } from "node:path";
15
- import { fileURLToPath } from "node:url";
16
-
17
- const scriptsDir = dirname(fileURLToPath(import.meta.url));
18
- const skillDir = dirname(scriptsDir);
19
- const designMdDir = join(skillDir, "data", "design-md");
20
- const tokensDir = join(skillDir, "data", "design-tokens");
21
-
22
- // ---------------------------------------------------------------------------
23
- // Vocabulary (names are law — mirrors data/design-tokens/SCHEMA.md)
24
- // ---------------------------------------------------------------------------
25
-
26
- // The 26 v1 source primitives in emission order, with their DTCG $type (the design.md machine
27
- // blocks carry these; primitive.stagger-unit is added by the v1→v2 upgrade transform).
28
- const PRIMITIVE_TYPES = new Map([
29
- ["radius", "dimension"], ["shadow-intensity", "number"], ["border-weight", "dimension"],
30
- ["density-gap", "number"], ["density-inset", "number"],
31
- ["font-sans", "fontFamily"], ["font-mono", "fontFamily"],
32
- ["font-weight-normal", "fontWeight"], ["font-weight-strong", "fontWeight"],
33
- ["heading-tracking", "number"],
34
- ["duration-unit", "duration"], ["duration-fast", "duration"], ["duration-normal", "duration"],
35
- ["duration-slow", "duration"], ["ease-default", "cubicBezier"],
36
- ["contrast-boost", "number"],
37
- ["icon-stroke", "number"], ["icon-size-base", "dimension"],
38
- ["sidebar-width-expanded", "dimension"], ["sidebar-width-collapsed", "dimension"],
39
- ["topbar-height", "dimension"], ["side-panel-width", "dimension"],
40
- ["side-panel-width-rich", "dimension"], ["modal-width-max", "dimension"],
41
- ["form-max-width", "dimension"], ["table-row-height", "dimension"],
42
- ]);
43
- const PRIMITIVES = [...PRIMITIVE_TYPES.keys()];
44
-
45
- // The 27 v2 primitives with stagger-unit in its emission slot (after ease-default).
46
- const PRIMITIVE_TYPES_V2 = new Map(
47
- [...PRIMITIVE_TYPES].flatMap(([k, t]) => (k === "ease-default" ? [[k, t], ["stagger-unit", "duration"]] : [[k, t]])),
48
- );
49
-
50
- // The 34 v1 source color roles in emission order (17 semantic + 17 substrate; the machine blocks carry these).
51
- const COLOR_ROLES = [
52
- "background", "foreground", "card", "card-foreground", "muted", "muted-foreground",
53
- "border", "input", "ring", "primary", "primary-foreground", "accent", "accent-foreground",
54
- "destructive", "destructive-foreground", "success", "warning",
55
- "popover", "popover-foreground", "secondary", "secondary-foreground",
56
- "chart-1", "chart-2", "chart-3", "chart-4", "chart-5",
57
- "sidebar", "sidebar-foreground", "sidebar-primary", "sidebar-primary-foreground",
58
- "sidebar-accent", "sidebar-accent-foreground", "sidebar-border", "sidebar-ring",
59
- ];
60
-
61
- // The 36 v2 roles: the dark-ladder pair (surface-raised/surface-overlay) closes the semantic set at 19.
62
- const COLOR_ROLES_V2 = COLOR_ROLES.flatMap((r) => (r === "warning" ? [r, "surface-raised", "surface-overlay"] : [r]));
63
-
64
- const RAMP_STEPS = ["h1", "h2", "h3", "body", "small", "caption"];
65
- const RAMP_STEPS_V2 = [...RAMP_STEPS, "display"];
66
- const ELEVATION_TOKENS = ["sm", "md", "lg", "overlay"];
67
- // Source §3 row names that fill the caption slot when no literal `caption` row exists.
68
- const CAPTION_ALIASES = ["caption", "eyebrow / caption", "eyebrow", "label", "mono-label"];
69
-
70
- const LEGAL_VERBOSITY = ["full", "default", "compact", "expert"];
71
- const LEGAL_FEEDBACK = ["subtle", "minimal"];
72
- const LEGAL_DENSITY = ["spacious", "standard", "dense"];
73
- const LEGAL_PROVENANCE = ["project", "extracted", "library", "default"];
74
- const LEGAL_TYPES = ["color", "dimension", "duration", "cubicBezier", "fontFamily", "fontWeight", "number", "typography", "shadow", "gradient"];
75
- const SHADOW_RANK = { none: 0, "2xs": 1, xs: 2, sm: 3, md: 4, lg: 5, xl: 6, "2xl": 7, overlay: 8 };
76
- const EXT = "com.nurix.design";
77
-
78
- // Provisional values worn when a source key is a `hint:` (the Nurix defaults, per SCHEMA.md examples).
79
- const HINT_PROVISIONALS = new Map([
80
- ["radius", "6px"], ["shadow-intensity", "0.4"], ["border-weight", "1px"], ["density-gap", "1"],
81
- ["density-inset", "1"], ["font-sans", "Inter, system-ui, sans-serif"],
82
- ["font-mono", "Geist Mono, ui-monospace, SFMono-Regular, monospace"],
83
- ["font-weight-normal", "400"], ["font-weight-strong", "600"], ["heading-tracking", "-0.01em"],
84
- ["duration-unit", "50ms"], ["duration-fast", "100ms"], ["duration-normal", "150ms"],
85
- ["duration-slow", "250ms"], ["ease-default", "cubic-bezier(0.4,0,0.2,1)"], ["contrast-boost", "1"],
86
- ["icon-stroke", "1.75"], ["icon-size-base", "16px"], ["sidebar-width-expanded", "248px"],
87
- ["sidebar-width-collapsed", "56px"], ["topbar-height", "52px"], ["side-panel-width", "420px"],
88
- ["side-panel-width-rich", "640px"], ["modal-width-max", "560px"], ["form-max-width", "640px"],
89
- ["table-row-height", "40px"],
90
- ]);
91
-
92
- // ---------------------------------------------------------------------------
93
- // Low-level text helpers
94
- // ---------------------------------------------------------------------------
95
-
96
- // Split a raw line at its first unquoted, whitespace-preceded '#': { body, comment|null }.
97
- function splitLineComment(line) {
98
- let inDouble = false, inSingle = false;
99
- for (let i = 0; i < line.length; i++) {
100
- const c = line[i];
101
- if (c === '"' && !inSingle && line[i - 1] !== "\\") inDouble = !inDouble;
102
- else if (c === "'" && !inDouble) inSingle = !inSingle;
103
- else if (c === "#" && !inDouble && !inSingle && (i === 0 || /\s/.test(line[i - 1]))) {
104
- return { body: line.slice(0, i).replace(/\s+$/, ""), comment: line.slice(i + 1).trim() };
105
- }
106
- }
107
- return { body: line.replace(/\s+$/, ""), comment: null };
108
- }
109
-
110
- // Strip one layer of matching surrounding quotes and unescape \" within double quotes.
111
- function unquote(raw) {
112
- const s = raw.trim();
113
- if (s.length >= 2 && s[0] === '"' && s.at(-1) === '"') return s.slice(1, -1).replaceAll('\\"', '"');
114
- if (s.length >= 2 && s[0] === "'" && s.at(-1) === "'") return s.slice(1, -1);
115
- return s;
116
- }
117
-
118
- // Split on a delimiter at bracket-depth 0 and outside quotes.
119
- function splitTopLevel(str, delim) {
120
- const parts = [];
121
- let depth = 0, inDouble = false, inSingle = false, cur = "";
122
- for (let i = 0; i < str.length; i++) {
123
- const c = str[i];
124
- if (c === '"' && !inSingle && str[i - 1] !== "\\") { inDouble = !inDouble; cur += c; }
125
- else if (c === "'" && !inDouble) { inSingle = !inSingle; cur += c; }
126
- else if (!inDouble && !inSingle && (c === "[" || c === "{" || c === "(")) { depth++; cur += c; }
127
- else if (!inDouble && !inSingle && (c === "]" || c === "}" || c === ")")) { depth--; cur += c; }
128
- else if (!inDouble && !inSingle && depth === 0 && c === delim) { parts.push(cur); cur = ""; }
129
- else cur += c;
130
- }
131
- parts.push(cur);
132
- return parts;
133
- }
134
-
135
- // Round to at most 5 decimals (the srgb component precision).
136
- function round5(n) {
137
- return Math.round(n * 1e5) / 1e5;
138
- }
139
-
140
- function fail(msg) {
141
- console.error(`convert-design-md: ${msg}`);
142
- process.exit(1);
143
- }
144
-
145
- // ---------------------------------------------------------------------------
146
- // CSS value parsers (single source of truth — conversion AND parse-back verify use these)
147
- // ---------------------------------------------------------------------------
148
-
149
- // Parse any corpus color string (oklch / #rrggbb / #rrggbbaa / rgb() / rgba()) into a typed DTCG color $value.
150
- function parseColorCss(css) {
151
- const s = css.trim();
152
- let m = /^oklch\(\s*([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)\s*(?:\/\s*([\d.]+%?)\s*)?\)$/.exec(s);
153
- if (m) {
154
- const value = { colorSpace: "oklch", components: [parseFloat(m[1]), parseFloat(m[2]), parseFloat(m[3])] };
155
- if (m[4] !== undefined) value.alpha = m[4].endsWith("%") ? round5(parseFloat(m[4]) / 100) : parseFloat(m[4]);
156
- return value;
157
- }
158
- m = /^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})?$/.exec(s);
159
- if (m) {
160
- const bytes = [0, 2, 4].map((i) => parseInt(m[1].slice(i, i + 2), 16));
161
- const value = { colorSpace: "srgb", components: bytes.map((b) => round5(b / 255)), hex: `#${m[1].toLowerCase()}` };
162
- if (m[2] !== undefined) value.alpha = round5(parseInt(m[2], 16) / 255);
163
- return value;
164
- }
165
- m = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)$/.exec(s);
166
- if (!m) m = /^rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*(?:\/\s*([\d.]+%?)\s*)?\)$/.exec(s); // modern space syntax
167
- if (m) {
168
- const bytes = [m[1], m[2], m[3]].map(Number);
169
- const hex = "#" + bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
170
- const value = { colorSpace: "srgb", components: bytes.map((b) => round5(b / 255)), hex };
171
- if (m[4] !== undefined) value.alpha = m[4].endsWith("%") ? round5(parseFloat(m[4]) / 100) : parseFloat(m[4]);
172
- return value;
173
- }
174
- throw new Error(`unparseable color: ${JSON.stringify(css)}`);
175
- }
176
-
177
- // Parse one shadow-list string (the elevation byte form) into the DTCG layer array; byte-compatible
178
- // with derive-personality.mjs. A layer alpha may ride calc(A * var(--shadow-intensity)) — A is the alpha.
179
- function parseShadowCss(css) {
180
- return splitTopLevel(css, ",").map((layerRaw) => {
181
- const s = layerRaw.trim();
182
- const colorStart = s.search(/rgba?\(|oklch\(|hsla?\(|#[0-9a-fA-F]/);
183
- if (colorStart < 0) throw new Error(`shadow layer names no color: ${JSON.stringify(s)}`);
184
- const colorCss = s.slice(colorStart).trim().replace(/calc\(\s*([\d.]+)\s*\*\s*var\(--shadow-intensity\)\s*\)/, "$1");
185
- const dims = s.slice(0, colorStart).trim().split(/\s+/);
186
- if (dims.length < 3 || dims.length > 4) throw new Error(`shadow layer wants 3-4 lengths: ${JSON.stringify(s)}`);
187
- const dim = (d) => (d === "0" ? { value: 0, unit: "px" } : parseDimensionCss(d));
188
- return {
189
- color: parseColorCss(colorCss),
190
- offsetX: dim(dims[0]),
191
- offsetY: dim(dims[1]),
192
- blur: dim(dims[2]),
193
- spread: dims[3] !== undefined ? dim(dims[3]) : { value: 0, unit: "px" },
194
- };
195
- });
196
- }
197
-
198
- // Parse a tracking string ("0" | "0em" | "-0.015em") into its em-multiplier number.
199
- function parseTrackingCss(css) {
200
- const m = /^([+-]?\d*\.?\d+)(em)?$/.exec(css.trim());
201
- if (!m) throw new Error(`unparseable tracking: ${JSON.stringify(css)}`);
202
- return parseFloat(m[1]);
203
- }
204
-
205
- // Parse "cubic-bezier(x1,y1,x2,y2)" into its four numbers.
206
- function parseCubicBezierCss(css) {
207
- const m = /^cubic-bezier\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)$/.exec(css.trim());
208
- if (!m) throw new Error(`unparseable cubic-bezier: ${JSON.stringify(css)}`);
209
- return [1, 2, 3, 4].map((i) => parseFloat(m[i]));
210
- }
211
-
212
- // Parse a font stack string into its array of unquoted family names.
213
- function parseFontStackCss(css) {
214
- return splitTopLevel(css, ",").map((p) => unquote(p)).filter((p) => p !== "");
215
- }
216
-
217
- // Parse a ramp letterSpacing string ("+0.02em" | "-0.28px" | "0") into an em multiplier, px scaled by fontSize.
218
- function parseLetterSpacingCss(css, fontSizePx) {
219
- const m = /^([+-]?\d*\.?\d+)(em|px)?$/.exec(css.trim());
220
- if (!m) throw new Error(`unparseable letterSpacing: ${JSON.stringify(css)}`);
221
- const n = parseFloat(m[1]);
222
- return m[2] === "px" ? round5(n / fontSizePx) : n;
223
- }
224
-
225
- function parseDimensionCss(css) {
226
- const m = /^(-?\d*\.?\d+)px$/.exec(css.trim());
227
- if (!m) throw new Error(`unparseable dimension: ${JSON.stringify(css)}`);
228
- return { value: parseFloat(m[1]), unit: "px" };
229
- }
230
-
231
- function parseDurationCss(css) {
232
- const m = /^(\d*\.?\d+)ms$/.exec(css.trim());
233
- if (!m) throw new Error(`unparseable duration: ${JSON.stringify(css)}`);
234
- return { value: parseFloat(m[1]), unit: "ms" };
235
- }
236
-
237
- function parseNumberCss(css) {
238
- const n = parseFloat(css.trim());
239
- if (Number.isNaN(n) || !/^[+-]?\d*\.?\d+$/.test(css.trim())) throw new Error(`unparseable number: ${JSON.stringify(css)}`);
240
- return n;
241
- }
242
-
243
- // ---------------------------------------------------------------------------
244
- // Source parsing — frontmatter, the two machine blocks, the §3 ramp table
245
- // ---------------------------------------------------------------------------
246
-
247
- function parseFrontmatter(source) {
248
- const m = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(source);
249
- if (!m) return null;
250
- const out = {};
251
- for (const raw of m[1].split("\n")) {
252
- const { body } = splitLineComment(raw);
253
- const ci = body.indexOf(":");
254
- if (ci > 0) out[body.slice(0, ci).trim()] = unquote(body.slice(ci + 1));
255
- }
256
- return out;
257
- }
258
-
259
- // Extract the raw body of the ```yaml <label> fenced block, or null.
260
- function extractFencedBlock(source, label) {
261
- const re = new RegExp("^`{3,}\\s*yaml\\s+" + label + "\\s*$", "m");
262
- const open = re.exec(source);
263
- if (!open) return null;
264
- const start = source.indexOf("\n", open.index) + 1;
265
- const close = /^`{3,}\s*$/m.exec(source.slice(start));
266
- if (!close) return null;
267
- return source.slice(start, start + close.index);
268
- }
269
-
270
- // Parse the design-primitives block, keeping raw value strings and per-line comments.
271
- function parsePrimitivesBlock(text) {
272
- const entries = new Map();
273
- const standalone = [];
274
- for (const raw of text.split("\n")) {
275
- if (raw.trim() === "") continue;
276
- const { body, comment } = splitLineComment(raw);
277
- if (body.trim() === "") { if (comment !== null) standalone.push(comment); continue; }
278
- const m = /^([a-z-]+):\s*(.*)$/.exec(body.trim());
279
- if (!m) throw new Error(`unparseable design-primitives line: ${JSON.stringify(raw)}`);
280
- entries.set(m[1], { raw: m[2].trim(), comment });
281
- }
282
- return { entries, standalone };
283
- }
284
-
285
- // Parse an inline flow array "[a, b, c]" into raw scalar strings.
286
- function parseFlowArray(raw) {
287
- const inner = raw.trim().replace(/^\[/, "").replace(/\]$/, "");
288
- return splitTopLevel(inner, ",").map((p) => unquote(p)).filter((p) => p !== "");
289
- }
290
-
291
- // Parse an inline flow object "{ k: v, ... }" into raw scalar/array values keyed by name.
292
- function parseFlowObject(raw) {
293
- const inner = raw.trim().replace(/^\{/, "").replace(/\}$/, "");
294
- const out = {};
295
- for (const part of splitTopLevel(inner, ",")) {
296
- if (part.trim() === "") continue;
297
- const ci = part.indexOf(":");
298
- const key = part.slice(0, ci).trim();
299
- const val = part.slice(ci + 1).trim();
300
- out[key] = val.startsWith("[") ? parseFlowArray(val) : unquote(val);
301
- }
302
- return out;
303
- }
304
-
305
- // Parse the lever-manifest block: scalars, the light/dark color maps (with comments), non-token comments.
306
- function parseManifestBlock(text) {
307
- const out = { scalars: new Map(), light: new Map(), dark: null, standalone: [] };
308
- let section = null;
309
- for (const raw of text.split("\n")) {
310
- if (raw.trim() === "") continue;
311
- const { body, comment } = splitLineComment(raw);
312
- if (body.trim() === "") { if (comment !== null) out.standalone.push({ where: "lever-manifest", comment }); continue; }
313
- const indent = body.length - body.trimStart().length;
314
- const t = body.trim();
315
- if (indent === 0) {
316
- const m = /^([a-z-]+):\s*(.*)$/.exec(t);
317
- if (!m) throw new Error(`unparseable lever-manifest line: ${JSON.stringify(raw)}`);
318
- if (m[1] === "color-tokens") {
319
- section = "color-tokens";
320
- if (comment !== null) out.standalone.push({ where: "color-tokens", comment });
321
- continue;
322
- }
323
- section = null;
324
- out.scalars.set(m[1], { raw: m[2].trim(), comment });
325
- } else if (["color-tokens", "light", "dark"].includes(section) && indent === 2 && /^(light|dark):$/.test(t)) {
326
- section = t.slice(0, -1) === "light" ? "light" : "dark";
327
- if (section === "dark") out.dark = new Map();
328
- if (comment !== null) out.standalone.push({ where: `color-tokens.${section}`, comment });
329
- } else if ((section === "light" || section === "dark") && indent >= 4) {
330
- const m = /^([a-z0-9-]+):\s*(.+)$/.exec(t);
331
- if (!m) throw new Error(`unparseable color-token line: ${JSON.stringify(raw)}`);
332
- (section === "light" ? out.light : out.dark).set(m[1], { raw: unquote(m[2]), comment });
333
- } else {
334
- throw new Error(`unexpected lever-manifest structure: ${JSON.stringify(raw)}`);
335
- }
336
- }
337
- return out;
338
- }
339
-
340
- // Extract the §3 typography table as header-mapped rows: { name, role, family, size, weight }.
341
- function parseRampTable(source) {
342
- const sec = /^## 3\b.*?\n([\s\S]*?)(?=^## \d|\n```yaml )/m.exec(source);
343
- if (!sec) return null;
344
- const lines = sec[1].split("\n").filter((l) => l.trim().startsWith("|"));
345
- if (lines.length < 3) return null;
346
- const cells = (l) => l.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
347
- const header = cells(lines[0]).map((h) => h.toLowerCase());
348
- const col = (name) => header.findIndex((h) => h.includes(name));
349
- const iStep = col("step"), iRole = col("role"), iFam = col("family"), iSize = col("size"), iWeight = col("weight");
350
- const rows = [];
351
- for (const line of lines.slice(2)) {
352
- const c = cells(line);
353
- if (c.length < header.length) continue;
354
- rows.push({
355
- name: c[iStep], role: c[iRole], family: iFam >= 0 ? c[iFam] : null,
356
- size: c[iSize], weight: c[iWeight],
357
- });
358
- }
359
- return rows;
360
- }
361
-
362
- // ---------------------------------------------------------------------------
363
- // Conversion — one source entry → the token artifact
364
- // ---------------------------------------------------------------------------
365
-
366
- // Build one primitive token (typed $value, comment → $description, preserved css / hint where the schema requires).
367
- function buildPrimitiveToken(key, rawValue, comment, notes) {
368
- const type = PRIMITIVE_TYPES.get(key);
369
- const token = { $type: type };
370
- let raw = rawValue;
371
- let hint = null;
372
- if (raw.startsWith("hint:")) {
373
- hint = raw.slice(5).trim();
374
- raw = HINT_PROVISIONALS.get(key);
375
- notes.hints.push({ key, hint });
376
- }
377
- const ext = {};
378
- if (type === "dimension") {
379
- token.$value = parseDimensionCss(raw);
380
- if (`${token.$value.value}px` !== raw) notes.irregularities.push(`primitive ${key}: non-canonical byte form ${JSON.stringify(raw)}`);
381
- } else if (type === "duration") {
382
- token.$value = parseDurationCss(raw);
383
- if (`${token.$value.value}ms` !== raw) notes.irregularities.push(`primitive ${key}: non-canonical byte form ${JSON.stringify(raw)}`);
384
- } else if (type === "fontWeight") {
385
- token.$value = parseNumberCss(raw);
386
- } else if (type === "cubicBezier") {
387
- token.$value = parseCubicBezierCss(raw);
388
- ext.css = raw;
389
- } else if (type === "fontFamily") {
390
- token.$value = parseFontStackCss(raw);
391
- ext.css = raw;
392
- } else if (key === "heading-tracking") {
393
- token.$value = parseTrackingCss(raw);
394
- ext.css = raw;
395
- } else {
396
- token.$value = parseNumberCss(raw);
397
- if (String(token.$value) !== raw) notes.irregularities.push(`primitive ${key}: non-canonical byte form ${JSON.stringify(raw)}`);
398
- }
399
- if (comment !== null) { token.$description = comment; notes.migrated++; }
400
- if (hint !== null) ext.hint = hint;
401
- if (Object.keys(ext).length > 0) token.$extensions = { [EXT]: ext };
402
- return token;
403
- }
404
-
405
- // Build one color token: typed $value + mandatory preserved css; comment → $description.
406
- function buildColorToken(css, comment, notes) {
407
- const token = { $type: "color", $value: parseColorCss(css) };
408
- if (comment !== null) { token.$description = comment; notes.migrated++; }
409
- token.$extensions = { [EXT]: { css } };
410
- return token;
411
- }
412
-
413
- // Interpret one §3 weight·tracking cell → { weight, tracking, trackingCss, mono, parenWeights }.
414
- function parseWeightCell(cell, fontSizePx) {
415
- const cleaned = cell.replace(/`/g, "");
416
- const parenWeights = [...cleaned.matchAll(/\((\d{3})\)/g)].map((m) => Number(m[1]));
417
- const rest = cleaned.replace(/\([^)]*\)/g, " ");
418
- const tokens = rest.split(/[·,]/).map((t) => t.trim()).filter((t) => t !== "");
419
- let weight = null, tracking = null, trackingCss = null, mono = false;
420
- for (const tok of tokens) {
421
- if (/^font-weight-strong$/.test(tok)) weight = "{primitive.font-weight-strong}";
422
- else if (/^font-weight-normal$/.test(tok)) weight = "{primitive.font-weight-normal}";
423
- else if (/^heading-tracking$/.test(tok)) tracking = "{primitive.heading-tracking}";
424
- else if (/^[+-]?\d*\.?\d+(em|px)$/.test(tok)) { tracking = parseLetterSpacingCss(tok, fontSizePx); trackingCss = tok; }
425
- else if (/^\d{3}$/.test(tok) && weight === null) weight = Number(tok);
426
- else if (tok === "0") tracking = 0;
427
- else if (/\bfont-mono\b|^mono\b/.test(tok)) mono = true;
428
- // remaining words (uppercase, sentence case, no tracking, positive, …) are usage prose — the .md keeps them
429
- }
430
- return { weight, tracking, trackingCss, mono, parenWeights };
431
- }
432
-
433
- // Build typography.family.* + the six-step typography.ramp.* from the §3 table rows.
434
- function buildTypography(rows, primitives, notes) {
435
- const family = {
436
- heading: { $type: "fontFamily", $value: "{primitive.font-sans}" },
437
- body: { $type: "fontFamily", $value: "{primitive.font-sans}" },
438
- mono: { $type: "fontFamily", $value: "{primitive.font-mono}" },
439
- };
440
- if (rows === null) throw new Error("no §3 typography table found");
441
-
442
- // Map source rows onto the six fixed step names; leftovers are reported, never emitted.
443
- const bySlot = new Map();
444
- const leftovers = [];
445
- for (const row of rows) {
446
- const name = row.name.toLowerCase();
447
- if (RAMP_STEPS.includes(name) && !bySlot.has(name)) bySlot.set(name, row);
448
- else leftovers.push(row);
449
- }
450
- if (!bySlot.has("caption")) {
451
- for (const alias of CAPTION_ALIASES) {
452
- const i = leftovers.findIndex((r) => r.name.toLowerCase() === alias);
453
- if (i >= 0) {
454
- const row = leftovers.splice(i, 1)[0];
455
- bySlot.set("caption", row);
456
- notes.ramp.push(`caption ← source step \`${row.name}\``);
457
- break;
458
- }
459
- }
460
- }
461
- for (const row of leftovers) notes.ramp.push(`unmapped §3 row \`${row.name}\` (${row.role}) — no ramp slot`);
462
-
463
- // The look pairs a serif display face the machine blocks don't carry — note it, alias stays font-sans.
464
- const serifRows = [...bySlot.values()].filter((r) => r.family && /serif/i.test(r.family) && !/sans/i.test(r.family));
465
- if (serifRows.length > 0) {
466
- family.heading.$description =
467
- "source §3 pairs a serif display family for " + serifRows.map((r) => r.name).join("/") +
468
- "; its stack is prose-carried (the agent applies it by hand) — aliased to font-sans mechanically";
469
- notes.ramp.push(`serif heading family noted on typography.family.heading (rows: ${serifRows.map((r) => r.name).join(", ")})`);
470
- }
471
-
472
- const ramp = {};
473
- for (const step of RAMP_STEPS) {
474
- let row = bySlot.get(step);
475
- let synthesized = null;
476
- if (!row) {
477
- row = bySlot.get("body");
478
- if (!row) throw new Error(`§3 table lacks both \`${step}\` and \`body\` rows`);
479
- synthesized = `synthesized: source §3 ramp carries no \`${step}\` step; values copied from body (role: ${row.role})`;
480
- notes.ramp.push(`${step} synthesized from body (missing in source §3 table)`);
481
- }
482
- const sm = /(\d+(?:\.\d+)?)px\s*\/\s*(\d+(?:\.\d+)?)/.exec(row.size);
483
- if (!sm) throw new Error(`unparseable §3 size cell for ${step}: ${JSON.stringify(row.size)}`);
484
- const fontSize = parseFloat(sm[1]);
485
- const cell = parseWeightCell(row.weight, fontSize);
486
-
487
- // A parenthetical weight that contradicts the aliased primitive is a source irregularity.
488
- for (const pw of cell.parenWeights) {
489
- const aliasKey = cell.weight === "{primitive.font-weight-strong}" ? "font-weight-strong"
490
- : cell.weight === "{primitive.font-weight-normal}" ? "font-weight-normal" : null;
491
- if (aliasKey && parseNumberCss(primitives.get(aliasKey).raw) !== pw) {
492
- notes.irregularities.push(`§3 ${step}: parenthetical weight (${pw}) contradicts primitive ${aliasKey}: ${primitives.get(aliasKey).raw}`);
493
- }
494
- }
495
- if (cell.weight === null) {
496
- cell.weight = "{primitive.font-weight-normal}";
497
- notes.ramp.push(`${step}: §3 row names no weight — defaulted to font-weight-normal`);
498
- }
499
-
500
- const monoFamily = cell.mono || (row.family !== null && /^mono/i.test(row.family));
501
- const familyAlias = monoFamily ? "{typography.family.mono}"
502
- : (step === "h1" || step === "h2" || step === "h3") ? "{typography.family.heading}"
503
- : "{typography.family.body}";
504
-
505
- const token = {
506
- $type: "typography",
507
- $value: {
508
- fontFamily: familyAlias,
509
- fontSize: { value: fontSize, unit: "px" },
510
- lineHeight: parseFloat(sm[2]),
511
- fontWeight: cell.weight,
512
- letterSpacing: cell.tracking === null ? 0 : cell.tracking,
513
- },
514
- };
515
- const descriptions = [];
516
- if (step === "caption") descriptions.push(row.role);
517
- if (synthesized) descriptions.push(synthesized);
518
- if (descriptions.length > 0) token.$description = descriptions.join(" — ");
519
- if (cell.trackingCss !== null) token.$extensions = { [EXT]: { css: { letterSpacing: cell.trackingCss } } };
520
- ramp[step] = token;
521
- if (cell.trackingCss !== null && cell.trackingCss.endsWith("px")) {
522
- notes.ramp.push(`${step}: px tracking ${cell.trackingCss} → ${token.$value.letterSpacing}em (at ${fontSize}px)`);
523
- }
524
- }
525
- return { family, ramp };
526
- }
527
-
528
- // Build the payload lever-manifest from the source block, dropping token-duplicating keys (fonts, icons.stroke, version).
529
- function buildLeverManifest(manifest, primitives, notes) {
530
- const scalar = (k) => manifest.scalars.get(k);
531
- const nums = (arr) => arr.map((v) => parseNumberCss(v));
532
-
533
- // fonts duplicates primitive.font-sans/font-mono — the allowlist reads the tokens now.
534
- const fonts = parseFlowObject(scalar("fonts").raw);
535
- for (const [slot, primKey] of [["sans", "font-sans"], ["mono", "font-mono"]]) {
536
- const a = parseFontStackCss(fonts[slot] ?? "");
537
- const b = parseFontStackCss(primitives.get(primKey).raw);
538
- if (JSON.stringify(a) !== JSON.stringify(b)) {
539
- notes.irregularities.push(`manifest fonts.${slot} differs from primitive ${primKey}: ${JSON.stringify(fonts[slot])} vs ${JSON.stringify(primitives.get(primKey).raw)}`);
540
- }
541
- }
542
- notes.drops.push("fonts (duplicates primitive.font-sans / primitive.font-mono)");
543
-
544
- const icons = parseFlowObject(scalar("icons").raw);
545
- if (icons.stroke !== undefined) {
546
- if (icons.stroke !== primitives.get("icon-stroke").raw) {
547
- notes.irregularities.push(`manifest icons.stroke ${icons.stroke} differs from primitive icon-stroke ${primitives.get("icon-stroke").raw}`);
548
- }
549
- notes.drops.push("icons.stroke (duplicates primitive.icon-stroke)");
550
- }
551
- if (scalar("version") !== undefined) notes.drops.push("version (superseded by the payload `schema` literal)");
552
-
553
- const motion = parseFlowObject(scalar("motion").raw);
554
- return {
555
- "spacing-scale": nums(parseFlowArray(scalar("spacing-scale").raw)),
556
- "radius-scale": nums(parseFlowArray(scalar("radius-scale").raw)),
557
- "shadow-levels": parseFlowArray(scalar("shadow-levels").raw),
558
- motion: { durations: nums(motion.durations), easings: motion.easings },
559
- icons: { family: icons.family },
560
- "accent-count": parseNumberCss(scalar("accent-count").raw),
561
- };
562
- }
563
-
564
- // Convert one parsed source entry into its three artifact documents.
565
- function convertEntry(source, slug) {
566
- const notes = { migrated: 0, rootAttached: [], dropped: [], hints: [], ramp: [], drops: [], irregularities: [], darkFile: false };
567
- const frontmatter = parseFrontmatter(source);
568
- if (!frontmatter) throw new Error("missing frontmatter");
569
- if (frontmatter.schema !== "design-md/v1") throw new Error(`unknown source schema: ${frontmatter.schema}`);
570
-
571
- const primText = extractFencedBlock(source, "design-primitives");
572
- const maniText = extractFencedBlock(source, "lever-manifest");
573
- if (primText === null || maniText === null) throw new Error("missing machine block(s)");
574
- const prims = parsePrimitivesBlock(primText);
575
- const manifest = parseManifestBlock(maniText);
576
-
577
- // Non-token comments (dials, scales, standalone lines) have no token to land on — they're preserved
578
- // on the artifact root $description and reported, never dropped silently.
579
- const rootNotes = [];
580
- const attachRoot = (where, comment) => { rootNotes.push(`[${where}] ${comment}`); notes.rootAttached.push({ where, comment }); };
581
-
582
- // Primitive tokens.
583
- const primitive = {};
584
- for (const key of PRIMITIVES) {
585
- const entry = prims.entries.get(key);
586
- if (!entry) throw new Error(`missing primitive: ${key}`);
587
- primitive[key] = buildPrimitiveToken(key, entry.raw, entry.comment, notes);
588
- }
589
-
590
- // Dials + traits → payload; their comments are non-token.
591
- const dial = (key) => {
592
- const entry = prims.entries.get(key);
593
- if (!entry) throw new Error(`missing dial: ${key}`);
594
- if (entry.comment !== null) attachRoot(key, entry.comment);
595
- return entry.raw;
596
- };
597
- const verbosity = dial("verbosity"), feedback = dial("feedback"), densityTier = dial("density-tier");
598
- const traits = parseFlowArray(dial("product-traits"));
599
- for (const c of prims.standalone) attachRoot("design-primitives", c);
600
- for (const key of prims.entries.keys()) {
601
- if (!PRIMITIVES.includes(key) && !["verbosity", "feedback", "density-tier", "product-traits"].includes(key)) {
602
- notes.irregularities.push(`unknown design-primitives key: ${key}`);
603
- }
604
- }
605
-
606
- // Color tokens (light) — every role required.
607
- const color = {};
608
- for (const role of COLOR_ROLES) {
609
- const entry = manifest.light.get(role);
610
- if (!entry) throw new Error(`missing color token: ${role}`);
611
- color[role] = buildColorToken(entry.raw, entry.comment, notes);
612
- }
613
- for (const role of manifest.light.keys()) if (!COLOR_ROLES.includes(role)) notes.irregularities.push(`unknown light color token: ${role}`);
614
-
615
- // Manifest scalar comments + standalone comments are non-token.
616
- for (const [key, { comment }] of manifest.scalars) if (comment !== null) attachRoot(key, comment);
617
- for (const c of manifest.standalone) attachRoot(c.where, c.comment);
618
-
619
- const leverManifest = buildLeverManifest(manifest, prims.entries, notes);
620
- const typography = buildTypography(parseRampTable(source), prims.entries, notes);
621
-
622
- // Root $description: the source H1 title, then any root-attached provenance notes.
623
- const h1 = /^# (.+)$/m.exec(source);
624
- const $description = [h1 ? h1[1].trim() : frontmatter.product, ...rootNotes].join("\n");
625
-
626
- const base = {
627
- $description,
628
- $extensions: {
629
- [EXT]: {
630
- schema: "design-tokens/v1",
631
- dtcg: "2025.10",
632
- product: frontmatter.product,
633
- provenance: frontmatter.provenance,
634
- sourced: frontmatter.sourced,
635
- "product-traits": traits,
636
- verbosity, feedback, "density-tier": densityTier,
637
- "lever-manifest": leverManifest,
638
- },
639
- },
640
- primitive, color, typography,
641
- };
642
-
643
- // Dark set — the color group remapped, nothing else; partial maps inherit light with a marker.
644
- let dark = null;
645
- if (manifest.dark !== null) {
646
- notes.darkFile = true;
647
- const darkColor = {};
648
- for (const role of COLOR_ROLES) {
649
- const entry = manifest.dark.get(role);
650
- if (entry) {
651
- darkColor[role] = buildColorToken(entry.raw, entry.comment, notes);
652
- } else {
653
- darkColor[role] = structuredClone(color[role]);
654
- darkColor[role].$description = "dark: inherited from light (source map partial)";
655
- notes.irregularities.push(`dark map partial: ${role} inherited from light`);
656
- }
657
- }
658
- for (const role of manifest.dark.keys()) if (!COLOR_ROLES.includes(role)) notes.irregularities.push(`unknown dark color token: ${role}`);
659
- dark = { color: darkColor };
660
- }
661
-
662
- const resolver = {
663
- schema: "design-resolver/v1",
664
- sets: [{ name: "base", values: ["design.tokens.json"] }],
665
- modifiers: [{
666
- name: "theme", default: "light",
667
- contexts: dark ? { light: [], dark: ["design.tokens.dark.json"] } : { light: [] },
668
- }],
669
- };
670
-
671
- return { base, dark, resolver, notes };
672
- }
673
-
674
- // ---------------------------------------------------------------------------
675
- // v1 → v2 upgrade transform (SCHEMA.md migration; idempotent — every step skips when already applied)
676
- // ---------------------------------------------------------------------------
677
-
678
- // The v1 hardcoded slate shadow recipes (the old globals.css @theme block) reified as elevation byte
679
- // forms — value-preserving: the calc(α × --shadow-intensity) rider keeps the twin's shadow-intensity
680
- // as the master multiplier, so emitted --shadow-sm/md/lg are byte-identical before/after migration.
681
- // overlay is the widest-blur member of the same family (new in v2, additive).
682
- const V1_SHADOW_RECIPES = {
683
- sm: "0 1px 3px 0 rgb(15 23 42 / calc(0.10 * var(--shadow-intensity))), 0 1px 2px -1px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity)))",
684
- md: "0 4px 6px -1px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity))), 0 2px 4px -2px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity)))",
685
- lg: "0 10px 15px -3px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity))), 0 4px 6px -4px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity)))",
686
- overlay: "0 20px 25px -5px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity))), 0 8px 10px -6px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity)))",
687
- };
688
-
689
- // Collect design.md code spans that name real shadow values — a color function (shadow tints ride
690
- // alpha'd rgba/oklch) or box-shadow geometry (>= 2 consecutive lengths) on a shadow-context line.
691
- // A bare hex near the word "shadow" (a border color in a "never shadow" sentence) is not one.
692
- function findShadowProse(designMd) {
693
- if (designMd === null) return [];
694
- const evidence = [];
695
- const lines = designMd.split("\n");
696
- for (const [i, line] of lines.entries()) {
697
- // The shadow context may sit on the previous line when the prose wraps; a negated mention
698
- // ("no shadow", "not shadow", "never ... shadow", "shadowless") is not a shadow context.
699
- const window = `${lines[i - 1] ?? ""} ${line}`
700
- .replace(/\b(?:no|not|never|without|zero)\b[^.;:]*?shadow\w*/gi, "")
701
- .replace(/shadow-?less/gi, "");
702
- if (!/shadow/i.test(window)) continue;
703
- for (const m of line.matchAll(/`[^`]+`/g)) {
704
- const span = m[0];
705
- const hasColorFn = /rgba?\(|oklch\(|hsla?\(/.test(span);
706
- const hasGeometry = /(?:^|[\s`])(?:-?\d+(?:\.\d+)?px|0)\s+(?:-?\d+(?:\.\d+)?px|0)(?:\s|`|,)/.test(span);
707
- if (hasColorFn || hasGeometry) evidence.push(span);
708
- }
709
- }
710
- return evidence;
711
- }
712
-
713
- // True when a design.md declares the dark theme as the product's default.
714
- function declaresDarkFirst(designMd) {
715
- return designMd !== null && /dark by default|default to dark|wear the dark theme by default/i.test(designMd);
716
- }
717
-
718
- // Rebuild an object with `additions` ([key, value] pairs) inserted immediately after `afterKey`.
719
- function insertAfter(obj, afterKey, additions) {
720
- const out = {};
721
- for (const [k, v] of Object.entries(obj)) {
722
- out[k] = v;
723
- if (k === afterKey) for (const [ak, av] of additions) out[ak] = av;
724
- }
725
- return out;
726
- }
727
-
728
- // Apply the seven-step v1→v2 migration to one artifact's documents in place-order (idempotent:
729
- // every step is skipped when its key already exists). Returns per-entry stats.
730
- function upgradeArtifact(base, dark, resolver, designMd, slug) {
731
- const stats = { elevationHinted: false, darkFirst: false, displayHinted: false };
732
- const payload = base.$extensions?.[EXT];
733
- if (!payload) throw new Error("upgrade: no payload on the base root");
734
-
735
- // Step 1 — the schema literal.
736
- payload.schema = "design-tokens/v2";
737
-
738
- // Step 6 — theme-default: light everywhere except twins whose design.md declares dark-first
739
- // (only a twin shipping a dark set can boot dark); the resolver's theme default mirrors it.
740
- if (payload["theme-default"] === undefined) {
741
- const darkFirst = dark !== null && declaresDarkFirst(designMd);
742
- base.$extensions[EXT] = insertAfter(payload, "sourced", [["theme-default", darkFirst ? "dark" : "light"]]);
743
- }
744
- stats.darkFirst = base.$extensions[EXT]["theme-default"] === "dark";
745
- const themeModifier = (resolver.modifiers ?? []).find((m) => m?.name === "theme");
746
- if (themeModifier) themeModifier.default = base.$extensions[EXT]["theme-default"];
747
-
748
- // The Nurix default's stated v2 shadow vocabulary (SCHEMA.md elevation section).
749
- if (slug === "nurix") base.$extensions[EXT]["lever-manifest"]["shadow-levels"] = ["none", "sm", "md", "lg", "overlay"];
750
-
751
- // Step 2 — elevation from the v1 slate recipes × the twin's shadow-intensity; hinted when the
752
- // design.md names real shadow values (judgment owed: the composites should carry that prose).
753
- if (base.elevation === undefined) {
754
- const prose = findShadowProse(designMd);
755
- stats.elevationHinted = prose.length > 0;
756
- const elevation = {};
757
- for (const level of ELEVATION_TOKENS) {
758
- const css = V1_SHADOW_RECIPES[level];
759
- const ext = { css };
760
- if (prose.length > 0) {
761
- ext.hint = `elevation unset — the design.md names real shadow values (${prose.slice(0, 3).join("; ")}); resolve the composites from that prose at the data build`;
762
- }
763
- elevation[level] = {
764
- $type: "shadow",
765
- $value: parseShadowCss(css),
766
- $description: "derived: v1 shadow-intensity migration",
767
- $extensions: { [EXT]: ext },
768
- };
769
- }
770
- base.elevation = elevation;
771
- } else {
772
- stats.elevationHinted = ELEVATION_TOKENS.some((l) => base.elevation?.[l]?.$extensions?.[EXT]?.hint !== undefined);
773
- }
774
-
775
- // Step 3 — the dark-ladder roles as aliases: zero visual change in light; a dark set gains the
776
- // same aliases (they resolve post-overlay to the dark card/popover).
777
- if (base.color["surface-raised"] === undefined) {
778
- base.color = insertAfter(base.color, "warning", [
779
- ["surface-raised", { $type: "color", $value: "{color.card}", $description: "derived: v1→v2 migration — light aliases card; dark looks carry the surface-lightness ladder" }],
780
- ["surface-overlay", { $type: "color", $value: "{color.popover}", $description: "derived: v1→v2 migration — light aliases popover; dark looks carry the surface-lightness ladder" }],
781
- ]);
782
- }
783
- if (dark !== null && dark.color["surface-raised"] === undefined) {
784
- dark.color = insertAfter(dark.color, "warning", [
785
- ["surface-raised", { $type: "color", $value: "{color.card}", $description: "derived: v1→v2 migration — aliased to the dark card; the +4–6 L surface ladder is owed at budget-time" }],
786
- ["surface-overlay", { $type: "color", $value: "{color.popover}", $description: "derived: v1→v2 migration — aliased to the dark popover; the +4–6 L surface ladder is owed at budget-time" }],
787
- ]);
788
- }
789
-
790
- // Step 4 — the display step, hinted (provisional: h1 × 1.75, strong weight). The Nurix default
791
- // adopts the provisional derivation as its own look, assigned, so emission stays possible.
792
- if (base.typography?.ramp && base.typography.ramp.display === undefined) {
793
- const h1 = base.typography.ramp.h1;
794
- if (!h1?.$value?.fontSize?.value) throw new Error("upgrade: ramp.h1 carries no fontSize to scale");
795
- const display = {
796
- $type: "typography",
797
- $value: {
798
- fontFamily: h1.$value.fontFamily,
799
- fontSize: { value: Math.round(h1.$value.fontSize.value * 1.75), unit: "px" },
800
- lineHeight: h1.$value.lineHeight,
801
- fontWeight: "{primitive.font-weight-strong}",
802
- letterSpacing: h1.$value.letterSpacing,
803
- },
804
- };
805
- if (slug === "nurix") {
806
- display.$description = "derived: v2 display step — the provisional h1 × 1.75 derivation adopted as the default's own look (KPI numerals, the one focal metric)";
807
- } else {
808
- display.$description = "derived: v1→v2 migration — provisional display step (h1 × 1.75, strong weight)";
809
- display.$extensions = { [EXT]: { hint: "display step unset — resolve from the product's design.md or leave for budget-time" } };
810
- }
811
- base.typography.ramp.display = display;
812
- }
813
- stats.displayHinted = base.typography?.ramp?.display?.$extensions?.[EXT]?.hint !== undefined;
814
-
815
- // Step 5 — the stagger base, 30ms default (the prior derived clamp's value).
816
- if (base.primitive["stagger-unit"] === undefined) {
817
- base.primitive = insertAfter(base.primitive, "ease-default", [
818
- ["stagger-unit", { $type: "duration", $value: { value: 30, unit: "ms" }, $description: "derived: v1→v2 migration — the 30ms default stagger base (0–60ms legal)" }],
819
- ]);
820
- }
821
-
822
- return stats;
823
- }
824
-
825
- // Every artifact directory under data/design-tokens/ (a directory holding design.tokens.json), sorted.
826
- function listArtifactSlugs() {
827
- return readdirSync(tokensDir, { withFileTypes: true })
828
- .filter((d) => d.isDirectory() && existsSync(join(tokensDir, d.name, "design.tokens.json")))
829
- .map((d) => d.name)
830
- .sort();
831
- }
832
-
833
- // The design.md source text for one artifact slug, or null when the twin ships no source.
834
- function readDesignMd(slug) {
835
- const path = slug === "nurix" ? join(designMdDir, "nurix.md") : join(designMdDir, "library", `${slug}.md`);
836
- return existsSync(path) ? readFileSync(path, "utf8") : null;
837
- }
838
-
839
- // Run the idempotent v1→v2 migration over the existing artifact directories, then re-validate.
840
- function runUpgradeV2(only) {
841
- const slugs = listArtifactSlugs().filter((s) => !only || s === only);
842
- if (slugs.length === 0) fail(`no artifact directory named ${only}`);
843
- const totals = { upgraded: 0, elevationHinted: [], darkFirst: [], displayHinted: [] };
844
- for (const slug of slugs) {
845
- const dir = join(tokensDir, slug);
846
- const base = JSON.parse(readFileSync(join(dir, "design.tokens.json"), "utf8"));
847
- const resolver = JSON.parse(readFileSync(join(dir, "design.resolver.json"), "utf8"));
848
- const darkPath = join(dir, "design.tokens.dark.json");
849
- const dark = existsSync(darkPath) ? JSON.parse(readFileSync(darkPath, "utf8")) : null;
850
-
851
- const stats = upgradeArtifact(base, dark, resolver, readDesignMd(slug), slug);
852
- writeFileSync(join(dir, "design.tokens.json"), JSON.stringify(base, null, 2) + "\n");
853
- writeFileSync(join(dir, "design.resolver.json"), JSON.stringify(resolver, null, 2) + "\n");
854
- if (dark !== null) writeFileSync(darkPath, JSON.stringify(dark, null, 2) + "\n");
855
-
856
- totals.upgraded++;
857
- if (stats.elevationHinted) totals.elevationHinted.push(slug);
858
- if (stats.darkFirst) totals.darkFirst.push(slug);
859
- if (stats.displayHinted) totals.displayHinted.push(slug);
860
- const marks = [stats.darkFirst ? "dark-first" : null, stats.elevationHinted ? "elevation hinted" : null, stats.displayHinted ? "display hinted" : "display assigned"].filter(Boolean);
861
- console.log(`UPGRADE OK ${slug} (${marks.join(", ")})`);
862
- }
863
- console.log(`\nupgraded: ${totals.upgraded}`);
864
- console.log(`elevation hinted (design.md names shadow values): ${totals.elevationHinted.length} — ${totals.elevationHinted.join(", ") || "none"}`);
865
- console.log(`dark-first (theme-default: dark): ${totals.darkFirst.join(", ") || "none"}`);
866
- console.log(`display hinted: ${totals.displayHinted.length} of ${totals.upgraded}`);
867
- return totals;
868
- }
869
-
870
- // ---------------------------------------------------------------------------
871
- // Validation — SCHEMA.md rules 2–10 over an emitted artifact directory
872
- // ---------------------------------------------------------------------------
873
-
874
- // Collect every token in the artifact as path → token for alias resolution and type sweeps.
875
- function collectTokens(doc) {
876
- const tokens = new Map();
877
- const walk = (node, path) => {
878
- if (node && typeof node === "object" && "$type" in node) { tokens.set(path.join("."), node); return; }
879
- if (node && typeof node === "object") {
880
- for (const [k, v] of Object.entries(node)) if (!k.startsWith("$")) walk(v, [...path, k]);
881
- }
882
- };
883
- walk(doc, []);
884
- return tokens;
885
- }
886
-
887
- // Resolve an alias chain to its target token; records an error string instead on failure.
888
- function resolveAlias(ref, tokens, errors, context) {
889
- const seen = new Set();
890
- let cur = ref;
891
- while (typeof cur === "string" && /^\{[^}]+\}$/.test(cur)) {
892
- const path = cur.slice(1, -1);
893
- if (seen.has(path)) { errors.push(`${context}: alias cycle through {${path}}`); return null; }
894
- seen.add(path);
895
- const target = tokens.get(path);
896
- if (!target) { errors.push(`${context}: alias {${path}} does not resolve`); return null; }
897
- cur = target.$value;
898
- if (typeof cur !== "string" || !/^\{[^}]+\}$/.test(cur)) return target;
899
- }
900
- return null;
901
- }
902
-
903
- // Verify a preserved-css color token: parse-back equality plus the srgb hex round-trip.
904
- function verifyColorParseBack(name, token, errors) {
905
- const css = token.$extensions?.[EXT]?.css;
906
- if (typeof css !== "string") { errors.push(`${name}: color token missing preserved .css`); return; }
907
- let reparsed;
908
- try { reparsed = parseColorCss(css); } catch (e) { errors.push(`${name}: ${e.message}`); return; }
909
- if (JSON.stringify(reparsed) !== JSON.stringify(token.$value)) {
910
- errors.push(`${name}: parse-back mismatch — css ${JSON.stringify(css)} → ${JSON.stringify(reparsed)} != $value ${JSON.stringify(token.$value)}`);
911
- return;
912
- }
913
- if (token.$value.colorSpace === "srgb") {
914
- const viaHex = parseColorCss(token.$value.hex);
915
- if (JSON.stringify(viaHex.components) !== JSON.stringify(token.$value.components)) {
916
- errors.push(`${name}: srgb components do not round-trip through hex ${token.$value.hex}`);
917
- }
918
- }
919
- }
920
-
921
- // Validate one artifact directory against SCHEMA.md rules 2–10 (plus the type whitelist); returns error strings.
922
- function validateArtifact(dir) {
923
- const errors = [];
924
- const read = (f) => JSON.parse(readFileSync(join(dir, f), "utf8"));
925
- let doc, resolver;
926
- try { doc = read("design.tokens.json"); } catch (e) { return [`design.tokens.json: ${e.message}`]; }
927
- try { resolver = read("design.resolver.json"); } catch (e) { return [`design.resolver.json: ${e.message}`]; }
928
-
929
- // An alias-valued token ("{path.to.token}") defers shape/.css to its target (rule 6 resolves it).
930
- const isAliasValue = (t) => typeof t?.$value === "string" && /^\{[^}]+\}$/.test(t.$value);
931
-
932
- // Rule 2 — payload.
933
- const payload = doc.$extensions?.[EXT];
934
- if (!payload) { errors.push("payload: missing $extensions[com.nurix.design] on the base root"); return errors; }
935
- if (payload.schema !== "design-tokens/v2") errors.push(`payload.schema: ${payload.schema}`);
936
- if (payload.dtcg !== "2025.10") errors.push(`payload.dtcg: ${payload.dtcg}`);
937
- if (!payload.product) errors.push("payload.product: empty");
938
- if (!LEGAL_PROVENANCE.includes(payload.provenance)) errors.push(`payload.provenance: ${payload.provenance}`);
939
- if (!/^\d{4}-\d{2}-\d{2}$/.test(payload.sourced ?? "")) errors.push(`payload.sourced: ${payload.sourced}`);
940
- if (!LEGAL_VERBOSITY.includes(payload.verbosity)) errors.push(`payload.verbosity: ${payload.verbosity}`);
941
- if (!LEGAL_FEEDBACK.includes(payload.feedback)) errors.push(`payload.feedback: ${payload.feedback}`);
942
- if (!LEGAL_DENSITY.includes(payload["density-tier"])) errors.push(`payload.density-tier: ${payload["density-tier"]}`);
943
- if (!Array.isArray(payload["product-traits"]) || payload["product-traits"].length === 0) errors.push("payload.product-traits: empty");
944
- if (payload["theme-default"] !== undefined && !["light", "dark"].includes(payload["theme-default"])) errors.push(`payload.theme-default: ${payload["theme-default"]}`);
945
- if (payload.anchors !== undefined && (!Array.isArray(payload.anchors) || payload.anchors.length < 2 || payload.anchors.length > 3 || payload.anchors.some((a) => typeof a !== "string" || a.trim() === ""))) {
946
- errors.push(`payload.anchors: must be 2–3 non-empty strings (${JSON.stringify(payload.anchors)})`);
947
- }
948
- const lm = payload["lever-manifest"];
949
- for (const key of ["spacing-scale", "radius-scale", "shadow-levels", "motion", "icons", "accent-count"]) {
950
- if (lm?.[key] === undefined) errors.push(`lever-manifest.${key}: missing`);
951
- }
952
- if (errors.length > 0) return errors;
953
-
954
- // Rule 3 — primitive coverage with exact $type.
955
- for (const [key, type] of PRIMITIVE_TYPES_V2) {
956
- const t = doc.primitive?.[key];
957
- if (!t) errors.push(`primitive.${key}: missing`);
958
- else if (t.$type !== type) errors.push(`primitive.${key}: $type ${t.$type} (want ${type})`);
959
- else if (t.$value === undefined || t.$value === "") errors.push(`primitive.${key}: empty $value`);
960
- }
961
- const stagger = doc.primitive?.["stagger-unit"]?.$value?.value;
962
- if (typeof stagger === "number" && (stagger < 0 || stagger > 60)) errors.push(`primitive.stagger-unit: ${stagger}ms outside 0–60ms`);
963
-
964
- // Rule 4 — color coverage (an aliased role takes its target's shape and .css).
965
- for (const role of COLOR_ROLES_V2) {
966
- const t = doc.color?.[role];
967
- if (!t) { errors.push(`color.${role}: missing`); continue; }
968
- if (t.$type !== "color") errors.push(`color.${role}: $type ${t.$type}`);
969
- if (isAliasValue(t)) continue;
970
- if (!["oklch", "srgb"].includes(t.$value?.colorSpace)) errors.push(`color.${role}: colorSpace ${t.$value?.colorSpace}`);
971
- if (typeof t.$extensions?.[EXT]?.css !== "string") errors.push(`color.${role}: missing .css`);
972
- }
973
-
974
- // Rule 5 — typography coverage (the seven-step ramp; family.display optional).
975
- for (const fam of ["heading", "body", "mono"]) {
976
- const t = doc.typography?.family?.[fam];
977
- if (!t || t.$type !== "fontFamily") errors.push(`typography.family.${fam}: missing or mistyped`);
978
- }
979
- if (doc.typography?.family?.display !== undefined && doc.typography.family.display.$type !== "fontFamily") {
980
- errors.push("typography.family.display: mistyped (optional slot, fontFamily shape)");
981
- }
982
- for (const step of RAMP_STEPS_V2) {
983
- const t = doc.typography?.ramp?.[step];
984
- if (!t || t.$type !== "typography") { errors.push(`typography.ramp.${step}: missing or mistyped`); continue; }
985
- for (const sub of ["fontFamily", "fontSize", "fontWeight", "letterSpacing", "lineHeight"]) {
986
- if (t.$value?.[sub] === undefined) errors.push(`typography.ramp.${step}.${sub}: missing sub-value`);
987
- }
988
- }
989
-
990
- // Elevation coverage (rule 13) — four shadow composites, byte form preserved and parse-backed.
991
- for (const level of ELEVATION_TOKENS) {
992
- const t = doc.elevation?.[level];
993
- if (!t || t.$type !== "shadow") { errors.push(`elevation.${level}: missing or not $type shadow`); continue; }
994
- if (!Array.isArray(t.$value) || t.$value.length === 0) { errors.push(`elevation.${level}: $value must be a non-empty layer array`); continue; }
995
- const css = t.$extensions?.[EXT]?.css;
996
- if (typeof css !== "string") { errors.push(`elevation.${level}: missing .css`); continue; }
997
- try {
998
- if (JSON.stringify(parseShadowCss(css)) !== JSON.stringify(t.$value)) errors.push(`elevation.${level}: css does not parse back to $value`);
999
- } catch (e) {
1000
- errors.push(`elevation.${level}: ${e.message}`);
1001
- }
1002
- }
1003
-
1004
- // Wash shape (rule 14) — optional group, ≤ 2 gradient tokens.
1005
- if (doc.wash !== undefined) {
1006
- const washKeys = Object.keys(doc.wash).filter((k) => !k.startsWith("$"));
1007
- if (washKeys.length > 2) errors.push(`wash: at most 2 tokens (found ${washKeys.length})`);
1008
- for (const key of washKeys) {
1009
- if (!["surface", "emphasis"].includes(key)) errors.push(`wash.${key}: not a legal slot`);
1010
- else if (doc.wash[key].$type !== "gradient") errors.push(`wash.${key}: not $type gradient`);
1011
- }
1012
- }
1013
- if (errors.length > 0) return errors;
1014
-
1015
- // Rule 6 — alias integrity (token values and composite sub-values), with type expectations.
1016
- const tokens = collectTokens(doc);
1017
- const expectType = (ref, want, context) => {
1018
- const target = resolveAlias(ref, tokens, errors, context);
1019
- if (target && want && target.$type !== want) errors.push(`${context}: alias ${ref} resolves to $type ${target.$type} (want ${want})`);
1020
- };
1021
- for (const [path, token] of tokens) {
1022
- if (typeof token.$value === "string" && token.$value.startsWith("{")) expectType(token.$value, token.$type, path);
1023
- if (token.$type === "typography") {
1024
- const subTypes = { fontFamily: "fontFamily", fontWeight: "fontWeight", letterSpacing: "number", fontSize: "dimension" };
1025
- for (const [sub, want] of Object.entries(subTypes)) {
1026
- const v = token.$value[sub];
1027
- if (typeof v === "string" && v.startsWith("{")) expectType(v, want, `${path}.${sub}`);
1028
- }
1029
- }
1030
- }
1031
-
1032
- // Rule 7 — manifest scales.
1033
- const ascending = (arr) => arr.every((v, i) => i === 0 || v > arr[i - 1]);
1034
- for (const key of ["spacing-scale", "radius-scale"]) {
1035
- if (!Array.isArray(lm[key]) || lm[key].length === 0 || !ascending(lm[key])) errors.push(`lever-manifest.${key}: not non-empty ascending`);
1036
- }
1037
- if (!Array.isArray(lm.motion.durations) || lm.motion.durations.length === 0 || !ascending(lm.motion.durations)) errors.push("lever-manifest.motion.durations: not non-empty ascending");
1038
- if (!Array.isArray(lm.motion.easings) || lm.motion.easings.length === 0) errors.push("lever-manifest.motion.easings: empty");
1039
- const ranks = (lm["shadow-levels"] ?? []).map((s) => SHADOW_RANK[s]);
1040
- if (ranks.length === 0 || ranks.some((r) => r === undefined) || !ascending(ranks)) errors.push(`lever-manifest.shadow-levels: ${JSON.stringify(lm["shadow-levels"])}`);
1041
- if (!lm.icons?.family) errors.push("lever-manifest.icons.family: empty");
1042
- if (!(lm["accent-count"] >= 1)) errors.push(`lever-manifest.accent-count: ${lm["accent-count"]}`);
1043
-
1044
- // Rule 8 — the look's own base values sit on its own allowlist; an off-manifest value is legal
1045
- // only as a recorded deviation (a deviation: $description — rule 15's vocabulary).
1046
- const deviationTagged = (t) => typeof t?.$description === "string" && t.$description.startsWith("deviation:");
1047
- if (!lm["radius-scale"].includes(doc.primitive.radius.$value.value) && !deviationTagged(doc.primitive.radius)) errors.push(`radius ${doc.primitive.radius.$value.value} not on radius-scale`);
1048
- for (const key of ["duration-fast", "duration-normal", "duration-slow"]) {
1049
- if (!lm.motion.durations.includes(doc.primitive[key].$value.value) && !deviationTagged(doc.primitive[key])) errors.push(`${key} ${doc.primitive[key].$value.value} not in motion.durations`);
1050
- }
1051
- const easeCss = doc.primitive["ease-default"].$extensions?.[EXT]?.css;
1052
- if (!lm.motion.easings.includes(easeCss) && !deviationTagged(doc.primitive["ease-default"])) errors.push(`ease-default ${easeCss} not in motion.easings`);
1053
-
1054
- // Rule 9 — parse-back for every preserved .css (aliased roles carry none).
1055
- for (const role of COLOR_ROLES_V2) {
1056
- if (!isAliasValue(doc.color[role])) verifyColorParseBack(`color.${role}`, doc.color[role], errors);
1057
- }
1058
- const ht = doc.primitive["heading-tracking"];
1059
- const htCss = ht.$extensions?.[EXT]?.css;
1060
- if (typeof htCss !== "string") errors.push("primitive.heading-tracking: missing .css");
1061
- else if (parseTrackingCss(htCss) !== ht.$value) errors.push(`primitive.heading-tracking: css ${htCss} != $value ${ht.$value}`);
1062
- if (easeCss === undefined) errors.push("primitive.ease-default: missing .css");
1063
- else if (JSON.stringify(parseCubicBezierCss(easeCss)) !== JSON.stringify(doc.primitive["ease-default"].$value)) errors.push("primitive.ease-default: css/$value mismatch");
1064
- for (const key of ["font-sans", "font-mono"]) {
1065
- const t = doc.primitive[key];
1066
- const css = t.$extensions?.[EXT]?.css;
1067
- if (typeof css !== "string") { errors.push(`primitive.${key}: missing .css`); continue; }
1068
- if (JSON.stringify(parseFontStackCss(css)) !== JSON.stringify(t.$value)) errors.push(`primitive.${key}: css/$value mismatch`);
1069
- }
1070
- for (const step of RAMP_STEPS_V2) {
1071
- const t = doc.typography.ramp[step];
1072
- const css = t.$extensions?.[EXT]?.css;
1073
- if (css?.letterSpacing !== undefined) {
1074
- const reparsed = parseLetterSpacingCss(css.letterSpacing, t.$value.fontSize.value);
1075
- if (reparsed !== t.$value.letterSpacing) errors.push(`typography.ramp.${step}: letterSpacing css ${css.letterSpacing} → ${reparsed} != ${t.$value.letterSpacing}`);
1076
- }
1077
- }
1078
-
1079
- // Rule 10 + resolver coherence — the dark file carries exactly the 36-role color group; the
1080
- // resolver's theme default mirrors the payload's theme-default.
1081
- const darkNamed = resolver.modifiers?.[0]?.contexts?.dark !== undefined;
1082
- const darkExists = existsSync(join(dir, "design.tokens.dark.json"));
1083
- if (darkNamed !== darkExists) errors.push(`dark file/resolver mismatch: named=${darkNamed} exists=${darkExists}`);
1084
- if (resolver.schema !== "design-resolver/v1") errors.push(`resolver.schema: ${resolver.schema}`);
1085
- const expectedDefault = payload["theme-default"] ?? "light";
1086
- if (resolver.modifiers?.[0]?.default !== expectedDefault) errors.push(`resolver: theme default ${resolver.modifiers?.[0]?.default} does not mirror payload theme-default ${expectedDefault}`);
1087
- if (darkExists) {
1088
- const dark = read("design.tokens.dark.json");
1089
- const rootKeys = Object.keys(dark);
1090
- if (rootKeys.length !== 1 || rootKeys[0] !== "color") errors.push(`dark file: root keys ${rootKeys.join(",")} (want exactly color)`);
1091
- for (const role of COLOR_ROLES_V2) {
1092
- const t = dark.color?.[role];
1093
- if (!t || t.$type !== "color") { errors.push(`dark color.${role}: missing or mistyped`); continue; }
1094
- if (!isAliasValue(t)) verifyColorParseBack(`dark color.${role}`, t, errors);
1095
- }
1096
- for (const role of Object.keys(dark.color ?? {})) if (!COLOR_ROLES_V2.includes(role)) errors.push(`dark color.${role}: not a legal role`);
1097
- }
1098
-
1099
- // Rule 11 (type whitelist sweep) — only the ten legal $type values appear.
1100
- for (const [path, token] of tokens) {
1101
- if (!LEGAL_TYPES.includes(token.$type)) errors.push(`${path}: illegal $type ${token.$type}`);
1102
- }
1103
- return errors;
1104
- }
1105
-
1106
- // ---------------------------------------------------------------------------
1107
- // Strip mode — remove the machine blocks and their orphaned prose references
1108
- // ---------------------------------------------------------------------------
1109
-
1110
- // A sentence or parenthetical that references the (removed) machine blocks.
1111
- const ORPHAN_PATTERN = /machine blocks?|machine-carried|lever[- ]manifest|design-primitives|`hint:`|manifest carries|manifest ships|manifest holds|the manifest|manifest's|both blocks/i;
1112
- // A reference to the in-block `# rationale:` / `# derived:` comment convention — also orphaned by the strip.
1113
- const ANNOTATION_PATTERN = /`# ?(rationale|derived|corpus|note)/i;
1114
-
1115
- // Re-wrap a paragraph's text at ~100 columns, preserving a leading indent/bullet prefix.
1116
- function rewrap(text, prefix = "", width = 100) {
1117
- const words = text.split(/\s+/).filter((w) => w !== "");
1118
- const lines = [];
1119
- let cur = prefix;
1120
- let bare = prefix.replace(/\S/g, " ");
1121
- for (const w of words) {
1122
- if (cur.trim() === "" || (cur + " " + w).length <= width) cur = cur.trim() === "" && cur === prefix ? prefix + w : cur + " " + w;
1123
- else { lines.push(cur); cur = bare + w; }
1124
- }
1125
- if (cur.trim() !== "") lines.push(cur);
1126
- return lines;
1127
- }
1128
-
1129
- // Lossless sentence split: cut after . ! ? only when a new sentence plausibly starts (whitespace + capital/opener).
1130
- function splitSentences(text) {
1131
- const out = [];
1132
- let start = 0;
1133
- for (let i = 0; i < text.length; i++) {
1134
- if (".!?".includes(text[i]) && /^\s+["'*(\[`—A-Z0-9]/.test(text.slice(i + 1))) {
1135
- const end = i + 1 + /^\s+/.exec(text.slice(i + 1))[0].length;
1136
- out.push(text.slice(start, end));
1137
- start = end;
1138
- }
1139
- }
1140
- if (start < text.length) out.push(text.slice(start));
1141
- return out;
1142
- }
1143
-
1144
- // Remove orphaned block-referencing parentheticals and sentences from one prose paragraph; null = unchanged.
1145
- function stripParagraph(lines) {
1146
- const joined = lines.map((l) => l.trim()).join(" ");
1147
- // 1. surgical: drop "(…)" parentheticals whose content references the blocks.
1148
- let text = joined.replace(/\s*\(([^()]*)\)/g, (m, inner) => (ORPHAN_PATTERN.test(inner) ? "" : m));
1149
- // 2. clause-level: drop a "; …" clause that only cites the annotation convention, keeping its sentence.
1150
- text = text.replace(/;\s*[^.!?;]*`# ?(rationale|derived|corpus|note)[^.!?]*?(?=[.!?])/gi, "");
1151
- // 3. sentence-level: drop whole sentences that still reference the blocks or their annotations.
1152
- const sentences = splitSentences(text);
1153
- if (sentences.join("") !== text) throw new Error(`lossy sentence split in: ${text.slice(0, 80)}…`);
1154
- text = sentences.filter((s) => !ORPHAN_PATTERN.test(s) && !ANNOTATION_PATTERN.test(s)).join("").trim();
1155
- if (text === joined) return null;
1156
- if (text === "") return [];
1157
- const m = /^(\s*(?:[-*]\s+|\d+\.\s+)?)/.exec(lines[0]);
1158
- return rewrap(text, m[1] ?? "");
1159
- }
1160
-
1161
- // Strip both machine blocks (and orphaned prose) from one source text; returns { text, summary }.
1162
- function stripSource(source) {
1163
- const summary = { blocksRemoved: 0, paragraphsEdited: 0 };
1164
- let text = source;
1165
- for (const label of ["design-primitives", "lever-manifest"]) {
1166
- const re = new RegExp("\\n*^`{3,}\\s*yaml\\s+" + label + "\\s*$\\n[\\s\\S]*?^`{3,}\\s*$\\n?", "m");
1167
- if (re.test(text)) { text = text.replace(re, "\n"); summary.blocksRemoved++; }
1168
- }
1169
-
1170
- // Paragraph pass: skip frontmatter, headings, tables, and fenced code — only plain prose is edited.
1171
- const lines = text.split("\n");
1172
- const out = [];
1173
- let i = 0;
1174
- let inFence = false;
1175
- if (lines[0] === "---") {
1176
- out.push(lines[0]);
1177
- for (i = 1; i < lines.length; i++) { out.push(lines[i]); if (lines[i] === "---") { i++; break; } }
1178
- }
1179
- while (i < lines.length) {
1180
- const line = lines[i];
1181
- if (/^`{3,}/.test(line.trim())) inFence = !inFence;
1182
- const isProse = !inFence && line.trim() !== "" && !/^(#|\||`{3,}|>)/.test(line.trim());
1183
- if (!isProse) { out.push(line); i++; continue; }
1184
- const para = [];
1185
- while (i < lines.length && lines[i].trim() !== "" && !/^(#|\||`{3,}|>)/.test(lines[i].trim())) {
1186
- // a new bullet starts a new paragraph unit
1187
- if (para.length > 0 && /^\s*[-*]\s+/.test(lines[i])) break;
1188
- para.push(lines[i]); i++;
1189
- }
1190
- const replaced = stripParagraph(para);
1191
- if (replaced === null) out.push(...para);
1192
- else { summary.paragraphsEdited++; out.push(...replaced); }
1193
- }
1194
- let result = out.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "\n");
1195
- return { text: result, summary };
1196
- }
1197
-
1198
- // ---------------------------------------------------------------------------
1199
- // Drivers
1200
- // ---------------------------------------------------------------------------
1201
-
1202
- // Enumerate the source entries: nurix.md + every library/*.md, slug = filename minus .md.
1203
- function listSources() {
1204
- const entries = [{ slug: "nurix", path: join(designMdDir, "nurix.md") }];
1205
- for (const f of readdirSync(join(designMdDir, "library")).filter((f) => f.endsWith(".md")).sort()) {
1206
- entries.push({ slug: basename(f, ".md"), path: join(designMdDir, "library", f) });
1207
- }
1208
- return entries;
1209
- }
1210
-
1211
- function runConvert(only, reportPath) {
1212
- const sources = listSources().filter((s) => !only || s.slug === only);
1213
- if (sources.length === 0) fail(`no source entry named ${only}`);
1214
- const report = { converted: [], failed: [], totals: { migrated: 0, rootAttached: 0, dropped: 0, hints: 0, darkFiles: [], fontDrops: 0 } };
1215
-
1216
- for (const { slug, path } of sources) {
1217
- let result;
1218
- try {
1219
- const sourceText = readFileSync(path, "utf8");
1220
- result = convertEntry(sourceText, slug);
1221
- // The converter emits the v2 schema: the same idempotent upgrade transform the migration
1222
- // mode runs, applied to the freshly converted (v1-shaped) documents.
1223
- upgradeArtifact(result.base, result.dark, result.resolver, sourceText, slug);
1224
- } catch (e) {
1225
- report.failed.push({ slug, error: e.message });
1226
- console.log(`CONVERT FAIL ${slug}: ${e.message}`);
1227
- continue;
1228
- }
1229
- const dir = join(tokensDir, slug);
1230
- mkdirSync(dir, { recursive: true });
1231
- writeFileSync(join(dir, "design.tokens.json"), JSON.stringify(result.base, null, 2) + "\n");
1232
- writeFileSync(join(dir, "design.resolver.json"), JSON.stringify(result.resolver, null, 2) + "\n");
1233
- if (result.dark) writeFileSync(join(dir, "design.tokens.dark.json"), JSON.stringify(result.dark, null, 2) + "\n");
1234
-
1235
- const n = result.notes;
1236
- report.converted.push({ slug, migrated: n.migrated, rootAttached: n.rootAttached, dropped: n.dropped, hints: n.hints, ramp: n.ramp, drops: n.drops, irregularities: n.irregularities, darkFile: n.darkFile });
1237
- report.totals.migrated += n.migrated;
1238
- report.totals.rootAttached += n.rootAttached.length;
1239
- report.totals.dropped += n.dropped.length;
1240
- report.totals.hints += n.hints.length;
1241
- if (n.darkFile) report.totals.darkFiles.push(slug);
1242
- if (n.drops.some((d) => d.startsWith("fonts"))) report.totals.fontDrops++;
1243
- console.log(`CONVERT OK ${slug} (annotations: ${n.migrated}${n.darkFile ? ", dark" : ""}${n.hints.length ? `, hints: ${n.hints.length}` : ""})`);
1244
- }
1245
-
1246
- // _dropped: comments that landed nowhere. Non-token comments are preserved on the root $description
1247
- // and listed per entry as rootAttached — reported here so nothing disappears silently.
1248
- const droppedReport = report.converted.flatMap((e) => e.dropped.map((d) => ({ slug: e.slug, ...d })));
1249
- console.log(`\n_dropped (unattachable comments): ${droppedReport.length}`);
1250
- for (const d of droppedReport) console.log(` ${d.slug}: ${JSON.stringify(d)}`);
1251
- const rootAttached = report.converted.flatMap((e) => e.rootAttached.map((r) => ({ slug: e.slug, ...r })));
1252
- console.log(`non-token comments preserved on root $description: ${rootAttached.length}`);
1253
- for (const r of rootAttached) console.log(` ${r.slug} [${r.where}] ${r.comment}`);
1254
-
1255
- if (reportPath) writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n");
1256
- return report;
1257
- }
1258
-
1259
- function runValidate(only) {
1260
- const sources = listSources().filter((s) => !only || s.slug === only);
1261
- let pass = 0, failCount = 0;
1262
- for (const { slug } of sources) {
1263
- const dir = join(tokensDir, slug);
1264
- if (!existsSync(join(dir, "design.tokens.json"))) { console.log(`VALIDATE FAIL ${slug}: no artifact`); failCount++; continue; }
1265
- let errors;
1266
- try { errors = validateArtifact(dir); } catch (e) { errors = [`validator crash: ${e.message}`]; }
1267
- if (errors.length === 0) { console.log(`VALIDATE OK ${slug}`); pass++; }
1268
- else {
1269
- failCount++;
1270
- console.log(`VALIDATE FAIL ${slug}`);
1271
- for (const err of errors) console.log(` ${err}`);
1272
- }
1273
- }
1274
- console.log(`\nvalidation: ${pass} passed, ${failCount} failed of ${sources.length}`);
1275
- return failCount === 0;
1276
- }
1277
-
1278
- function runStrip(paths) {
1279
- if (paths.length === 0) fail("--strip requires explicit file paths (it rewrites sources in place; never run it implicitly)");
1280
- for (const p of paths) {
1281
- const abs = resolve(p);
1282
- const { text, summary } = stripSource(readFileSync(abs, "utf8"));
1283
- writeFileSync(abs, text);
1284
- console.log(`STRIP ${abs}: ${summary.blocksRemoved} machine block(s) removed, ${summary.paragraphsEdited} paragraph(s) edited`);
1285
- }
1286
- }
1287
-
1288
- // ---------------------------------------------------------------------------
1289
- // CLI
1290
- // ---------------------------------------------------------------------------
1291
-
1292
- const args = process.argv.slice(2);
1293
- const flag = (name) => args.includes(name);
1294
- const opt = (name) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : null; };
1295
-
1296
- if (flag("--strip")) {
1297
- runStrip(args.slice(args.indexOf("--strip") + 1).filter((a) => !a.startsWith("--")));
1298
- } else if (flag("--upgrade-v2")) {
1299
- runUpgradeV2(opt("--only"));
1300
- process.exit(runValidate(opt("--only")) ? 0 : 1);
1301
- } else if (flag("--validate")) {
1302
- process.exit(runValidate(opt("--only")) ? 0 : 1);
1303
- } else {
1304
- const report = runConvert(opt("--only"), opt("--report"));
1305
- const ok = runValidate(opt("--only"));
1306
- console.log(`\nconverted: ${report.converted.length}, failed: ${report.failed.length}`);
1307
- console.log(`annotations migrated to token $description: ${report.totals.migrated}`);
1308
- console.log(`dark files: ${report.totals.darkFiles.join(", ") || "none"}`);
1309
- console.log(`source hints found: ${report.totals.hints}`);
1310
- console.log(`manifest fonts duplication dropped: ${report.totals.fontDrops} entries`);
1311
- process.exit(ok && report.failed.length === 0 ? 0 : 1);
1312
- }