@nurix/etna 0.18.20 → 0.18.21

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 (272) hide show
  1. package/README.md +1 -1
  2. package/data/harness-index.json +1 -1
  3. package/data/harnesses/code/CLAUDE.md +1 -1
  4. package/data/harnesses/code/agents/brainstorm.md +1 -1
  5. package/data/harnesses/code/agents/maintenance.md +2 -2
  6. package/data/harnesses/code/harness.json +2 -2
  7. package/data/harnesses/code/rules/database.md +38 -0
  8. package/data/harnesses/code/rules/express.md +28 -0
  9. package/data/harnesses/code/rules/frontend.md +14 -0
  10. package/data/harnesses/code/rules/general.md +2 -40
  11. package/data/harnesses/code/rules/nomenclature.md +1 -8
  12. package/data/harnesses/code/rules/nustack.md +150 -0
  13. package/data/harnesses/code/skills/brainstorm/README.md +1 -1
  14. package/data/harnesses/code/skills/brainstorm/SKILL.md +1 -1
  15. package/data/harnesses/code/skills/brainstorm/exit-artifacts.md +4 -4
  16. package/data/harnesses/code/skills/brainstorm/templates/source-of-truth.md +3 -3
  17. package/data/harnesses/code/skills/design/SKILL.md +14 -11
  18. package/data/harnesses/code/skills/design/data/catalog/rules.csv +4 -4
  19. package/data/harnesses/code/skills/design/data/combinations/Readme.md +29 -0
  20. package/data/harnesses/code/skills/design/data/combinations/charts.csv +33 -0
  21. package/data/harnesses/code/skills/design/data/combinations/fonts-known.json +437 -0
  22. package/data/harnesses/code/skills/design/data/combinations/icons.csv +157 -0
  23. package/data/harnesses/code/skills/design/data/combinations/landing.csv +38 -0
  24. package/data/harnesses/code/skills/design/data/combinations/lucide-names.json +2001 -0
  25. package/data/harnesses/code/skills/design/data/combinations/manifest.json +283 -0
  26. package/data/harnesses/code/skills/design/data/combinations/palettes.csv +49 -0
  27. package/data/harnesses/code/skills/design/data/combinations/product-router.csv +80 -0
  28. package/data/harnesses/code/skills/design/data/combinations/styles.csv +54 -0
  29. package/data/harnesses/code/skills/design/data/combinations/typography.csv +61 -0
  30. package/data/harnesses/code/skills/design/data/design-md/SCHEMA.md +27 -238
  31. package/data/harnesses/code/skills/design/data/design-md/library/airbnb.md +7 -88
  32. package/data/harnesses/code/skills/design/data/design-md/library/airtable.md +1 -82
  33. package/data/harnesses/code/skills/design/data/design-md/library/anthropic-claude.md +4 -85
  34. package/data/harnesses/code/skills/design/data/design-md/library/apple.md +5 -86
  35. package/data/harnesses/code/skills/design/data/design-md/library/binance.md +4 -87
  36. package/data/harnesses/code/skills/design/data/design-md/library/bmw.md +7 -86
  37. package/data/harnesses/code/skills/design/data/design-md/library/cal.md +4 -84
  38. package/data/harnesses/code/skills/design/data/design-md/library/clay.md +5 -86
  39. package/data/harnesses/code/skills/design/data/design-md/library/clickhouse.md +3 -84
  40. package/data/harnesses/code/skills/design/data/design-md/library/cohere.md +2 -82
  41. package/data/harnesses/code/skills/design/data/design-md/library/coinbase.md +10 -91
  42. package/data/harnesses/code/skills/design/data/design-md/library/composio.md +2 -84
  43. package/data/harnesses/code/skills/design/data/design-md/library/cursor.md +3 -85
  44. package/data/harnesses/code/skills/design/data/design-md/library/elevenlabs.md +7 -90
  45. package/data/harnesses/code/skills/design/data/design-md/library/expo.md +0 -80
  46. package/data/harnesses/code/skills/design/data/design-md/library/figma.md +0 -80
  47. package/data/harnesses/code/skills/design/data/design-md/library/framer.md +5 -85
  48. package/data/harnesses/code/skills/design/data/design-md/library/hashicorp.md +2 -82
  49. package/data/harnesses/code/skills/design/data/design-md/library/ibm.md +5 -86
  50. package/data/harnesses/code/skills/design/data/design-md/library/intercom.md +9 -91
  51. package/data/harnesses/code/skills/design/data/design-md/library/kraken.md +9 -89
  52. package/data/harnesses/code/skills/design/data/design-md/library/linear-app.md +0 -115
  53. package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +5 -85
  54. package/data/harnesses/code/skills/design/data/design-md/library/meta.md +7 -88
  55. package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +3 -84
  56. package/data/harnesses/code/skills/design/data/design-md/library/mintlify.md +4 -85
  57. package/data/harnesses/code/skills/design/data/design-md/library/miro.md +8 -88
  58. package/data/harnesses/code/skills/design/data/design-md/library/mistral-ai.md +8 -88
  59. package/data/harnesses/code/skills/design/data/design-md/library/mongodb.md +4 -85
  60. package/data/harnesses/code/skills/design/data/design-md/library/notion.md +4 -84
  61. package/data/harnesses/code/skills/design/data/design-md/library/nvidia.md +7 -88
  62. package/data/harnesses/code/skills/design/data/design-md/library/ollama.md +5 -86
  63. package/data/harnesses/code/skills/design/data/design-md/library/opencode-ai.md +6 -87
  64. package/data/harnesses/code/skills/design/data/design-md/library/posthog.md +9 -91
  65. package/data/harnesses/code/skills/design/data/design-md/library/raycast.md +5 -88
  66. package/data/harnesses/code/skills/design/data/design-md/library/replicate.md +9 -89
  67. package/data/harnesses/code/skills/design/data/design-md/library/resend.md +4 -119
  68. package/data/harnesses/code/skills/design/data/design-md/library/revolut.md +4 -84
  69. package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +0 -82
  70. package/data/harnesses/code/skills/design/data/design-md/library/sanity.md +3 -86
  71. package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +13 -94
  72. package/data/harnesses/code/skills/design/data/design-md/library/shopify.md +2 -82
  73. package/data/harnesses/code/skills/design/data/design-md/library/slack.md +6 -88
  74. package/data/harnesses/code/skills/design/data/design-md/library/spacex.md +13 -94
  75. package/data/harnesses/code/skills/design/data/design-md/library/spotify.md +4 -86
  76. package/data/harnesses/code/skills/design/data/design-md/library/stripe.md +7 -88
  77. package/data/harnesses/code/skills/design/data/design-md/library/supabase.md +6 -88
  78. package/data/harnesses/code/skills/design/data/design-md/library/superhuman.md +5 -86
  79. package/data/harnesses/code/skills/design/data/design-md/library/tesla.md +6 -86
  80. package/data/harnesses/code/skills/design/data/design-md/library/theverge.md +12 -93
  81. package/data/harnesses/code/skills/design/data/design-md/library/together-ai.md +11 -91
  82. package/data/harnesses/code/skills/design/data/design-md/library/uber.md +8 -88
  83. package/data/harnesses/code/skills/design/data/design-md/library/vercel.md +2 -82
  84. package/data/harnesses/code/skills/design/data/design-md/library/voltagent.md +11 -126
  85. package/data/harnesses/code/skills/design/data/design-md/library/warp.md +8 -88
  86. package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +3 -83
  87. package/data/harnesses/code/skills/design/data/design-md/library/wired.md +10 -91
  88. package/data/harnesses/code/skills/design/data/design-md/library/wise.md +7 -90
  89. package/data/harnesses/code/skills/design/data/design-md/library/x-ai.md +6 -88
  90. package/data/harnesses/code/skills/design/data/design-md/library/zapier.md +9 -89
  91. package/data/harnesses/code/skills/design/data/design-md/nurix.md +4 -87
  92. package/data/harnesses/code/skills/design/data/design-tokens/SCHEMA.md +580 -0
  93. package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.resolver.json +20 -0
  94. package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.tokens.json +964 -0
  95. package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.resolver.json +20 -0
  96. package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.tokens.json +960 -0
  97. package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.resolver.json +20 -0
  98. package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.tokens.json +965 -0
  99. package/data/harnesses/code/skills/design/data/design-tokens/apple/design.resolver.json +20 -0
  100. package/data/harnesses/code/skills/design/data/design-tokens/apple/design.tokens.json +954 -0
  101. package/data/harnesses/code/skills/design/data/design-tokens/binance/design.resolver.json +20 -0
  102. package/data/harnesses/code/skills/design/data/design-tokens/binance/design.tokens.json +949 -0
  103. package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.resolver.json +20 -0
  104. package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.tokens.json +962 -0
  105. package/data/harnesses/code/skills/design/data/design-tokens/cal/design.resolver.json +20 -0
  106. package/data/harnesses/code/skills/design/data/design-tokens/cal/design.tokens.json +958 -0
  107. package/data/harnesses/code/skills/design/data/design-tokens/clay/design.resolver.json +20 -0
  108. package/data/harnesses/code/skills/design/data/design-tokens/clay/design.tokens.json +953 -0
  109. package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.resolver.json +20 -0
  110. package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.tokens.json +960 -0
  111. package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.resolver.json +20 -0
  112. package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.tokens.json +973 -0
  113. package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.resolver.json +20 -0
  114. package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.tokens.json +954 -0
  115. package/data/harnesses/code/skills/design/data/design-tokens/composio/design.resolver.json +20 -0
  116. package/data/harnesses/code/skills/design/data/design-tokens/composio/design.tokens.json +963 -0
  117. package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.resolver.json +20 -0
  118. package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.tokens.json +968 -0
  119. package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.resolver.json +20 -0
  120. package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.tokens.json +981 -0
  121. package/data/harnesses/code/skills/design/data/design-tokens/expo/design.resolver.json +20 -0
  122. package/data/harnesses/code/skills/design/data/design-tokens/expo/design.tokens.json +962 -0
  123. package/data/harnesses/code/skills/design/data/design-tokens/figma/design.resolver.json +20 -0
  124. package/data/harnesses/code/skills/design/data/design-tokens/figma/design.tokens.json +964 -0
  125. package/data/harnesses/code/skills/design/data/design-tokens/framer/design.resolver.json +20 -0
  126. package/data/harnesses/code/skills/design/data/design-tokens/framer/design.tokens.json +959 -0
  127. package/data/harnesses/code/skills/design/data/design-tokens/goldens.json +251 -0
  128. package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.resolver.json +20 -0
  129. package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.tokens.json +965 -0
  130. package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.resolver.json +20 -0
  131. package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.tokens.json +978 -0
  132. package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.resolver.json +20 -0
  133. package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.tokens.json +982 -0
  134. package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.resolver.json +20 -0
  135. package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.tokens.json +958 -0
  136. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.resolver.json +23 -0
  137. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.dark.json +589 -0
  138. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.json +964 -0
  139. package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.resolver.json +20 -0
  140. package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.tokens.json +955 -0
  141. package/data/harnesses/code/skills/design/data/design-tokens/meta/design.resolver.json +20 -0
  142. package/data/harnesses/code/skills/design/data/design-tokens/meta/design.tokens.json +977 -0
  143. package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.resolver.json +20 -0
  144. package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.tokens.json +950 -0
  145. package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.resolver.json +20 -0
  146. package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.tokens.json +976 -0
  147. package/data/harnesses/code/skills/design/data/design-tokens/miro/design.resolver.json +20 -0
  148. package/data/harnesses/code/skills/design/data/design-tokens/miro/design.tokens.json +979 -0
  149. package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.resolver.json +20 -0
  150. package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.tokens.json +955 -0
  151. package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.resolver.json +20 -0
  152. package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.tokens.json +958 -0
  153. package/data/harnesses/code/skills/design/data/design-tokens/notion/design.resolver.json +20 -0
  154. package/data/harnesses/code/skills/design/data/design-tokens/notion/design.tokens.json +953 -0
  155. package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.resolver.json +20 -0
  156. package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.tokens.json +904 -0
  157. package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.resolver.json +20 -0
  158. package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.tokens.json +957 -0
  159. package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.resolver.json +20 -0
  160. package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.tokens.json +945 -0
  161. package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.resolver.json +20 -0
  162. package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.tokens.json +960 -0
  163. package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.resolver.json +20 -0
  164. package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.tokens.json +953 -0
  165. package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.resolver.json +20 -0
  166. package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.tokens.json +955 -0
  167. package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.resolver.json +20 -0
  168. package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.tokens.json +958 -0
  169. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.resolver.json +23 -0
  170. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.dark.json +613 -0
  171. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.json +968 -0
  172. package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.resolver.json +20 -0
  173. package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.tokens.json +956 -0
  174. package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.resolver.json +20 -0
  175. package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.tokens.json +960 -0
  176. package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.resolver.json +20 -0
  177. package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.tokens.json +958 -0
  178. package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.resolver.json +20 -0
  179. package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.tokens.json +980 -0
  180. package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.resolver.json +20 -0
  181. package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.tokens.json +964 -0
  182. package/data/harnesses/code/skills/design/data/design-tokens/slack/design.resolver.json +20 -0
  183. package/data/harnesses/code/skills/design/data/design-tokens/slack/design.tokens.json +957 -0
  184. package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.resolver.json +20 -0
  185. package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.tokens.json +979 -0
  186. package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.resolver.json +20 -0
  187. package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.tokens.json +974 -0
  188. package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.resolver.json +20 -0
  189. package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.tokens.json +973 -0
  190. package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.resolver.json +20 -0
  191. package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.tokens.json +979 -0
  192. package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.resolver.json +20 -0
  193. package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.tokens.json +966 -0
  194. package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.resolver.json +20 -0
  195. package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.tokens.json +961 -0
  196. package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.resolver.json +20 -0
  197. package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.tokens.json +977 -0
  198. package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.resolver.json +20 -0
  199. package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.tokens.json +991 -0
  200. package/data/harnesses/code/skills/design/data/design-tokens/uber/design.resolver.json +20 -0
  201. package/data/harnesses/code/skills/design/data/design-tokens/uber/design.tokens.json +966 -0
  202. package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.resolver.json +20 -0
  203. package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.tokens.json +988 -0
  204. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.resolver.json +23 -0
  205. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.dark.json +602 -0
  206. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.json +974 -0
  207. package/data/harnesses/code/skills/design/data/design-tokens/warp/design.resolver.json +20 -0
  208. package/data/harnesses/code/skills/design/data/design-tokens/warp/design.tokens.json +979 -0
  209. package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.resolver.json +20 -0
  210. package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.tokens.json +960 -0
  211. package/data/harnesses/code/skills/design/data/design-tokens/wired/design.resolver.json +20 -0
  212. package/data/harnesses/code/skills/design/data/design-tokens/wired/design.tokens.json +968 -0
  213. package/data/harnesses/code/skills/design/data/design-tokens/wise/design.resolver.json +20 -0
  214. package/data/harnesses/code/skills/design/data/design-tokens/wise/design.tokens.json +965 -0
  215. package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.resolver.json +20 -0
  216. package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.tokens.json +988 -0
  217. package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.resolver.json +20 -0
  218. package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.tokens.json +978 -0
  219. package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +1 -1
  220. package/data/harnesses/code/skills/design/references/create.md +14 -9
  221. package/data/harnesses/code/skills/design/references/diagnosis.md +1 -1
  222. package/data/harnesses/code/skills/design/references/hard-rules.md +4 -4
  223. package/data/harnesses/code/skills/design/references/improve.md +11 -9
  224. package/data/harnesses/code/skills/design/references/introspect.md +1 -1
  225. package/data/harnesses/code/skills/design/references/personality.md +337 -413
  226. package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +1032 -0
  227. package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +596 -296
  228. package/data/harnesses/code/skills/design/scripts/{detector.mjs → lib/checks.mjs} +145 -140
  229. package/data/harnesses/code/skills/design/scripts/lib/golden.mjs +26 -0
  230. package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +105 -37
  231. package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +60 -73
  232. package/data/harnesses/code/skills/design/scripts/render-rung.mjs +1 -1
  233. package/data/harnesses/code/skills/design/scripts/scaffold.mjs +1 -1
  234. package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +205 -37
  235. package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +68 -0
  236. package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +314 -0
  237. package/data/harnesses/code/skills/documentation/SKILL.md +61 -17
  238. package/data/harnesses/code/skills/documentation/references/examples.md +41 -0
  239. package/data/harnesses/code/skills/documentation/references/style-guide.md +14 -9
  240. package/data/harnesses/code/skills/documentation/references/templates.md +305 -51
  241. package/data/harnesses/etna/CLAUDE.md +1 -1
  242. package/data/harnesses/etna/agents/brainstorm.md +1 -1
  243. package/data/harnesses/etna/agents/maintenance.md +2 -2
  244. package/data/harnesses/etna/harness.json +2 -2
  245. package/data/harnesses/etna/rules/general.md +2 -40
  246. package/data/harnesses/etna/rules/nomenclature.md +1 -8
  247. package/data/harnesses/etna/skills/brainstorm/README.md +1 -1
  248. package/data/harnesses/etna/skills/brainstorm/SKILL.md +1 -1
  249. package/data/harnesses/etna/skills/brainstorm/exit-artifacts.md +4 -4
  250. package/data/harnesses/etna/skills/brainstorm/templates/source-of-truth.md +3 -3
  251. package/data/harnesses/etna/skills/documentation/SKILL.md +61 -17
  252. package/data/harnesses/etna/skills/documentation/references/examples.md +41 -0
  253. package/data/harnesses/etna/skills/documentation/references/style-guide.md +14 -9
  254. package/data/harnesses/etna/skills/documentation/references/templates.md +305 -51
  255. package/data/harnesses/mobile/CLAUDE.md +1 -1
  256. package/data/harnesses/mobile/agents/brainstorm.md +1 -1
  257. package/data/harnesses/mobile/agents/maintenance.md +2 -2
  258. package/data/harnesses/mobile/harness.json +2 -2
  259. package/data/harnesses/mobile/rules/database.md +38 -0
  260. package/data/harnesses/mobile/rules/general.md +2 -40
  261. package/data/harnesses/mobile/rules/nomenclature.md +1 -8
  262. package/data/harnesses/mobile/rules/nustack.md +150 -0
  263. package/data/harnesses/mobile/skills/brainstorm/README.md +1 -1
  264. package/data/harnesses/mobile/skills/brainstorm/SKILL.md +1 -1
  265. package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +4 -4
  266. package/data/harnesses/mobile/skills/brainstorm/templates/source-of-truth.md +3 -3
  267. package/data/harnesses/mobile/skills/documentation/SKILL.md +61 -17
  268. package/data/harnesses/mobile/skills/documentation/references/examples.md +41 -0
  269. package/data/harnesses/mobile/skills/documentation/references/style-guide.md +14 -9
  270. package/data/harnesses/mobile/skills/documentation/references/templates.md +305 -51
  271. package/package.json +1 -1
  272. package/data/harnesses/code/skills/design/scripts/hook-config.json +0 -11
@@ -1,31 +1,44 @@
1
- #!/usr/bin/env node
2
- // skills/design/scripts/detector.mjs — per-edit source detector (beam 2, D19): every detector=source catalog rule as pure regex/token analysis over .tsx/.ts/.css/.html text, plus the lever-manifest drift check (cod-02, D10). Exit 1 on violations; --json emits [{rule,file,line,excerpt,fix}].
3
- import { readFileSync } from 'node:fs';
4
- import process from 'node:process';
1
+ // skills/design/scripts/lib/checks.mjs — the machine-check engine (beam 2, D19): every detector=source catalog rule as pure regex/token analysis over .tsx/.ts/.css/.html text, plus the lever-manifest drift check (cod-02, D10) whose vocabulary is read from the project's design-tokens artifact (design.tokens.json). Pure library — no argv, no CLI banner, no process.exit; fired at structural boundaries by phase-gate.mjs, preship-sweep.mjs, and self-integrity.mjs. Findings are [{rule, file, line, excerpt, fix}].
2
+ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
5
4
 
6
5
  // ---------------------------------------------------------------------------
7
- // CLI parse
6
+ // File-type routing
8
7
  // ---------------------------------------------------------------------------
9
8
 
10
- // Split argv into the file list, an optional --manifest path, and the --json flag.
11
- function parseArgs(argv) {
12
- const files = [];
13
- let manifest = null;
14
- let json = false;
15
- for (let i = 0; i < argv.length; i++) {
16
- const a = argv[i];
17
- if (a === '--json') json = true;
18
- else if (a === '--manifest') manifest = argv[++i];
19
- else if (a.startsWith('--manifest=')) manifest = a.slice('--manifest='.length);
20
- else if (a === '--help' || a === '-h') { printUsage(); process.exit(0); }
21
- else files.push(a);
22
- }
23
- return { files, manifest, json };
9
+ export const CHECKABLE_EXTS = ['.tsx', '.ts', '.css', '.html'];
10
+
11
+ // Detect the file's extension bucket, or null when it is not a checkable type.
12
+ export function extOf(path) {
13
+ const m = path.toLowerCase().match(/\.(tsx|ts|css|html)$/);
14
+ return m ? m[1] : null;
24
15
  }
25
16
 
26
- // Print the one-line usage banner.
27
- function printUsage() {
28
- process.stdout.write('usage: detector.mjs <file...> [--manifest design.md] [--json]\n');
17
+ // Recursively collect checkable UI files under a dir, skipping node_modules and dotfiles.
18
+ export function walkUiFiles(dir, acc = []) {
19
+ if (!existsSync(dir)) return acc;
20
+ let entries;
21
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return acc; }
22
+ for (const ent of entries) {
23
+ if (ent.name === 'node_modules' || ent.name.startsWith('.')) continue;
24
+ const full = join(dir, ent.name);
25
+ if (ent.isDirectory()) walkUiFiles(full, acc);
26
+ else if (CHECKABLE_EXTS.some((e) => ent.name.endsWith(e))) acc.push(full);
27
+ }
28
+ return acc;
29
+ }
30
+
31
+ // Read the passed files into check-ready docs, skipping unreadable and uncheckable ones.
32
+ export function loadDocs(files) {
33
+ const docs = [];
34
+ for (const file of files) {
35
+ const ext = extOf(file);
36
+ if (!ext) continue;
37
+ let text;
38
+ try { text = readFileSync(file, 'utf8'); } catch { continue; }
39
+ docs.push({ file, ext, text, lines: text.split('\n') });
40
+ }
41
+ return docs;
29
42
  }
30
43
 
31
44
  // ---------------------------------------------------------------------------
@@ -134,7 +147,7 @@ function toPx(numStr, unit) {
134
147
  // ---------------------------------------------------------------------------
135
148
 
136
149
  // Build a findings collector that de-dupes on (rule, file, line, excerpt).
137
- function makeSink() {
150
+ export function makeSink() {
138
151
  const seen = new Set();
139
152
  const list = [];
140
153
  return {
@@ -154,7 +167,7 @@ function makeSink() {
154
167
  // ---------------------------------------------------------------------------
155
168
 
156
169
  // Run every single-line source check against one line and record any violations.
157
- function checkLine(add, file, ext, lineNo, line, fileText) {
170
+ export function checkLine(add, file, ext, lineNo, line, fileText) {
158
171
  const isJsx = ext === 'tsx' || ext === 'ts' || ext === 'html';
159
172
  const isCss = ext === 'css';
160
173
 
@@ -283,7 +296,7 @@ function checkLine(add, file, ext, lineNo, line, fileText) {
283
296
  // spc-06: corner radius off the fixed scale.
284
297
  for (const m of line.matchAll(/(?:border-radius\s*:\s*|rounded-\[)(\d*\.?\d+)(px|rem)?/g)) {
285
298
  const px = toPx(m[1], m[2] || 'px');
286
- if (px != null && !RADIUS_SCALE.has(px)) add('spc-06', file, lineNo, m[0], `Corners land on the fixed radius scale (0/2/4/6/8/12/full); ${px}px is off-scale.`);
299
+ if (px != null && !RADIUS_SCALE.has(px)) add('spc-06', file, lineNo, m[0], `Corners land on the fixed radius scale (0/2/4/6/8/12/16/24/full); ${px}px is off-scale.`);
287
300
  }
288
301
  // spc-07: a button taller than the 28px dense-row cap sitting inside a table row.
289
302
  if (/<t[dh]\b/i.test(line) && /<[Bb]utton\b/.test(line)) {
@@ -440,7 +453,7 @@ function checkLine(add, file, ext, lineNo, line, fileText) {
440
453
  const RE_EMOJI = /[\u{1F300}-\u{1FAFF}\u{1F1E6}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE0F}]/u;
441
454
 
442
455
  // Blank out // line comments and /* */ (incl. JSX {/* */}) block comments, preserving line count, so copy checks skip commentary.
443
- function cleanComments(lines) {
456
+ export function cleanComments(lines) {
444
457
  const out = [];
445
458
  let inBlock = false;
446
459
  for (let raw of lines) {
@@ -464,7 +477,7 @@ function cleanComments(lines) {
464
477
  }
465
478
 
466
479
  // Scan comment-stripped copy for em-dashes (typ-15) and three-period ellipses (typ-14).
467
- function checkCopy(add, file, cleaned) {
480
+ export function checkCopy(add, file, cleaned) {
468
481
  cleaned.forEach((line, i) => {
469
482
  if (line.includes('—')) add('typ-15', file, i + 1, line.trim(), 'Zero em-dashes in UI copy; recast the sentence or use a colon/en-dash.');
470
483
  const el = line.match(/\.\.\.(?![\w$.([{])/);
@@ -477,7 +490,7 @@ function checkCopy(add, file, cleaned) {
477
490
  // ---------------------------------------------------------------------------
478
491
 
479
492
  // Run the file-scoped source checks over one file's full text.
480
- function checkFile(add, file, ext, text, lines) {
493
+ export function checkFile(add, file, ext, text, lines) {
481
494
  const isJsx = ext === 'tsx' || ext === 'ts';
482
495
 
483
496
  // dep-02: more than one <h1> in a view.
@@ -597,7 +610,7 @@ const REQUIRED_TOKENS = [
597
610
  // ---------------------------------------------------------------------------
598
611
 
599
612
  // Run the checks that need the whole passed-in file set (token-set counts, distinct sizes).
600
- function checkCrossFile(add, docs) {
613
+ export function checkCrossFile(add, docs) {
601
614
  // typ-06: more than two font families declared across the passed files.
602
615
  const families = new Set();
603
616
  const iconLibs = new Set();
@@ -653,7 +666,7 @@ function checkCrossFile(add, docs) {
653
666
  }
654
667
  }
655
668
  }
656
- // spc-02 is checked with the same near-duplicate logic over explicit spacing (below).
669
+ // spc-02 is checked with the same near-duplicate logic over explicit spacing (checkSpacingScale).
657
670
 
658
671
  // col-08: a color family exceeding the 10-shade ceiling.
659
672
  for (const [hue, set] of hexByHue) {
@@ -675,7 +688,7 @@ function checkCrossFile(add, docs) {
675
688
  }
676
689
 
677
690
  // Run spc-02 over explicit spacing values collected across files.
678
- function checkSpacingScale(add, docs) {
691
+ export function checkSpacingScale(add, docs) {
679
692
  const spacings = new Map();
680
693
  for (const d of docs) {
681
694
  d.lines.forEach((line, i) => {
@@ -719,61 +732,87 @@ function hexHueBucket(hex) {
719
732
  }
720
733
 
721
734
  // ---------------------------------------------------------------------------
722
- // cod-02 — lever-manifest drift check (D10)
735
+ // cod-02 — lever-manifest drift check (D10), vocabulary from the design-tokens artifact
723
736
  // ---------------------------------------------------------------------------
724
737
 
725
- // Extract the fenced ```yaml lever-manifest block body from a design.md's text.
726
- function extractLeverManifest(text) {
727
- const m = text.match(/```yaml\s+lever-manifest\s*\n([\s\S]*?)\n```/);
728
- return m ? m[1] : null;
738
+ // Resolve a manifest argument (a design.tokens.json path or a directory holding one) to the tokens file, or null.
739
+ function resolveTokensFile(path) {
740
+ if (!path || !existsSync(path)) return null;
741
+ let st;
742
+ try { st = statSync(path); } catch { return null; }
743
+ if (st.isDirectory()) {
744
+ const candidate = join(path, 'design.tokens.json');
745
+ return existsSync(candidate) ? candidate : null;
746
+ }
747
+ return path;
729
748
  }
730
749
 
731
- // Tolerantly pull the allowed vocabulary (colors, scales, fonts) out of a lever-manifest body.
732
- function parseManifestVocab(body) {
733
- const stripComment = (s) => s.replace(/\s+#.*$/, '');
750
+ // Follow {path.to.token} alias strings within the artifact to the target token (hop-bounded against cycles).
751
+ function resolveAlias(root, token) {
752
+ let cur = token;
753
+ for (let hops = 0; hops < 8 && cur && typeof cur.$value === 'string' && /^\{[^}]+\}$/.test(cur.$value.trim()); hops++) {
754
+ let node = root;
755
+ for (const seg of cur.$value.trim().slice(1, -1).split('.')) node = node && typeof node === 'object' ? node[seg] : undefined;
756
+ if (!node || typeof node !== 'object') return cur;
757
+ cur = node;
758
+ }
759
+ return cur;
760
+ }
761
+
762
+ // Pull a token's preserved source string from $extensions["com.nurix.design"].css, or null.
763
+ function preservedCss(token) {
764
+ const css = token && token.$extensions && token.$extensions['com.nurix.design'] && token.$extensions['com.nurix.design'].css;
765
+ return typeof css === 'string' ? css : null;
766
+ }
767
+
768
+ // Load the drift-check vocabulary from a design-tokens artifact (a design.tokens.json path, or its directory):
769
+ // scales come from the payload's lever-manifest; colors, font stacks, and icon stroke are read from the tokens
770
+ // themselves (data/design-tokens/SCHEMA.md — token-valued allowlists are never restated in the manifest).
771
+ // Each color enters in both spellings — the preserved .css string and the typed value's hex — so either passes
772
+ // drift. Returns null when no parseable artifact is found; never runs the coverage gate.
773
+ export function loadManifestVocab(manifestPath) {
774
+ const file = resolveTokensFile(manifestPath);
775
+ if (!file) return null;
776
+ let root;
777
+ try { root = JSON.parse(readFileSync(file, 'utf8')); } catch { return null; }
778
+ if (!root || typeof root !== 'object') return null;
779
+ const payload = (root.$extensions && root.$extensions['com.nurix.design']) || {};
780
+ const manifest = payload['lever-manifest'] || {};
781
+
782
+ const numSet = (arr) => (Array.isArray(arr) ? new Set(arr.map(Number).filter((n) => !Number.isNaN(n))) : null);
783
+ const spacing = numSet(manifest['spacing-scale']);
784
+ const radius = numSet(manifest['radius-scale']);
785
+ const durations = numSet(manifest.motion && manifest.motion.durations);
786
+
734
787
  const colors = new Set();
735
- const fonts = new Set();
736
- // color-tokens region: from `color-tokens:` up to the next top-level (0-indent) key.
737
- const ctStart = body.search(/^color-tokens\s*:/m);
738
- if (ctStart >= 0) {
739
- const rest = body.slice(ctStart).split('\n');
740
- let region = [rest[0]];
741
- for (let i = 1; i < rest.length; i++) {
742
- if (/^\S/.test(rest[i]) && !/^\s/.test(rest[i])) break; // next top-level key
743
- region.push(rest[i]);
744
- }
745
- for (const line of region) {
746
- for (const c of stripComment(line).matchAll(RE_COLOR_LITERAL)) colors.add(normColor(c[0]));
747
- for (const c of stripComment(line).matchAll(/"([^"]+)"|'([^']+)'/g)) {
748
- const v = (c[1] || c[2]).trim();
749
- if (RE_COLOR_LITERAL.test(v)) { RE_COLOR_LITERAL.lastIndex = 0; colors.add(normColor(v)); }
750
- RE_COLOR_LITERAL.lastIndex = 0;
751
- }
752
- }
788
+ for (const token of Object.values(root.color || {})) {
789
+ if (!token || typeof token !== 'object') continue;
790
+ const resolved = resolveAlias(root, token);
791
+ const css = preservedCss(token) || preservedCss(resolved);
792
+ if (css) colors.add(normColor(css));
793
+ const value = resolved && resolved.$value;
794
+ if (value && typeof value === 'object' && typeof value.hex === 'string') colors.add(normColor(value.hex));
753
795
  }
754
- // numeric scales
755
- const numList = (key) => {
756
- const mm = body.match(new RegExp(`^${key}\\s*:\\s*\\[([^\\]]*)\\]`, 'm'));
757
- if (!mm) return null;
758
- return new Set(mm[1].split(',').map((s) => parseFloat(s)).filter((n) => !Number.isNaN(n)));
796
+
797
+ const fonts = new Set();
798
+ // Add every family name in a fontFamily token's own stack (alias-valued tokens contribute nothing new).
799
+ const addStack = (token) => {
800
+ if (!token || !Array.isArray(token.$value)) return;
801
+ for (const fam of token.$value) if (typeof fam === 'string' && fam.trim()) fonts.add(fam.trim().toLowerCase());
759
802
  };
760
- const spacing = numList('spacing-scale');
761
- const radius = numList('radius-scale');
762
- let durations = null;
763
- const dm = body.match(/durations\s*:\s*\[([^\]]*)\]/);
764
- if (dm) durations = new Set(dm[1].split(',').map((s) => parseFloat(s)).filter((n) => !Number.isNaN(n)));
765
- // fonts (inline object)
766
- const fm = body.match(/fonts\s*:\s*\{([^}]*)\}/);
767
- if (fm) {
768
- for (const p of fm[1].matchAll(/(?:sans|mono)\s*:\s*"([^"]+)"/g)) {
769
- fonts.add(p[1].split(',')[0].replace(/["']/g, '').trim().toLowerCase());
770
- }
771
- }
772
- return { colors, spacing, radius, durations, fonts };
803
+ const primitive = root.primitive || {};
804
+ addStack(primitive['font-sans']);
805
+ addStack(primitive['font-mono']);
806
+ for (const token of Object.values((root.typography && root.typography.family) || {})) addStack(token);
807
+
808
+ const strokeToken = primitive['icon-stroke'] ? resolveAlias(root, primitive['icon-stroke']) : null;
809
+ const iconStroke = strokeToken && typeof strokeToken.$value === 'number' ? strokeToken.$value : null;
810
+
811
+ return { colors, spacing, radius, durations, fonts, iconStroke };
773
812
  }
774
813
 
775
814
  // Flag any color/spacing/radius/duration/font literal in the diff files outside the manifest vocabulary (cod-02).
776
- function checkDrift(add, docs, vocab) {
815
+ export function checkDrift(add, docs, vocab) {
777
816
  for (const d of docs) {
778
817
  if (d.ext === 'css' && vocab.colors.size) {
779
818
  // token-definition files legitimately hold the manifest's own literals; only flag component-side drift
@@ -783,7 +822,7 @@ function checkDrift(add, docs, vocab) {
783
822
  // colors
784
823
  if (vocab.colors.size) {
785
824
  for (const m of line.matchAll(RE_COLOR_LITERAL)) {
786
- if (!vocab.colors.has(normColor(m[0]))) add('cod-02', d.file, i + 1, m[0], 'Value drifts from the lever manifest; use a color already in color-tokens, not a fresh literal.');
825
+ if (!vocab.colors.has(normColor(m[0]))) add('cod-02', d.file, i + 1, m[0], "Value drifts from the lever manifest; use a color already in the artifact's color tokens, not a fresh literal.");
787
826
  }
788
827
  }
789
828
  // spacing (explicit only)
@@ -819,59 +858,12 @@ function checkDrift(add, docs, vocab) {
819
858
  }
820
859
 
821
860
  // ---------------------------------------------------------------------------
822
- // Output
861
+ // Orchestration + output formatting
823
862
  // ---------------------------------------------------------------------------
824
863
 
825
- // Print findings as the fix-or-classify directive (human) or a JSON array.
826
- function report(findings, json) {
827
- if (json) {
828
- process.stdout.write(JSON.stringify(findings, null, 2) + '\n');
829
- return;
830
- }
831
- if (findings.length === 0) {
832
- process.stdout.write('detector: clean — no source violations.\n');
833
- return;
834
- }
835
- const useColor = process.stdout.isTTY;
836
- const bold = (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s);
837
- const red = (s) => (useColor ? `\x1b[31m${s}\x1b[0m` : s);
838
- const dim = (s) => (useColor ? `\x1b[2m${s}\x1b[0m` : s);
839
- process.stdout.write(bold(red(`detector: ${findings.length} source violation${findings.length === 1 ? '' : 's'}\n\n`)));
840
- for (const f of findings) {
841
- process.stdout.write(`${bold(red('[' + f.rule + ']'))} ${f.file}:${f.line}\n`);
842
- process.stdout.write(` ${dim('offending:')} ${f.excerpt}\n`);
843
- process.stdout.write(` ${dim('fix:')} ${f.fix}\n\n`);
844
- }
845
- process.stdout.write(bold('Fix each now, or classify why the rule does not apply and record that classification\n'));
846
- process.stdout.write(bold('in the decay ledger (docs/memory/design-decay.md) — never ignore silently.\n'));
847
- }
848
-
849
- // ---------------------------------------------------------------------------
850
- // Main
851
- // ---------------------------------------------------------------------------
852
-
853
- // Detect the file's extension bucket, or null when it is not a checkable type.
854
- function extOf(path) {
855
- const m = path.toLowerCase().match(/\.(tsx|ts|css|html)$/);
856
- return m ? m[1] : null;
857
- }
858
-
859
- // Entry point: read the passed files, run every source check, emit findings, exit 1 on any.
860
- function main() {
861
- const { files, manifest, json } = parseArgs(process.argv.slice(2));
862
- if (files.length === 0) { printUsage(); process.exit(2); }
863
-
864
+ // Run the full engine (per-line, per-file, copy, cross-file, spacing, drift) over loaded docs; returns sorted findings.
865
+ export function checkDocs(docs, { vocab = null } = {}) {
864
866
  const sink = makeSink();
865
- const docs = [];
866
- for (const file of files) {
867
- const ext = extOf(file);
868
- if (!ext) continue;
869
- let text;
870
- try { text = readFileSync(file, 'utf8'); } catch { continue; }
871
- const lines = text.split('\n');
872
- docs.push({ file, ext, text, lines });
873
- }
874
-
875
867
  for (const d of docs) {
876
868
  d.lines.forEach((line, i) => checkLine(sink.add, d.file, d.ext, i + 1, line, d.text));
877
869
  checkFile(sink.add, d.file, d.ext, d.text, d.lines);
@@ -879,20 +871,33 @@ function main() {
879
871
  }
880
872
  checkCrossFile(sink.add, docs);
881
873
  checkSpacingScale(sink.add, docs);
882
-
883
- if (manifest) {
884
- let mtext;
885
- try { mtext = readFileSync(manifest, 'utf8'); } catch { mtext = null; }
886
- if (mtext) {
887
- const body = extractLeverManifest(mtext);
888
- if (body) checkDrift(sink.add, docs, parseManifestVocab(body));
889
- }
890
- }
891
-
874
+ if (vocab) checkDrift(sink.add, docs, vocab);
892
875
  // Stable sort: by file, then line, then rule id.
893
876
  sink.list.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.rule.localeCompare(b.rule));
894
- report(sink.list, json);
895
- process.exit(sink.list.length > 0 ? 1 : 0);
877
+ return sink.list;
878
+ }
879
+
880
+ // Convenience entry: read the passed file paths (and the optional design-tokens artifact) and run the full engine.
881
+ export function runChecks(files, { manifestPath = null, vocab = null } = {}) {
882
+ const loaded = vocab || (manifestPath ? loadManifestVocab(manifestPath) : null);
883
+ return checkDocs(loadDocs(files), { vocab: loaded });
896
884
  }
897
885
 
898
- main();
886
+ // Render findings as the fix-or-classify block (the caller prints it and decides the exit code).
887
+ export function formatFindings(findings, { color = false, label = 'machine checks' } = {}) {
888
+ const bold = (s) => (color ? `\x1b[1m${s}\x1b[0m` : s);
889
+ const red = (s) => (color ? `\x1b[31m${s}\x1b[0m` : s);
890
+ const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
891
+ const out = [];
892
+ out.push(bold(red(`${label}: ${findings.length} source violation${findings.length === 1 ? '' : 's'}`)));
893
+ out.push('');
894
+ for (const f of findings) {
895
+ out.push(`${bold(red('[' + f.rule + ']'))} ${f.file}:${f.line}`);
896
+ out.push(` ${dim('offending:')} ${f.excerpt}`);
897
+ out.push(` ${dim('fix:')} ${f.fix}`);
898
+ out.push('');
899
+ }
900
+ out.push(bold('Fix each now, or classify why the rule does not apply and record that classification'));
901
+ out.push(bold('in the decay ledger (docs/memory/design-decay.md) — never ignore silently.'));
902
+ return out.join('\n');
903
+ }
@@ -0,0 +1,26 @@
1
+ // skills/design/scripts/lib/golden.mjs — golden-CSS comparison primitives: comment stripping + sha256 hashing for the byte-match regression oracle.
2
+
3
+ import { createHash } from "node:crypto";
4
+
5
+ // Remove every /* ... */ comment block (CSS has no other comment syntax; an unterminated comment strips to EOF) — no whitespace normalization, byte-exact otherwise.
6
+ export function stripCssComments(css) {
7
+ let out = "";
8
+ let i = 0;
9
+ while (i < css.length) {
10
+ const open = css.indexOf("/*", i);
11
+ if (open === -1) {
12
+ out += css.slice(i);
13
+ break;
14
+ }
15
+ out += css.slice(i, open);
16
+ const close = css.indexOf("*/", open + 2);
17
+ if (close === -1) break; // unterminated comment: strip to EOF
18
+ i = close + 2;
19
+ }
20
+ return out;
21
+ }
22
+
23
+ // sha256 hex digest of stripCssComments(css) — the golden fingerprint of one emitted CSS file.
24
+ export function goldenHash(css) {
25
+ return createHash("sha256").update(stripCssComments(css), "utf8").digest("hex");
26
+ }
@@ -1,8 +1,9 @@
1
- // skills/design/scripts/phase-gate.mjs — create-phase precondition gates (D21/D23): validate the named phase's exit preconditions, exit non-zero printing the exact fix, one green line on pass.
1
+ // skills/design/scripts/phase-gate.mjs — create-phase precondition gates (D21/D23): validate the named phase's exit preconditions, printing the exact fix on failure; every gate also fires the machine-check engine (lib/checks.mjs — per-file catalog + manifest drift) over the project's whole UI tree and exits non-zero on either failure.
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
- import { join, dirname, resolve } from 'node:path';
3
+ import { join, dirname, resolve, relative } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { spawnSync } from 'node:child_process';
6
+ import { runChecks, formatFindings, walkUiFiles } from './lib/checks.mjs';
6
7
 
7
8
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
8
9
  const PHASES = ['intake', 'structure', 'scaffold', 'tokens', 'fixtures', 'surfaces'];
@@ -20,13 +21,15 @@ function readText(path) {
20
21
  try { return readFileSync(path, 'utf8'); } catch { return ''; }
21
22
  }
22
23
 
23
- // Report a passing gate as one green line and exit 0.
24
+ // Sentinel a gate throws (via fail) to abort its remaining precondition checks without exiting the process.
25
+ class GateFailure extends Error {}
26
+
27
+ // Report a passing gate as one green line (the process exit code is decided in main, after the source sweep).
24
28
  function pass(phase, msg) {
25
29
  console.log(`${green(`PASS phase-gate[${phase}]`)} ${msg}`);
26
- process.exit(0);
27
30
  }
28
31
 
29
- // Report a failing gate with the problem and the exact fix, then exit 1.
32
+ // Report a failing gate with the problem and the exact fix, then abort the gate.
30
33
  function fail(phase, problem, fixLines) {
31
34
  console.error(`${red(`FAIL phase-gate[${phase}]`)} ${problem}`);
32
35
  console.error('');
@@ -35,7 +38,7 @@ function fail(phase, problem, fixLines) {
35
38
  if (line == null || line === '') continue;
36
39
  for (const sub of String(line).split('\n')) console.error(` ${sub}`);
37
40
  }
38
- process.exit(1);
41
+ throw new GateFailure(problem);
39
42
  }
40
43
 
41
44
  // Lowercase-kebab a display name for route-dir / fixture-module matching.
@@ -57,17 +60,27 @@ function lastStaticSegment(route) {
57
60
  return parts.length ? kebab(parts[parts.length - 1]) : null;
58
61
  }
59
62
 
60
- // Parse leading YAML frontmatter into a flat key->value map, or null when absent.
61
- function parseFrontmatter(text) {
62
- const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
63
- if (!m) return null;
64
- const out = {};
65
- for (const line of m[1].split(/\r?\n/)) {
66
- const idx = line.indexOf(':');
67
- if (idx < 0) continue;
68
- out[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
63
+ // Parse a JSON file into an object, or null when it is absent or malformed.
64
+ function readJson(path) {
65
+ try {
66
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
67
+ return parsed && typeof parsed === 'object' ? parsed : null;
68
+ } catch { return null; }
69
+ }
70
+
71
+ // Collect every token file the resolver references (set values plus all modifier-context overlays).
72
+ function resolverNamedFiles(resolver) {
73
+ const named = [];
74
+ for (const set of Array.isArray(resolver.sets) ? resolver.sets : []) {
75
+ if (set && Array.isArray(set.values)) named.push(...set.values.filter((f) => typeof f === 'string'));
76
+ }
77
+ for (const mod of Array.isArray(resolver.modifiers) ? resolver.modifiers : []) {
78
+ const contexts = mod && mod.contexts && typeof mod.contexts === 'object' ? mod.contexts : {};
79
+ for (const files of Object.values(contexts)) {
80
+ if (Array.isArray(files)) named.push(...files.filter((f) => typeof f === 'string'));
81
+ }
69
82
  }
70
- return out;
83
+ return named;
71
84
  }
72
85
 
73
86
  // Split a `[a, b, c]` inline list into trimmed members.
@@ -217,21 +230,49 @@ function checkImportOrder(rawCss) {
217
230
  return null;
218
231
  }
219
232
 
220
- // intake gate: a schema-valid design.md with a legal provenance sits at project root.
233
+ // intake gate: a structurally sound design-tokens artifact sits at project root — both files parse, the dark
234
+ // file agrees with the resolver, and the payload carries a legal schema/provenance/product. Light checks only;
235
+ // deep validation (coverage, aliases, parse-back) is derive-personality.mjs --check at the tokens gate.
221
236
  function gateIntake(project) {
222
- const p = join(project, 'design.md');
223
- if (!existsSync(p)) {
224
- fail('intake', `No design.md at project root (${p}).`, [
225
- `Create ${p} against skills/design/data/design-md/SCHEMA.md.`,
226
- 'Frontmatter must set schema: design-md/v1 and provenance: project|extracted|library|default.',
227
- 'Resolve the look design.md-first: the project doc wins, else a confirmed library entry, else the Nurix default.',
237
+ const tokensPath = join(project, 'design.tokens.json');
238
+ const resolverPath = join(project, 'design.resolver.json');
239
+ const absent = [tokensPath, resolverPath].filter((p) => !existsSync(p)).map((p) => relative(project, p));
240
+ if (absent.length) {
241
+ fail('intake', `Missing token-artifact file(s) at project root: ${absent.join(', ')}.`, [
242
+ 'Write the design-tokens artifact against skills/design/data/design-tokens/SCHEMA.md:',
243
+ 'design.tokens.json (base/light set + the $extensions["com.nurix.design"] payload) and design.resolver.json,',
244
+ 'plus design.tokens.dark.json only when the look has dark values.',
245
+ 'Resolve the look artifact-first: the project artifact wins, else a confirmed library entry, else the Nurix default.',
246
+ ]);
247
+ }
248
+ const tokens = readJson(tokensPath);
249
+ if (!tokens) fail('intake', 'design.tokens.json does not parse as JSON.', ['Fix the JSON before anything deeper runs (data/design-tokens/SCHEMA.md).']);
250
+ const resolver = readJson(resolverPath);
251
+ if (!resolver) fail('intake', 'design.resolver.json does not parse as JSON.', ['Fix the JSON (data/design-tokens/SCHEMA.md — the resolver section).']);
252
+ const namesDark = resolverNamedFiles(resolver).includes('design.tokens.dark.json');
253
+ const hasDark = existsSync(join(project, 'design.tokens.dark.json'));
254
+ if (namesDark && !hasDark) {
255
+ fail('intake', 'design.resolver.json names design.tokens.dark.json but the file is absent.', [
256
+ 'Ship the dark modifier set beside the base file, or drop the dark context from the resolver —',
257
+ 'the dark file exists exactly when the resolver names it (data/design-tokens/SCHEMA.md).',
228
258
  ]);
229
259
  }
230
- const fm = parseFrontmatter(readText(p));
231
- if (!fm) fail('intake', `${p} has no YAML frontmatter.`, ['Add a --- delimited frontmatter block with schema, product, provenance, sourced.']);
232
- if (fm.schema !== 'design-md/v1') fail('intake', `design.md frontmatter schema is "${fm.schema || '(missing)'}", expected design-md/v1.`, ['Set schema: design-md/v1 in the frontmatter.']);
233
- if (!PROVENANCE.has(fm.provenance)) fail('intake', `design.md provenance is "${fm.provenance || '(missing)'}"; must be one of project|extracted|library|default.`, ['Set provenance to one of: project | extracted | library | default (SCHEMA.md).']);
234
- pass('intake', `design.md present with schema design-md/v1 and provenance "${fm.provenance}".`);
260
+ if (!namesDark && hasDark) {
261
+ fail('intake', 'design.tokens.dark.json is present but the resolver names no dark context.', [
262
+ 'Add "dark": ["design.tokens.dark.json"] to the theme modifier contexts, or remove the dark file —',
263
+ 'the dark file exists exactly when the resolver names it (data/design-tokens/SCHEMA.md).',
264
+ ]);
265
+ }
266
+ const payload = tokens.$extensions && tokens.$extensions['com.nurix.design'];
267
+ if (!payload || typeof payload !== 'object') {
268
+ fail('intake', 'design.tokens.json carries no root $extensions["com.nurix.design"] payload.', [
269
+ 'Add the file-level payload (schema, dtcg, product, provenance, sourced, lever-manifest, …) per data/design-tokens/SCHEMA.md.',
270
+ ]);
271
+ }
272
+ if (payload.schema !== 'design-tokens/v1') fail('intake', `payload schema is "${payload.schema || '(missing)'}", expected design-tokens/v1.`, ['Set schema: "design-tokens/v1" in the payload (data/design-tokens/SCHEMA.md).']);
273
+ if (!PROVENANCE.has(payload.provenance)) fail('intake', `payload provenance is "${payload.provenance || '(missing)'}"; must be one of project|extracted|library|default.`, ['Set provenance to one of: project | extracted | library | default (data/design-tokens/SCHEMA.md).']);
274
+ if (typeof payload.product !== 'string' || !payload.product.trim()) fail('intake', 'payload product is missing or empty.', ['Set product to the product name; slugified it becomes the data-style value (data/design-tokens/SCHEMA.md).']);
275
+ pass('intake', `design.tokens.json + design.resolver.json present and parsing; payload schema design-tokens/v1, provenance "${payload.provenance}"${namesDark ? ', dark set named and present' : ', light-only'}.`);
235
276
  }
236
277
 
237
278
  // structure gate: the map parses, is populated, has a temporal block, and every surface archetype is one of the six.
@@ -290,17 +331,17 @@ function gateScaffold(project) {
290
331
  pass('scaffold', `Shell, ${blk.surfaces.length} wired route dir(s) with state-shells, and globals.css import order all in place.`);
291
332
  }
292
333
 
293
- // tokens gate: design.md clears the coverage gate (derive-personality.mjs --check) and both CSS files are emitted with the cascade order intact.
334
+ // tokens gate: the design-tokens artifact clears the coverage gate (derive-personality.mjs <project> --check) and both CSS files are emitted with the cascade order intact.
294
335
  function gateTokens(project) {
295
- const dm = join(project, 'design.md');
296
- if (!existsSync(dm)) fail('tokens', `No design.md at ${dm}.`, ['Resolve and materialize design.md first (intake phase).']);
336
+ const tokensPath = join(project, 'design.tokens.json');
337
+ if (!existsSync(tokensPath)) fail('tokens', `No design.tokens.json at ${tokensPath}.`, ['Resolve and materialize the design-tokens artifact first (intake phase; data/design-tokens/SCHEMA.md).']);
297
338
  const derive = join(SCRIPT_DIR, 'derive-personality.mjs');
298
339
  if (!existsSync(derive)) fail('tokens', `Coverage-gate sibling not found: ${derive}.`, ['derive-personality.mjs must sit beside phase-gate.mjs in the scripts/ directory.']);
299
- const res = spawnSync(process.execPath, [derive, dm, '--check'], { encoding: 'utf8' });
340
+ const res = spawnSync(process.execPath, [derive, project, '--check'], { encoding: 'utf8' });
300
341
  if (res.status !== 0) {
301
342
  const detail = `${res.stdout || ''}${res.stderr || ''}`.trim();
302
- fail('tokens', `The design.md coverage gate failed (derive-personality.mjs --check exited ${res.status == null ? '(spawn error)' : res.status}).`, [
303
- 'Assign or hint every named primitive / dial / token below, then re-run this gate:',
343
+ fail('tokens', `The token-artifact coverage gate failed (derive-personality.mjs --check exited ${res.status == null ? '(spawn error)' : res.status}).`, [
344
+ 'Assign or hint every named token path below, then re-run this gate:',
304
345
  detail,
305
346
  ]);
306
347
  }
@@ -308,7 +349,7 @@ function gateTokens(project) {
308
349
  const miss = files.filter((f) => !existsSync(join(project, f)));
309
350
  if (miss.length) {
310
351
  fail('tokens', `Coverage gate passed but emitted CSS is missing: ${miss.join(', ')}.`, [
311
- `Run derive-personality.mjs ${dm} --emit ${project} to write src/styles/personality.css and src/styles/globals.css.`,
352
+ `Run derive-personality.mjs ${project} --emit ${project} to write src/styles/personality.css and src/styles/globals.css.`,
312
353
  ]);
313
354
  }
314
355
  const orderErr = checkImportOrder(readText(join(project, 'src/styles/globals.css')));
@@ -367,7 +408,26 @@ function gateSurfaces(project) {
367
408
 
368
409
  const GATES = { intake: gateIntake, structure: gateStructure, scaffold: gateScaffold, tokens: gateTokens, fixtures: gateFixtures, surfaces: gateSurfaces };
369
410
 
370
- // Parse argv, resolve the project dir, and dispatch to the named phase gate.
411
+ // Fire the check engine over the project's whole UI tree (plus design.tokens.json manifest drift); print findings fix-or-classify style, return the violation count.
412
+ function sourceSweep(project) {
413
+ const files = walkUiFiles(join(project, 'src'));
414
+ console.log('');
415
+ if (!files.length) {
416
+ console.log('source sweep: no src/**/*.{tsx,ts,css,html} files to scan.');
417
+ return 0;
418
+ }
419
+ const manifest = join(project, 'design.tokens.json');
420
+ const findings = runChecks(files, { manifestPath: existsSync(manifest) ? manifest : null })
421
+ .map((f) => ({ ...f, file: relative(project, f.file) || f.file }));
422
+ if (!findings.length) {
423
+ console.log(green(`source sweep: clean — ${files.length} file(s), no catalog or manifest-drift violations.`));
424
+ return 0;
425
+ }
426
+ console.log(formatFindings(findings, { color: useColor, label: 'source sweep' }));
427
+ return findings.length;
428
+ }
429
+
430
+ // Parse argv, resolve the project dir, dispatch to the named phase gate, then fire the whole-tree source sweep.
371
431
  function main() {
372
432
  const args = process.argv.slice(2);
373
433
  const phase = args[0];
@@ -380,7 +440,15 @@ function main() {
380
440
  console.error(`usage: phase-gate.mjs <${PHASES.join('|')}> [--project <dir>]`);
381
441
  process.exit(2);
382
442
  }
383
- GATES[phase](project);
443
+ let gateFailed = false;
444
+ try {
445
+ GATES[phase](project);
446
+ } catch (e) {
447
+ if (!(e instanceof GateFailure)) throw e;
448
+ gateFailed = true;
449
+ }
450
+ const sweepCount = sourceSweep(project);
451
+ process.exit(gateFailed || sweepCount > 0 ? 1 : 0);
384
452
  }
385
453
 
386
454
  main();