@nurix/etna 0.18.19 → 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 (283) 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 +68 -0
  18. package/data/harnesses/code/skills/design/data/catalog/rules.csv +132 -0
  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 +106 -0
  31. package/data/harnesses/code/skills/design/data/design-md/library/airbnb.md +154 -0
  32. package/data/harnesses/code/skills/design/data/design-md/library/airtable.md +151 -0
  33. package/data/harnesses/code/skills/design/data/design-md/library/anthropic-claude.md +139 -0
  34. package/data/harnesses/code/skills/design/data/design-md/library/apple.md +139 -0
  35. package/data/harnesses/code/skills/design/data/design-md/library/binance.md +140 -0
  36. package/data/harnesses/code/skills/design/data/design-md/library/bmw.md +140 -0
  37. package/data/harnesses/code/skills/design/data/design-md/library/cal.md +140 -0
  38. package/data/harnesses/code/skills/design/data/design-md/library/clay.md +139 -0
  39. package/data/harnesses/code/skills/design/data/design-md/library/clickhouse.md +139 -0
  40. package/data/harnesses/code/skills/design/data/design-md/library/cohere.md +147 -0
  41. package/data/harnesses/code/skills/design/data/design-md/library/coinbase.md +156 -0
  42. package/data/harnesses/code/skills/design/data/design-md/library/composio.md +138 -0
  43. package/data/harnesses/code/skills/design/data/design-md/library/cursor.md +138 -0
  44. package/data/harnesses/code/skills/design/data/design-md/library/elevenlabs.md +137 -0
  45. package/data/harnesses/code/skills/design/data/design-md/library/expo.md +148 -0
  46. package/data/harnesses/code/skills/design/data/design-md/library/figma.md +141 -0
  47. package/data/harnesses/code/skills/design/data/design-md/library/framer.md +156 -0
  48. package/data/harnesses/code/skills/design/data/design-md/library/hashicorp.md +140 -0
  49. package/data/harnesses/code/skills/design/data/design-md/library/ibm.md +153 -0
  50. package/data/harnesses/code/skills/design/data/design-md/library/intercom.md +158 -0
  51. package/data/harnesses/code/skills/design/data/design-md/library/kraken.md +156 -0
  52. package/data/harnesses/code/skills/design/data/design-md/library/linear-app.md +122 -0
  53. package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +140 -0
  54. package/data/harnesses/code/skills/design/data/design-md/library/meta.md +139 -0
  55. package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +139 -0
  56. package/data/harnesses/code/skills/design/data/design-md/library/mintlify.md +139 -0
  57. package/data/harnesses/code/skills/design/data/design-md/library/miro.md +153 -0
  58. package/data/harnesses/code/skills/design/data/design-md/library/mistral-ai.md +140 -0
  59. package/data/harnesses/code/skills/design/data/design-md/library/mongodb.md +155 -0
  60. package/data/harnesses/code/skills/design/data/design-md/library/notion.md +140 -0
  61. package/data/harnesses/code/skills/design/data/design-md/library/nvidia.md +141 -0
  62. package/data/harnesses/code/skills/design/data/design-md/library/ollama.md +145 -0
  63. package/data/harnesses/code/skills/design/data/design-md/library/opencode-ai.md +142 -0
  64. package/data/harnesses/code/skills/design/data/design-md/library/posthog.md +152 -0
  65. package/data/harnesses/code/skills/design/data/design-md/library/raycast.md +137 -0
  66. package/data/harnesses/code/skills/design/data/design-md/library/replicate.md +140 -0
  67. package/data/harnesses/code/skills/design/data/design-md/library/resend.md +170 -0
  68. package/data/harnesses/code/skills/design/data/design-md/library/revolut.md +145 -0
  69. package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +137 -0
  70. package/data/harnesses/code/skills/design/data/design-md/library/sanity.md +168 -0
  71. package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +163 -0
  72. package/data/harnesses/code/skills/design/data/design-md/library/shopify.md +147 -0
  73. package/data/harnesses/code/skills/design/data/design-md/library/slack.md +158 -0
  74. package/data/harnesses/code/skills/design/data/design-md/library/spacex.md +171 -0
  75. package/data/harnesses/code/skills/design/data/design-md/library/spotify.md +166 -0
  76. package/data/harnesses/code/skills/design/data/design-md/library/stripe.md +160 -0
  77. package/data/harnesses/code/skills/design/data/design-md/library/supabase.md +173 -0
  78. package/data/harnesses/code/skills/design/data/design-md/library/superhuman.md +145 -0
  79. package/data/harnesses/code/skills/design/data/design-md/library/tesla.md +153 -0
  80. package/data/harnesses/code/skills/design/data/design-md/library/theverge.md +167 -0
  81. package/data/harnesses/code/skills/design/data/design-md/library/together-ai.md +140 -0
  82. package/data/harnesses/code/skills/design/data/design-md/library/uber.md +140 -0
  83. package/data/harnesses/code/skills/design/data/design-md/library/vercel.md +140 -0
  84. package/data/harnesses/code/skills/design/data/design-md/library/voltagent.md +130 -0
  85. package/data/harnesses/code/skills/design/data/design-md/library/warp.md +140 -0
  86. package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +140 -0
  87. package/data/harnesses/code/skills/design/data/design-md/library/wired.md +139 -0
  88. package/data/harnesses/code/skills/design/data/design-md/library/wise.md +137 -0
  89. package/data/harnesses/code/skills/design/data/design-md/library/x-ai.md +138 -0
  90. package/data/harnesses/code/skills/design/data/design-md/library/zapier.md +158 -0
  91. package/data/harnesses/code/skills/design/data/design-md/nurix.md +134 -0
  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/exemplars/command-center.tsx +249 -0
  220. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/create-edit.tsx +249 -0
  221. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/dashboard.tsx +248 -0
  222. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/entity-detail.tsx +245 -0
  223. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/list-detail.tsx +223 -0
  224. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/log-stream.tsx +249 -0
  225. package/data/harnesses/code/skills/design/data/operator-pack/fixtures.md +88 -0
  226. package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +132 -0
  227. package/data/harnesses/code/skills/design/data/operator-pack/trust-rubric.md +76 -0
  228. package/data/harnesses/code/skills/design/references/create.md +131 -0
  229. package/data/harnesses/code/skills/design/references/diagnosis.md +108 -0
  230. package/data/harnesses/code/skills/design/references/hard-rules.md +155 -0
  231. package/data/harnesses/code/skills/design/references/improve.md +87 -0
  232. package/data/harnesses/code/skills/design/references/interaction.md +77 -0
  233. package/data/harnesses/code/skills/design/references/introspect.md +100 -0
  234. package/data/harnesses/code/skills/design/references/lifecycle.md +73 -0
  235. package/data/harnesses/code/skills/design/references/personality.md +428 -0
  236. package/data/harnesses/code/skills/design/references/structure.md +138 -0
  237. package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +1032 -0
  238. package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +1006 -0
  239. package/data/harnesses/code/skills/design/scripts/generate-fixtures.mjs +551 -0
  240. package/data/harnesses/code/skills/design/scripts/lib/checks.mjs +903 -0
  241. package/data/harnesses/code/skills/design/scripts/lib/golden.mjs +26 -0
  242. package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +454 -0
  243. package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +267 -0
  244. package/data/harnesses/code/skills/design/scripts/render-rung.mjs +690 -0
  245. package/data/harnesses/code/skills/design/scripts/scaffold.mjs +552 -0
  246. package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +600 -0
  247. package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +68 -0
  248. package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +314 -0
  249. package/data/harnesses/code/skills/documentation/SKILL.md +61 -17
  250. package/data/harnesses/code/skills/documentation/references/examples.md +41 -0
  251. package/data/harnesses/code/skills/documentation/references/style-guide.md +14 -9
  252. package/data/harnesses/code/skills/documentation/references/templates.md +305 -51
  253. package/data/harnesses/etna/CLAUDE.md +1 -1
  254. package/data/harnesses/etna/agents/brainstorm.md +1 -1
  255. package/data/harnesses/etna/agents/maintenance.md +2 -2
  256. package/data/harnesses/etna/harness.json +2 -2
  257. package/data/harnesses/etna/rules/general.md +2 -40
  258. package/data/harnesses/etna/rules/nomenclature.md +1 -8
  259. package/data/harnesses/etna/skills/brainstorm/README.md +1 -1
  260. package/data/harnesses/etna/skills/brainstorm/SKILL.md +1 -1
  261. package/data/harnesses/etna/skills/brainstorm/exit-artifacts.md +4 -4
  262. package/data/harnesses/etna/skills/brainstorm/templates/source-of-truth.md +3 -3
  263. package/data/harnesses/etna/skills/documentation/SKILL.md +61 -17
  264. package/data/harnesses/etna/skills/documentation/references/examples.md +41 -0
  265. package/data/harnesses/etna/skills/documentation/references/style-guide.md +14 -9
  266. package/data/harnesses/etna/skills/documentation/references/templates.md +305 -51
  267. package/data/harnesses/mobile/CLAUDE.md +1 -1
  268. package/data/harnesses/mobile/agents/brainstorm.md +1 -1
  269. package/data/harnesses/mobile/agents/maintenance.md +2 -2
  270. package/data/harnesses/mobile/harness.json +2 -2
  271. package/data/harnesses/mobile/rules/database.md +38 -0
  272. package/data/harnesses/mobile/rules/general.md +2 -40
  273. package/data/harnesses/mobile/rules/nomenclature.md +1 -8
  274. package/data/harnesses/mobile/rules/nustack.md +150 -0
  275. package/data/harnesses/mobile/skills/brainstorm/README.md +1 -1
  276. package/data/harnesses/mobile/skills/brainstorm/SKILL.md +1 -1
  277. package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +4 -4
  278. package/data/harnesses/mobile/skills/brainstorm/templates/source-of-truth.md +3 -3
  279. package/data/harnesses/mobile/skills/documentation/SKILL.md +61 -17
  280. package/data/harnesses/mobile/skills/documentation/references/examples.md +41 -0
  281. package/data/harnesses/mobile/skills/documentation/references/style-guide.md +14 -9
  282. package/data/harnesses/mobile/skills/documentation/references/templates.md +305 -51
  283. package/package.json +1 -1
@@ -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
+ }
@@ -0,0 +1,454 @@
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
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { join, dirname, resolve, relative } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { runChecks, formatFindings, walkUiFiles } from './lib/checks.mjs';
7
+
8
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
9
+ const PHASES = ['intake', 'structure', 'scaffold', 'tokens', 'fixtures', 'surfaces'];
10
+ const ARCHETYPES = new Set(['list-detail', 'dashboard', 'entity-detail', 'create-edit', 'log-stream', 'command-center']);
11
+ const PROVENANCE = new Set(['project', 'extracted', 'library', 'default']);
12
+ const STATE_SYNONYMS = { loading: ['loading', 'skeleton'], empty: ['empty'], error: ['error'], partial: ['partial', 'degraded', 'stale'] };
13
+
14
+ const useColor = process.stdout.isTTY;
15
+ // Wrap a string in green/red when writing to a TTY, plain otherwise.
16
+ const green = (s) => (useColor ? `\x1b[32m${s}\x1b[0m` : s);
17
+ const red = (s) => (useColor ? `\x1b[31m${s}\x1b[0m` : s);
18
+
19
+ // Read a UTF-8 file, returning '' when it is absent.
20
+ function readText(path) {
21
+ try { return readFileSync(path, 'utf8'); } catch { return ''; }
22
+ }
23
+
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).
28
+ function pass(phase, msg) {
29
+ console.log(`${green(`PASS phase-gate[${phase}]`)} ${msg}`);
30
+ }
31
+
32
+ // Report a failing gate with the problem and the exact fix, then abort the gate.
33
+ function fail(phase, problem, fixLines) {
34
+ console.error(`${red(`FAIL phase-gate[${phase}]`)} ${problem}`);
35
+ console.error('');
36
+ console.error(' Fix:');
37
+ for (const line of fixLines) {
38
+ if (line == null || line === '') continue;
39
+ for (const sub of String(line).split('\n')) console.error(` ${sub}`);
40
+ }
41
+ throw new GateFailure(problem);
42
+ }
43
+
44
+ // Lowercase-kebab a display name for route-dir / fixture-module matching.
45
+ function kebab(s) {
46
+ return String(s).trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
47
+ }
48
+
49
+ // Return the first non-parameter path segment of a route (/tickets/:id -> tickets).
50
+ function firstStaticSegment(route) {
51
+ for (const p of String(route).split('/').filter(Boolean)) {
52
+ if (!p.startsWith(':')) return kebab(p);
53
+ }
54
+ return null;
55
+ }
56
+
57
+ // Return the last non-parameter path segment of a route (/tickets/new -> new).
58
+ function lastStaticSegment(route) {
59
+ const parts = String(route).split('/').filter(Boolean).filter((p) => !p.startsWith(':'));
60
+ return parts.length ? kebab(parts[parts.length - 1]) : null;
61
+ }
62
+
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
+ }
82
+ }
83
+ return named;
84
+ }
85
+
86
+ // Split a `[a, b, c]` inline list into trimmed members.
87
+ function parseBracketList(s) {
88
+ const m = String(s).match(/\[([^\]]*)\]/);
89
+ if (!m) return [];
90
+ return m[1].split(',').map((x) => x.trim()).filter(Boolean);
91
+ }
92
+
93
+ // Store one `key: value` pair onto a list-item object, parsing `states` into an array.
94
+ function applyKV(obj, s) {
95
+ const idx = s.indexOf(':');
96
+ if (idx < 0) return;
97
+ const key = s.slice(0, idx).trim();
98
+ const value = s.slice(idx + 1).trim();
99
+ if (key === 'states') obj.states = parseBracketList(value);
100
+ else obj[key] = value;
101
+ }
102
+
103
+ // Parse the fenced ```yaml structure block into entities, surfaces, and temporal facts.
104
+ function parseStructureBlock(md) {
105
+ const m = md.match(/```yaml structure\r?\n([\s\S]*?)```/);
106
+ if (!m) return null;
107
+ const result = { unitOfWork: null, coreLoop: null, entities: [], temporal: null, surfaces: [] };
108
+ let section = null;
109
+ let itemIndent = -1;
110
+ let current = null;
111
+ for (const raw of m[1].split(/\r?\n/)) {
112
+ if (!raw.trim() || raw.trim().startsWith('#')) continue;
113
+ const indent = raw.length - raw.trimStart().length;
114
+ const line = raw.trim();
115
+ if (indent === 0) {
116
+ current = null;
117
+ itemIndent = -1;
118
+ if (line.startsWith('unit-of-work:')) { result.unitOfWork = line.slice(line.indexOf(':') + 1).trim(); section = null; }
119
+ else if (line.startsWith('core-loop:')) { result.coreLoop = line.slice(line.indexOf(':') + 1).trim(); section = null; }
120
+ else if (line === 'entities:') section = 'entities';
121
+ else if (line === 'surfaces:') section = 'surfaces';
122
+ else if (line === 'temporal:') { section = 'temporal'; result.temporal = {}; }
123
+ else section = null;
124
+ continue;
125
+ }
126
+ if (section === 'entities' || section === 'surfaces') {
127
+ const list = section === 'entities' ? result.entities : result.surfaces;
128
+ if (line.startsWith('- ')) {
129
+ if (itemIndent === -1) itemIndent = indent;
130
+ if (indent === itemIndent) {
131
+ current = {};
132
+ list.push(current);
133
+ const rest = line.slice(2).trim();
134
+ if (rest && rest.includes(':')) applyKV(current, rest);
135
+ }
136
+ } else if (current && itemIndent !== -1 && indent > itemIndent) {
137
+ applyKV(current, line);
138
+ }
139
+ } else if (section === 'temporal') {
140
+ const idx = line.indexOf(':');
141
+ if (idx > 0) result.temporal[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+
147
+ // Read and parse the structure map, or fail the gate with the structure-phase fix.
148
+ function requireMap(phase, project) {
149
+ const p = join(project, 'docs/design/structure.md');
150
+ if (!existsSync(p)) {
151
+ fail(phase, `No entity/surface map at ${p} — this phase reads it.`, [
152
+ 'Complete the structure phase first: derive docs/design/structure.md with a fenced ```yaml structure block (references/structure.md).',
153
+ ]);
154
+ }
155
+ const blk = parseStructureBlock(readText(p));
156
+ if (!blk) {
157
+ fail(phase, `${p} has no parseable "yaml structure" fenced block.`, [
158
+ 'The block must open exactly with the fence info-string `yaml structure` and carry entities[], temporal, and surfaces[].',
159
+ ]);
160
+ }
161
+ return blk;
162
+ }
163
+
164
+ // Collect all .ts/.tsx source under a route directory into one lowercased blob.
165
+ function routeSource(dir) {
166
+ let out = '';
167
+ const stack = [dir];
168
+ while (stack.length) {
169
+ const cur = stack.pop();
170
+ let entries;
171
+ try { entries = readdirSync(cur, { withFileTypes: true }); } catch { continue; }
172
+ for (const ent of entries) {
173
+ const full = join(cur, ent.name);
174
+ if (ent.isDirectory()) stack.push(full);
175
+ else if (/\.(tsx|ts)$/.test(ent.name)) out += `\n${readText(full)}`;
176
+ }
177
+ }
178
+ return out.toLowerCase();
179
+ }
180
+
181
+ // Return the candidate route-dir slugs for a surface (name-kebab, route segments, archetype).
182
+ function routeDirCandidates(s) {
183
+ const out = [];
184
+ if (s.name) out.push(kebab(s.name));
185
+ if (s.route) { const f = firstStaticSegment(s.route); if (f) out.push(f); const l = lastStaticSegment(s.route); if (l) out.push(l); }
186
+ if (s.archetype) out.push(s.archetype);
187
+ return [...new Set(out.filter(Boolean))];
188
+ }
189
+
190
+ // Resolve a surface's route directory to the first existing candidate, or null.
191
+ function resolveRouteDir(s, routesDir) {
192
+ for (const c of routeDirCandidates(s)) {
193
+ const d = join(routesDir, c);
194
+ if (existsSync(d) && statSync(d).isDirectory()) return d;
195
+ }
196
+ return null;
197
+ }
198
+
199
+ // List the declared states whose branch is absent from a route's shell source.
200
+ function missingStates(dir, states) {
201
+ const text = routeSource(dir);
202
+ return states.filter((st) => {
203
+ const words = STATE_SYNONYMS[st] || [st.toLowerCase()];
204
+ return !words.some((w) => text.includes(w));
205
+ });
206
+ }
207
+
208
+ // Validate the load-bearing four-step globals.css import order (Cascade Contract 2).
209
+ function checkImportOrder(rawCss) {
210
+ const css = rawCss.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^[ \t]*\/\/.*$/gm, '');
211
+ const marks = [
212
+ ['@import "./personality.css"', css.indexOf('@import "./personality.css"')],
213
+ ['@import "tailwindcss"', css.indexOf('@import "tailwindcss"')],
214
+ ['@import "@nurix/components/styles.css"', css.indexOf('@import "@nurix/components/styles.css"')],
215
+ ['@theme inline', css.search(/^\s*@theme\s+inline\b/m)],
216
+ ];
217
+ const absent = marks.filter(([, i]) => i < 0).map(([n]) => n);
218
+ if (absent.length) {
219
+ return { problem: `globals.css is missing: ${absent.join(', ')}.`, fix: [
220
+ 'Emit the four-step cascade order (references/personality.md §4, Cascade Contract 2):',
221
+ '@import "./personality.css"; -> @import "tailwindcss"; -> @import "@nurix/components/styles.css"; -> @theme inline { ... }',
222
+ ] };
223
+ }
224
+ const [p, t, l, th] = marks.map(([, i]) => i);
225
+ if (!(p < t && t < l && l < th)) {
226
+ return { problem: 'globals.css import order is wrong — personality -> tailwindcss -> library -> @theme is load-bearing.', fix: [
227
+ 'Reorder so the consumer @theme inline comes AFTER the @nurix/components/styles.css import, or the library radius scale and token defaults win.',
228
+ ] };
229
+ }
230
+ return null;
231
+ }
232
+
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.
236
+ function gateIntake(project) {
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).',
258
+ ]);
259
+ }
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'}.`);
276
+ }
277
+
278
+ // structure gate: the map parses, is populated, has a temporal block, and every surface archetype is one of the six.
279
+ function gateStructure(project) {
280
+ const blk = requireMap('structure', project);
281
+ if (!blk.entities.length) fail('structure', 'The structure map has no entities — the object pass was skipped.', ['Populate entities[] from the domain nouns (references/structure.md -> object pass); a small brief earns a small pass, never a skipped one.']);
282
+ if (!blk.surfaces.length) fail('structure', 'The structure map has no surfaces.', ['Assign each surface an archetype from the closed six under surfaces[].']);
283
+ if (!blk.temporal || !Object.keys(blk.temporal).length) fail('structure', 'The structure map has no temporal block.', ['Add the temporal probe (intermediate-states, read-model-lag, deletion, failure-paths) — model facts, not later polish.']);
284
+ const bad = blk.surfaces.filter((s) => !ARCHETYPES.has(s.archetype));
285
+ if (bad.length) {
286
+ fail('structure', `${bad.length} surface(s) carry an archetype outside the closed six: ${bad.map((s) => `${s.name || '(unnamed)'}="${s.archetype || '(none)'}"`).join(', ')}.`, [
287
+ `Every surface's archetype must be one of: ${[...ARCHETYPES].join(', ')}.`,
288
+ ]);
289
+ }
290
+ pass('structure', `${blk.surfaces.length} surface(s), all archetypes valid; ${blk.entities.length} entit(ies) and a temporal block present.`);
291
+ }
292
+
293
+ // scaffold gate: the shell, per-surface route dirs + state-shells, router wiring, and globals.css import order are in place.
294
+ function gateScaffold(project) {
295
+ const blk = requireMap('scaffold', project);
296
+ const shellFiles = ['index.html', 'src/main.tsx', 'src/App.tsx'];
297
+ const missing = shellFiles.filter((f) => !existsSync(join(project, f)));
298
+ if (missing.length) {
299
+ fail('scaffold', `Missing shell file(s): ${missing.join(', ')}.`, [
300
+ `Run scaffold.mjs docs/design/structure.md --out ${project} to emit index.html, src/main.tsx, src/App.tsx, route wiring, and state-shells.`,
301
+ ]);
302
+ }
303
+ const indexHtml = readText(join(project, 'index.html'));
304
+ if (!/<html[^>]*\bdata-style=/.test(indexHtml)) {
305
+ fail('scaffold', 'index.html <html> is missing the data-style attribute.', [
306
+ 'Emit <html data-style="..." data-density="..." data-verbosity="..." data-feedback="..." data-theme="light"> (references/personality.md §5).',
307
+ ]);
308
+ }
309
+ const routesDir = join(project, 'src/routes');
310
+ const app = readText(join(project, 'src/App.tsx'));
311
+ const noRoute = [];
312
+ const noWire = [];
313
+ const noShell = [];
314
+ for (const s of blk.surfaces) {
315
+ const dir = resolveRouteDir(s, routesDir);
316
+ const name = s.name || s.route || '(unnamed)';
317
+ if (!dir) { noRoute.push(`${name} (expected src/routes/${routeDirCandidates(s).join(' or /')})`); continue; }
318
+ const seg = s.route ? firstStaticSegment(s.route) : null;
319
+ if (seg && !app.toLowerCase().includes(seg)) noWire.push(`${name} (route "${s.route}" not referenced in src/App.tsx)`);
320
+ const states = (s.states && s.states.length) ? s.states : ['loading', 'empty', 'error', 'partial'];
321
+ const miss = missingStates(dir, states);
322
+ if (miss.length) noShell.push(`${name} (no ${miss.join(', ')} shell)`);
323
+ }
324
+ if (noRoute.length) fail('scaffold', `No route directory for surface(s): ${noRoute.join('; ')}.`, ['scaffold.mjs owns route/nav wiring — emit src/routes/<surface>/ per surface from the map.']);
325
+ if (noWire.length) fail('scaffold', `Route(s) not wired into the router: ${noWire.join('; ')}.`, ['Every surface route must be registered in src/App.tsx (react-router-dom) — routes and nav derive from the map, never decorated on.']);
326
+ if (noShell.length) fail('scaffold', `Missing state-shell stubs: ${noShell.join('; ')}.`, ['Each surface needs its loading / empty / error / degraded shells wired (references/create.md -> scaffold).']);
327
+ const globals = join(project, 'src/styles/globals.css');
328
+ if (!existsSync(globals)) fail('scaffold', 'Missing src/styles/globals.css (Tailwind glue with the four-step import order).', ['Emit src/styles/globals.css with the Cascade Contract 2 import order (references/personality.md §4).']);
329
+ const orderErr = checkImportOrder(readText(globals));
330
+ if (orderErr) fail('scaffold', orderErr.problem, orderErr.fix);
331
+ pass('scaffold', `Shell, ${blk.surfaces.length} wired route dir(s) with state-shells, and globals.css import order all in place.`);
332
+ }
333
+
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.
335
+ function gateTokens(project) {
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).']);
338
+ const derive = join(SCRIPT_DIR, 'derive-personality.mjs');
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.']);
340
+ const res = spawnSync(process.execPath, [derive, project, '--check'], { encoding: 'utf8' });
341
+ if (res.status !== 0) {
342
+ const detail = `${res.stdout || ''}${res.stderr || ''}`.trim();
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:',
345
+ detail,
346
+ ]);
347
+ }
348
+ const files = ['src/styles/personality.css', 'src/styles/globals.css'];
349
+ const miss = files.filter((f) => !existsSync(join(project, f)));
350
+ if (miss.length) {
351
+ fail('tokens', `Coverage gate passed but emitted CSS is missing: ${miss.join(', ')}.`, [
352
+ `Run derive-personality.mjs ${project} --emit ${project} to write src/styles/personality.css and src/styles/globals.css.`,
353
+ ]);
354
+ }
355
+ const orderErr = checkImportOrder(readText(join(project, 'src/styles/globals.css')));
356
+ if (orderErr) fail('tokens', orderErr.problem, orderErr.fix);
357
+ pass('tokens', 'Coverage gate green; personality.css + globals.css emitted with the cascade order intact.');
358
+ }
359
+
360
+ // fixtures gate: src/fixtures/ carries one seeded module per map entity.
361
+ function gateFixtures(project) {
362
+ const blk = requireMap('fixtures', project);
363
+ const fdir = join(project, 'src/fixtures');
364
+ if (!existsSync(fdir)) {
365
+ fail('fixtures', 'No src/fixtures/ directory.', [
366
+ `Run generate-fixtures.mjs docs/design/structure.md --seed <n> --out ${join(project, 'src/fixtures/')} to write one seeded module per entity.`,
367
+ ]);
368
+ }
369
+ const bases = readdirSync(fdir).filter((f) => /\.(ts|tsx)$/.test(f)).map((f) => f.replace(/\.(ts|tsx)$/, '').toLowerCase());
370
+ const missing = [];
371
+ for (const e of blk.entities) {
372
+ if (!e.name) continue;
373
+ const base = e.name.toLowerCase();
374
+ const k = kebab(e.name);
375
+ const cands = new Set([base, k, `${base}s`, `${k}s`]);
376
+ if (/y$/.test(base)) { cands.add(`${base.slice(0, -1)}ies`); cands.add(`${k.slice(0, -1)}ies`); }
377
+ if (!bases.some((n) => cands.has(n))) missing.push(e.name);
378
+ }
379
+ if (missing.length) {
380
+ fail('fixtures', `No seeded fixture module for entity/ies: ${missing.join(', ')}.`, [
381
+ 'Each map entity needs a seeded module in src/fixtures/ (fixture-before-surface). Re-run generate-fixtures.mjs.',
382
+ ]);
383
+ }
384
+ pass('fixtures', `Seeded fixture module present for all ${blk.entities.length} entit(ies).`);
385
+ }
386
+
387
+ // surfaces gate: every surface's route dir exists and its shell contains every declared state branch.
388
+ function gateSurfaces(project) {
389
+ const blk = requireMap('surfaces', project);
390
+ const routesDir = join(project, 'src/routes');
391
+ const problems = [];
392
+ for (const s of blk.surfaces) {
393
+ const name = s.name || s.route || '(unnamed)';
394
+ const dir = resolveRouteDir(s, routesDir);
395
+ if (!dir) { problems.push(`${name}: no route dir under src/routes/ (expected ${routeDirCandidates(s).join(' or ')})`); continue; }
396
+ const states = (s.states && s.states.length) ? s.states : ['loading', 'empty', 'error'];
397
+ const miss = missingStates(dir, states);
398
+ if (miss.length) problems.push(`${name}: shell missing state branch(es) ${miss.join(', ')}`);
399
+ }
400
+ if (problems.length) {
401
+ fail('surfaces', `${problems.length} surface(s) not fully composed:`, [
402
+ ...problems.map((p) => `- ${p}`),
403
+ 'Fill every declared lifecycle state into its pre-created shell (references/create.md -> surfaces); states are composed with the surface, not bolted on.',
404
+ ]);
405
+ }
406
+ pass('surfaces', `All ${blk.surfaces.length} surface(s) have a route dir with every declared state branch present.`);
407
+ }
408
+
409
+ const GATES = { intake: gateIntake, structure: gateStructure, scaffold: gateScaffold, tokens: gateTokens, fixtures: gateFixtures, surfaces: gateSurfaces };
410
+
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.
431
+ function main() {
432
+ const args = process.argv.slice(2);
433
+ const phase = args[0];
434
+ let project = process.cwd();
435
+ for (let i = 1; i < args.length; i++) {
436
+ if (args[i] === '--project') project = resolve(args[++i] || '.');
437
+ else if (args[i].startsWith('--project=')) project = resolve(args[i].slice('--project='.length));
438
+ }
439
+ if (!phase || !PHASES.includes(phase)) {
440
+ console.error(`usage: phase-gate.mjs <${PHASES.join('|')}> [--project <dir>]`);
441
+ process.exit(2);
442
+ }
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);
452
+ }
453
+
454
+ main();