@nurix/etna 0.18.20 → 0.18.22

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 +3 -3
  4. package/data/harnesses/code/agents/brainstorm.md +1 -1
  5. package/data/harnesses/code/agents/maintenance.md +8 -7
  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 +4 -4
  14. package/data/harnesses/code/skills/brainstorm/SKILL.md +5 -3
  15. package/data/harnesses/code/skills/brainstorm/exit-artifacts.md +15 -11
  16. package/data/harnesses/code/skills/brainstorm/templates/source-of-truth.md +5 -5
  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 +306 -52
  241. package/data/harnesses/etna/CLAUDE.md +3 -3
  242. package/data/harnesses/etna/agents/brainstorm.md +1 -1
  243. package/data/harnesses/etna/agents/maintenance.md +8 -7
  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 +4 -4
  248. package/data/harnesses/etna/skills/brainstorm/SKILL.md +5 -3
  249. package/data/harnesses/etna/skills/brainstorm/exit-artifacts.md +15 -11
  250. package/data/harnesses/etna/skills/brainstorm/templates/source-of-truth.md +5 -5
  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 +306 -52
  255. package/data/harnesses/mobile/CLAUDE.md +3 -3
  256. package/data/harnesses/mobile/agents/brainstorm.md +1 -1
  257. package/data/harnesses/mobile/agents/maintenance.md +8 -7
  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 +4 -4
  264. package/data/harnesses/mobile/skills/brainstorm/SKILL.md +5 -3
  265. package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +15 -11
  266. package/data/harnesses/mobile/skills/brainstorm/templates/source-of-truth.md +5 -5
  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 +306 -52
  271. package/package.json +1 -1
  272. package/data/harnesses/code/skills/design/scripts/hook-config.json +0 -11
@@ -1,8 +1,8 @@
1
- // skills/design/scripts/preship-sweep.mjs — verification-ladder rung 1 (D21/D23): whole-app static sweep — detector relay + cross-file machine checks + mandatory-procedures checklist + scope cross-check.
1
+ // skills/design/scripts/preship-sweep.mjs — verification-ladder rung 1 (D21/D23): static sweep — per-file catalog + manifest drift via lib/checks.mjs + cross-file machine checks + mandatory-procedures checklist + scope cross-check. --files <a> <b> … scopes the sweep (improve's end-of-run verify); no args = whole-app. --manifest <design.tokens.json | artifact dir> overrides the drift vocabulary source (default: <project>/design.tokens.json).
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
3
  import { join, dirname, resolve, relative } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { spawnSync } from 'node:child_process';
5
+ import { runChecks } from './lib/checks.mjs';
6
6
 
7
7
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
8
8
  const CATALOG = join(SCRIPT_DIR, '..', 'data', 'catalog', 'rules.csv');
@@ -122,56 +122,27 @@ function countNav(project) {
122
122
  return (text.match(/\bpath\s*=/g) || []).length;
123
123
  }
124
124
 
125
- // Try to parse detector --json stdout into a flat findings array, or null.
126
- function parseDetectorJson(stdout) {
127
- const s = (stdout || '').trim();
128
- if (!s) return [];
129
- try {
130
- const parsed = JSON.parse(s);
131
- if (Array.isArray(parsed)) return parsed;
132
- if (parsed && Array.isArray(parsed.findings)) return parsed.findings;
133
- if (parsed && Array.isArray(parsed.violations)) return parsed.violations;
134
- return null;
135
- } catch { return null; }
136
- }
137
-
138
- // Render one relayed detector finding as a single fix-or-classify line.
125
+ // Render one catalog finding as a single fix-or-classify line.
139
126
  function formatFinding(f, project) {
140
- const anchor = f.id || f.anchor || f.rule || f.key || '?';
127
+ const anchor = f.rule || '?';
141
128
  const file = f.file ? relative(project, f.file) || f.file : '';
142
129
  const loc = f.line != null ? `${file}:${f.line}` : file;
143
- const evidence = f.excerpt || f.evidence || f.value || '';
144
- const msg = f.message || f.statement || f.detail || f.fix || '';
145
- const ev = evidence ? ` [${evidence}]` : '';
146
- return ` ${red(anchor)} ${loc}${ev} ${msg}`.trimEnd();
130
+ const ev = f.excerpt ? ` [${f.excerpt}]` : '';
131
+ return ` ${red(anchor)} ${loc}${ev} ${f.fix || ''}`.trimEnd();
147
132
  }
148
133
 
149
- // Spawn detector.mjs over every source file and relay its findings; returns a violation count or -1 when unavailable.
150
- function runDetector(project, files, section) {
151
- const detector = join(SCRIPT_DIR, 'detector.mjs');
152
- if (!existsSync(detector)) {
153
- section.push(yellow(` SWEEP INCOMPLETE: detector.mjs not found at ${detector} — the per-file catalog was not run. Build it before shipping.`));
154
- return -1;
155
- }
134
+ // Run the per-file catalog + manifest drift (lib/checks.mjs) over the file set; returns the violation count.
135
+ function runCatalog(project, files, manifestPath, section) {
156
136
  if (!files.length) { section.push(' (no src/**/*.{tsx,ts,css,html} files to scan)'); return 0; }
157
- const args = [detector, ...files, '--json'];
158
- const manifest = join(project, 'design.md');
159
- if (existsSync(manifest)) args.push('--manifest', manifest);
160
- const res = spawnSync(process.execPath, args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
161
- const findings = parseDetectorJson(res.stdout);
162
- if (findings) {
163
- if (!findings.length) { section.push(green(' detector: no per-file violations.')); return 0; }
164
- section.push(` detector reported ${findings.length} violation(s) — fix each now, or classify why it does not apply in docs/memory/design-decay.md; never ignore silently:`);
165
- for (const f of findings) section.push(formatFinding(f, project));
166
- return findings.length;
167
- }
168
- if (res.stdout) section.push(res.stdout.replace(/\n$/, '').split('\n').map((l) => ` ${l}`).join('\n'));
169
- if (res.stderr) section.push(yellow(res.stderr.replace(/\n$/, '').split('\n').map((l) => ` ${l}`).join('\n')));
170
- return res.status ? 1 : 0;
137
+ const findings = runChecks(files, { manifestPath });
138
+ if (!findings.length) { section.push(green(' catalog checks: no per-file violations.')); return 0; }
139
+ section.push(` catalog checks reported ${findings.length} violation(s) — fix each now, or classify why it does not apply in docs/memory/design-decay.md; never ignore silently:`);
140
+ for (const f of findings) section.push(formatFinding(f, project));
141
+ return findings.length;
171
142
  }
172
143
 
173
- // Run the cross-file machine checks (dep-02 one-h1 + heading skips) and the advisory nav-count.
174
- function crossFileChecks(project, files, catalog, section) {
144
+ // Run the cross-file machine checks (dep-02 one-h1 + heading skips) and, on whole-app runs, the route-shell aggregate and advisory nav-count.
145
+ function crossFileChecks(project, files, catalog, section, scoped) {
175
146
  const violations = [];
176
147
  for (const file of files) {
177
148
  if (!/\.(tsx|html)$/.test(file)) continue;
@@ -183,7 +154,7 @@ function crossFileChecks(project, files, catalog, section) {
183
154
  if (skips.length) violations.push(` ${red('dep-02')} ${rel} heading level skips (${skips.join(', ')}); heading levels never skip.`);
184
155
  }
185
156
  const routesDir = join(project, 'src/routes');
186
- if (existsSync(routesDir)) {
157
+ if (!scoped && existsSync(routesDir)) {
187
158
  for (const ent of readdirSync(routesDir, { withFileTypes: true })) {
188
159
  if (!ent.isDirectory()) continue;
189
160
  const dir = join(routesDir, ent.name);
@@ -194,12 +165,14 @@ function crossFileChecks(project, files, catalog, section) {
194
165
  if (violations.length) { section.push(...violations); }
195
166
  else section.push(green(' cross-file machine checks (dep-02 one-h1, heading-skip): clean.'));
196
167
 
197
- const bounds = navBounds(catalog);
198
- const nav = countNav(project);
199
- if (bounds) {
200
- const within = nav >= bounds.min && nav <= bounds.max;
201
- const note = ` ${within ? '' : yellow('[advisory] ')}nav items: ${nav} (spc-14 advisory band ${bounds.min}-${bounds.max}${within ? ', within' : ', outside — judged, not gated'}).`;
202
- section.push(note);
168
+ if (!scoped) {
169
+ const bounds = navBounds(catalog);
170
+ const nav = countNav(project);
171
+ if (bounds) {
172
+ const within = nav >= bounds.min && nav <= bounds.max;
173
+ const note = ` ${within ? '' : yellow('[advisory] ')}nav items: ${nav} (spc-14 advisory band ${bounds.min}-${bounds.max}${within ? ', within' : ', outside — judged, not gated'}).`;
174
+ section.push(note);
175
+ }
203
176
  }
204
177
  return violations.length;
205
178
  }
@@ -221,59 +194,73 @@ function procChecklist(catalog) {
221
194
  return lines;
222
195
  }
223
196
 
224
- // Assemble and print the whole sweep, then exit non-zero when detector or cross-file checks found violations.
197
+ // Assemble and print the whole sweep, then exit non-zero when catalog or cross-file checks found violations.
225
198
  function main() {
226
199
  const args = process.argv.slice(2);
227
200
  let project = process.cwd();
201
+ let scopedFiles = null;
202
+ let manifestArg = null;
228
203
  for (let i = 0; i < args.length; i++) {
229
204
  if (args[i] === '--project') project = resolve(args[++i] || '.');
230
205
  else if (args[i].startsWith('--project=')) project = resolve(args[i].slice('--project='.length));
206
+ else if (args[i] === '--manifest') manifestArg = resolve(args[++i] || '.');
207
+ else if (args[i].startsWith('--manifest=')) manifestArg = resolve(args[i].slice('--manifest='.length));
208
+ else if (args[i] === '--files') {
209
+ scopedFiles = [];
210
+ while (i + 1 < args.length && !args[i + 1].startsWith('--')) scopedFiles.push(resolve(args[++i]));
211
+ }
231
212
  }
232
213
 
233
214
  const catalog = loadCatalog();
234
- const files = walk(join(project, 'src'), ['.tsx', '.ts', '.css', '.html']);
215
+ const scoped = scopedFiles !== null;
216
+ const files = scoped ? scopedFiles : walk(join(project, 'src'), ['.tsx', '.ts', '.css', '.html']);
217
+ const defaultManifest = join(project, 'design.tokens.json');
218
+ const manifestPath = manifestArg || (existsSync(defaultManifest) ? defaultManifest : null);
235
219
 
236
- console.log(bold(`preship-sweep — verification ladder rung 1 (project: ${project})`));
220
+ console.log(bold(`preship-sweep — verification ladder rung 1 (project: ${project}${scoped ? `, scoped to ${files.length} file(s) via --files` : ''})`));
237
221
  console.log('');
238
222
 
239
- console.log(bold('(a) Per-file catalog — detector.mjs relay'));
223
+ console.log(bold('(a) Per-file catalog + manifest drift — lib/checks.mjs'));
240
224
  const secA = [];
241
- const detectorCount = runDetector(project, files, secA);
225
+ const catalogCount = runCatalog(project, files, manifestPath, secA);
242
226
  console.log(secA.join('\n'));
243
227
  console.log('');
244
228
 
245
229
  console.log(bold('(b) Cross-file machine checks'));
246
230
  const secB = [];
247
- const crossCount = crossFileChecks(project, files, catalog, secB);
231
+ const crossCount = crossFileChecks(project, files, catalog, secB, scoped);
248
232
  console.log(secB.join('\n'));
249
233
  console.log('');
250
234
 
251
- console.log(bold('(c) Mandatory procedures — self-audit checklist (proc-*)'));
252
- console.log(procChecklist(catalog).join('\n'));
253
- console.log('');
235
+ if (!scoped) {
236
+ console.log(bold('(c) Mandatory procedures — self-audit checklist (proc-*)'));
237
+ console.log(procChecklist(catalog).join('\n'));
238
+ console.log('');
254
239
 
255
- console.log(bold('(c) Scope cross-check (proc-14)'));
256
- const surfaces = countSurfaces(project);
257
- const deliverables = countDeliverables(project);
258
- if (surfaces == null) {
259
- console.log(` ${yellow('no docs/design/structure.md map found — cannot cross-check scope.')}`);
240
+ console.log(bold('(c) Scope cross-check (proc-14)'));
241
+ const surfaces = countSurfaces(project);
242
+ const deliverables = countDeliverables(project);
243
+ if (surfaces == null) {
244
+ console.log(` ${yellow('no docs/design/structure.md map found — cannot cross-check scope.')}`);
245
+ } else {
246
+ console.log(` structure map declares ${surfaces} surface(s); found ${deliverables} route deliverable(s) under src/routes/.`);
247
+ if (surfaces !== deliverables) console.log(` ${yellow(`[advisory] scope mismatch (${surfaces} vs ${deliverables}) — add or reconcile before responding (proc-14).`)}`);
248
+ }
249
+ console.log('');
260
250
  } else {
261
- console.log(` structure map declares ${surfaces} surface(s); found ${deliverables} route deliverable(s) under src/routes/.`);
262
- if (surfaces !== deliverables) console.log(` ${yellow(`[advisory] scope mismatch (${surfaces} vs ${deliverables}) — add or reconcile before responding (proc-14).`)}`);
251
+ console.log('(scoped run — the mandatory-procedures checklist and scope cross-check run on the whole-app sweep)');
252
+ console.log('');
263
253
  }
264
- console.log('');
265
254
 
266
- const detectorFailed = detectorCount === -1 || detectorCount > 0;
267
- const failed = detectorFailed || crossCount > 0;
255
+ const failed = catalogCount > 0 || crossCount > 0;
268
256
  if (failed) {
269
257
  const reasons = [];
270
- if (detectorCount === -1) reasons.push('detector.mjs unavailable (sweep incomplete)');
271
- if (detectorCount > 0) reasons.push(`${detectorCount} per-file violation(s)`);
258
+ if (catalogCount > 0) reasons.push(`${catalogCount} per-file violation(s)`);
272
259
  if (crossCount > 0) reasons.push(`${crossCount} cross-file violation(s)`);
273
260
  console.log(red(`FAIL preship-sweep — ${reasons.join('; ')}. Resolve, then re-run.`));
274
261
  process.exit(1);
275
262
  }
276
- console.log(green('PASS preship-sweep — no static violations; work the checklist above before ship.'));
263
+ console.log(green(scoped ? 'PASS preship-sweep (scoped) — no static violations in the given files.' : 'PASS preship-sweep — no static violations; work the checklist above before ship.'));
277
264
  process.exit(0);
278
265
  }
279
266
 
@@ -440,7 +440,7 @@ function addFinding(findings, viewport, finding) {
440
440
  else findings.set(key, { ...finding, viewports: new Set([viewport]) });
441
441
  }
442
442
 
443
- // Print one finding in the detector's fix-or-classify format.
443
+ // Print one finding in the check engine's fix-or-classify format.
444
444
  function printFinding(f) {
445
445
  const vp = [...f.viewports].sort((a, b) => a - b).join(', ');
446
446
  console.log(`[${f.severity}] ${f.anchor} ${f.dimension} — ${f.statement}`);
@@ -284,7 +284,7 @@ function emit(root, rel, content, written) {
284
284
  function indexHtml() {
285
285
  return `<!doctype html>
286
286
  <!-- Generated by scaffold.mjs. Personality state lives in these <html data-*> attributes (code is the cache).
287
- The values below are placeholders; derive-personality.mjs --emit resolves them from the project's design.md. -->
287
+ The values below are placeholders; derive-personality.mjs --emit resolves them from the project's design-tokens artifact (design.tokens.json). -->
288
288
  <html
289
289
  lang="en"
290
290
  data-style="app"
@@ -1,8 +1,12 @@
1
- // skills/design/scripts/self-integrity.mjs — the design skill's own CI check: anchor parity, catalog invariants, stated-count and fixture-pair validation
2
- import { readFileSync, existsSync, readdirSync } from "node:fs";
3
- import { join, dirname, basename } from "node:path";
1
+ // skills/design/scripts/self-integrity.mjs — the design skill's own CI check: anchor parity, catalog invariants, stated-count and fixture-pair validation (pairs exercised through lib/checks.mjs), the token-era net — token file-tree + goldens parity, per-artifact --check validity, and (behind --goldens) golden emission hash match — plus combination-data validation (validate-combinations.mjs); --fixture <rule-id> runs a single pair
2
+ import { readFileSync, existsSync, readdirSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { join, dirname, resolve } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { spawn } from "node:child_process";
4
6
  import { fileURLToPath } from "node:url";
5
- import { spawnSync } from "node:child_process";
7
+ import { runChecks } from "./lib/checks.mjs";
8
+ import { goldenHash } from "./lib/golden.mjs";
9
+ import { validateCombinations } from "./validate-combinations.mjs";
6
10
 
7
11
  const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
8
12
  const SKILL_ROOT = join(SCRIPTS_DIR, "..");
@@ -10,7 +14,11 @@ const SKILL_ROOT = join(SCRIPTS_DIR, "..");
10
14
  const CATALOG_CSV = join(SKILL_ROOT, "data/catalog/rules.csv");
11
15
  const FIXTURES_DIR = join(SKILL_ROOT, "data/catalog/fixtures");
12
16
  const HARD_RULES_MD = join(SKILL_ROOT, "references/hard-rules.md");
13
- const DETECTOR_MJS = join(SCRIPTS_DIR, "detector.mjs");
17
+ const TOKENS_DIR = join(SKILL_ROOT, "data/design-tokens");
18
+ const COMBINATIONS_DIR = join(SKILL_ROOT, "data/combinations");
19
+ const GOLDENS_JSON = join(TOKENS_DIR, "goldens.json");
20
+ const DERIVE_PERSONALITY = join(SCRIPTS_DIR, "derive-personality.mjs");
21
+ const SPAWN_LIMIT = 8;
14
22
 
15
23
  const CSV_HEADER = ["id", "dimension", "statement", "numbers", "grade", "fire_points", "detector", "scope_note", "provenance", "fixture_pair"];
16
24
  const VALID_DIMENSIONS = new Set(["P1", "P2", "P3", "P4", "P5", "P6", "P7", "S1", "S2", "S3", "S4", "B1", "B2", "B3", "B4", "B5"]);
@@ -227,8 +235,8 @@ function checkFixturePresence(catalog) {
227
235
  return { name: "fixture-pair presence", pass: errors.length === 0, detail: `${ok}/${total} pairs present`, errors };
228
236
  }
229
237
 
230
- // Run detector.mjs on one fixture file and return the set of anchor ids it reports.
231
- function runDetector(file) {
238
+ // Run the check engine (lib/checks.mjs) on one fixture file and return the set of anchor ids it reports.
239
+ function runEngine(file) {
232
240
  // Scan the leading comment block for a `// detector-args:` directive (the mandatory file-path
233
241
  // comment occupies line 1, so the directive cannot be line 0). Relative paths in it (e.g. a
234
242
  // --manifest sibling) resolve against FIXTURES_DIR, matching each fixture's documented verify command.
@@ -239,46 +247,41 @@ function runDetector(file) {
239
247
  const argMatch = /^\/\/\s*detector-args:\s*(.+)$/.exec(t);
240
248
  if (argMatch) { extra = argMatch[1].trim().split(/\s+/); break; }
241
249
  }
242
- const res = spawnSync("node", [DETECTOR_MJS, file, "--json", ...extra], { encoding: "utf8", timeout: 30000, cwd: FIXTURES_DIR });
243
- const out = `${res.stdout || ""}\n${res.stderr || ""}`;
244
- const ids = new Set();
250
+ let manifestPath = null;
251
+ const extraFiles = [];
252
+ for (let i = 0; i < extra.length; i++) {
253
+ const a = extra[i];
254
+ if (a === "--manifest") manifestPath = resolve(FIXTURES_DIR, extra[++i] || "");
255
+ else if (a.startsWith("--manifest=")) manifestPath = resolve(FIXTURES_DIR, a.slice("--manifest=".length));
256
+ else if (!a.startsWith("--")) extraFiles.push(resolve(FIXTURES_DIR, a));
257
+ }
245
258
  try {
246
- const parsed = JSON.parse(res.stdout || "null");
247
- const collect = (v) => {
248
- if (v == null) return;
249
- if (typeof v === "string") { const mm = /^(?:typ|col|spc|dep|int|mot|sta|cod|proc|inst|trust)-\d{2}$/.exec(v); if (mm) ids.add(v); return; }
250
- if (Array.isArray(v)) { v.forEach(collect); return; }
251
- if (typeof v === "object") Object.values(v).forEach(collect);
252
- };
253
- collect(parsed);
254
- } catch { /* fall back to scanning raw output below */ }
255
- let m;
256
- ANCHOR_TOKEN.lastIndex = 0;
257
- const bare = new RegExp("\\b(" + ANCHOR_PREFIXES + ")-(\\d{2})\\b", "g");
258
- while ((m = bare.exec(out)) !== null) ids.add(`${m[1]}-${m[2]}`);
259
- return { ids, spawnError: res.error ? String(res.error) : null };
259
+ const findings = runChecks([file, ...extraFiles], { manifestPath });
260
+ return { ids: new Set(findings.map((f) => f.rule)) };
261
+ } catch (e) {
262
+ return { ids: new Set(), error: String(e) };
263
+ }
260
264
  }
261
265
 
262
266
  // ── Check 4: contrastive validity — flag fires the rule, pass does not ──────────
263
- function checkContrastiveValidity(catalog) {
267
+ function checkContrastiveValidity(catalog, onlyId = null) {
264
268
  const errors = [];
265
- if (!existsSync(DETECTOR_MJS)) return { name: "contrastive fixture validity", pass: false, detail: "detector.mjs missing", errors: ["scripts/detector.mjs not found — cannot exercise fixtures"] };
266
269
  if (!existsSync(FIXTURES_DIR)) return { name: "contrastive fixture validity", pass: false, detail: "fixtures dir missing", errors: ["data/catalog/fixtures/ does not exist"] };
267
270
  const files = readdirSync(FIXTURES_DIR);
268
- const present = new Set(files);
269
271
  const findFile = (id, kind) => files.find((f) => f.startsWith(`${id}.${kind}.`));
270
272
  let checked = 0;
271
273
  for (const row of catalog.rows) {
272
274
  if (row.fixture_pair !== "yes") continue;
275
+ if (onlyId && row.id !== onlyId) continue;
273
276
  const flag = findFile(row.id, "flag");
274
277
  const pass = findFile(row.id, "pass");
275
278
  if (!flag || !pass) continue; // absence is check 3's job
276
279
  checked++;
277
- const flagRes = runDetector(join(FIXTURES_DIR, flag));
278
- if (flagRes.spawnError) { errors.push(`${row.id}: detector spawn failed on flag (${flagRes.spawnError})`); continue; }
280
+ const flagRes = runEngine(join(FIXTURES_DIR, flag));
281
+ if (flagRes.error) { errors.push(`${row.id}: check engine threw on flag (${flagRes.error})`); continue; }
279
282
  if (!flagRes.ids.has(row.id)) errors.push(`${row.id}: flag fixture did not report ${row.id} (reported: ${[...flagRes.ids].join(",") || "none"})`);
280
- const passRes = runDetector(join(FIXTURES_DIR, pass));
281
- if (passRes.spawnError) { errors.push(`${row.id}: detector spawn failed on pass (${passRes.spawnError})`); continue; }
283
+ const passRes = runEngine(join(FIXTURES_DIR, pass));
284
+ if (passRes.error) { errors.push(`${row.id}: check engine threw on pass (${passRes.error})`); continue; }
282
285
  if (passRes.ids.has(row.id)) errors.push(`${row.id}: pass fixture wrongly reported ${row.id}`);
283
286
  }
284
287
  return { name: "contrastive fixture validity", pass: errors.length === 0, detail: `${checked} pair(s) exercised`, errors };
@@ -356,14 +359,15 @@ function checkFileTree() {
356
359
  "references/hard-rules.md", "references/create.md", "references/improve.md", "references/introspect.md",
357
360
  "references/diagnosis.md", "references/structure.md", "references/personality.md", "references/interaction.md", "references/lifecycle.md",
358
361
  "data/catalog/rules.csv",
362
+ "data/catalog/fixtures/cod-02.manifest.tokens.json",
359
363
  "data/design-md/SCHEMA.md", "data/design-md/nurix.md",
360
364
  "data/operator-pack/surface-catalog.md", "data/operator-pack/fixtures.md", "data/operator-pack/trust-rubric.md",
361
365
  "data/operator-pack/exemplars/list-detail.tsx", "data/operator-pack/exemplars/dashboard.tsx",
362
366
  "data/operator-pack/exemplars/entity-detail.tsx", "data/operator-pack/exemplars/create-edit.tsx",
363
367
  "data/operator-pack/exemplars/log-stream.tsx", "data/operator-pack/exemplars/command-center.tsx",
364
- "scripts/detector.mjs", "scripts/phase-gate.mjs", "scripts/preship-sweep.mjs", "scripts/render-rung.mjs",
368
+ "scripts/lib/checks.mjs", "scripts/phase-gate.mjs", "scripts/preship-sweep.mjs", "scripts/render-rung.mjs",
365
369
  "scripts/scaffold.mjs", "scripts/derive-personality.mjs", "scripts/generate-fixtures.mjs",
366
- "scripts/self-integrity.mjs", "scripts/hook-config.json",
370
+ "scripts/self-integrity.mjs",
367
371
  ];
368
372
  for (const rel of files) if (!existsSync(join(SKILL_ROOT, rel))) errors.push(`missing ${rel}`);
369
373
 
@@ -377,6 +381,138 @@ function checkFileTree() {
377
381
  return { name: "file-tree presence", pass: errors.length === 0, detail: `${found}/${files.length} files present`, errors };
378
382
  }
379
383
 
384
+ // Every artifact directory under data/design-tokens/ (a directory holding design.tokens.json), sorted by slug.
385
+ function listArtifactSlugs() {
386
+ if (!existsSync(TOKENS_DIR)) return [];
387
+ return readdirSync(TOKENS_DIR, { withFileTypes: true })
388
+ .filter((d) => d.isDirectory() && existsSync(join(TOKENS_DIR, d.name, "design.tokens.json")))
389
+ .map((d) => d.name)
390
+ .sort();
391
+ }
392
+
393
+ // Parse goldens.json into its entry slugs (every key except "_meta"), or null when absent/unparseable.
394
+ function readGoldenSlugs() {
395
+ const text = readSafe(GOLDENS_JSON);
396
+ if (text === null) return null;
397
+ try { return Object.keys(JSON.parse(text)).filter((k) => k !== "_meta").sort(); }
398
+ catch { return null; }
399
+ }
400
+
401
+ // Run an async worker over items with at most `limit` in flight; results keep item order.
402
+ async function runPool(items, limit, worker) {
403
+ const results = new Array(items.length);
404
+ let next = 0;
405
+ const lane = async () => {
406
+ while (next < items.length) {
407
+ const i = next++;
408
+ results[i] = await worker(items[i], i);
409
+ }
410
+ };
411
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) || 1 }, lane));
412
+ return results;
413
+ }
414
+
415
+ // Spawn `node <args>` and resolve with { status, stdout, stderr } (never rejects; a spawn error surfaces as status -1).
416
+ function execNode(args) {
417
+ return new Promise((resolveExec) => {
418
+ const child = spawn(process.execPath, args, { stdio: ["ignore", "pipe", "pipe"] });
419
+ let stdout = "";
420
+ let stderr = "";
421
+ child.stdout.on("data", (d) => { stdout += d; });
422
+ child.stderr.on("data", (d) => { stderr += d; });
423
+ child.on("error", (e) => resolveExec({ status: -1, stdout, stderr: stderr + String(e) }));
424
+ child.on("close", (status) => resolveExec({ status, stdout, stderr }));
425
+ });
426
+ }
427
+
428
+ // First non-empty line of a child process's output, for one-line error reporting.
429
+ function firstLine(text) {
430
+ return (text || "").split("\n").find((l) => l.trim() !== "") || "(no output)";
431
+ }
432
+
433
+ // ── Check 7: token-era file tree + goldens<->artifact parity ───────────────────
434
+ function checkTokenFileTree() {
435
+ const errors = [];
436
+ const pins = [
437
+ "data/design-tokens/SCHEMA.md",
438
+ "data/design-tokens/goldens.json",
439
+ "scripts/convert-design-md.mjs",
440
+ "scripts/snapshot-goldens.mjs",
441
+ "scripts/lib/golden.mjs",
442
+ ];
443
+ for (const rel of pins) if (!existsSync(join(SKILL_ROOT, rel))) errors.push(`missing ${rel}`);
444
+ const pinsFound = pins.length - errors.length;
445
+
446
+ const diskSlugs = listArtifactSlugs();
447
+ const goldenSlugs = readGoldenSlugs();
448
+ if (goldenSlugs === null) {
449
+ errors.push("goldens.json missing or does not parse as JSON");
450
+ } else {
451
+ const goldenSet = new Set(goldenSlugs);
452
+ for (const slug of goldenSlugs) {
453
+ if (!existsSync(join(TOKENS_DIR, slug, "design.tokens.json")))
454
+ errors.push(`goldens.json entry "${slug}" has no data/design-tokens/${slug}/design.tokens.json on disk`);
455
+ else if (!existsSync(join(TOKENS_DIR, slug, "design.resolver.json")))
456
+ errors.push(`goldens.json entry "${slug}": data/design-tokens/${slug}/ is missing design.resolver.json`);
457
+ }
458
+ for (const slug of diskSlugs) {
459
+ if (!goldenSet.has(slug)) errors.push(`artifact dir data/design-tokens/${slug}/ has no goldens.json entry`);
460
+ else if (!existsSync(join(TOKENS_DIR, slug, "design.resolver.json")) && !errors.some((e) => e.includes(`"${slug}"`)))
461
+ errors.push(`artifact dir data/design-tokens/${slug}/ is missing design.resolver.json`);
462
+ }
463
+ }
464
+ const goldenCount = goldenSlugs === null ? 0 : goldenSlugs.length;
465
+ return { name: "token file-tree + goldens parity", pass: errors.length === 0, detail: errors.length ? `${errors.length} issue(s)` : `${pinsFound}/${pins.length} pins, goldens<->artifacts ${goldenCount}<->${diskSlugs.length}`, errors };
466
+ }
467
+
468
+ // ── Check 8: every design-tokens artifact passes derive-personality --check ────
469
+ async function checkArtifactValidity() {
470
+ const slugs = listArtifactSlugs();
471
+ if (slugs.length === 0) return { name: "artifact validity (--check)", pass: false, detail: "no artifact dirs found", errors: [`no design.tokens.json dirs under ${TOKENS_DIR}`] };
472
+ const runs = await runPool(slugs, SPAWN_LIMIT, (slug) => execNode([DERIVE_PERSONALITY, join(TOKENS_DIR, slug), "--check"]));
473
+ const errors = [];
474
+ runs.forEach((res, i) => {
475
+ if (res.status !== 0) errors.push(`${slugs[i]}: --check exited ${res.status}: ${firstLine(res.stderr || res.stdout)}`);
476
+ });
477
+ return { name: "artifact validity (--check)", pass: errors.length === 0, detail: `${slugs.length - errors.length}/${slugs.length} artifacts pass --check`, errors };
478
+ }
479
+
480
+ // ── Check 9: emitted CSS hashes match goldens.json (slow; gated behind --goldens) ──
481
+ async function checkGoldenMatch(enabled) {
482
+ if (!enabled) return { name: "golden emission match", pass: true, skip: true, detail: "skipped (slow) — run with --goldens to verify emitted hashes against goldens.json", errors: [] };
483
+ const goldenSlugs = readGoldenSlugs();
484
+ if (goldenSlugs === null) return { name: "golden emission match", pass: false, detail: "goldens.json missing/unparseable", errors: ["goldens.json missing or does not parse as JSON"] };
485
+ const goldens = JSON.parse(readSafe(GOLDENS_JSON));
486
+ const emitRoot = mkdtempSync(join(tmpdir(), "design-self-integrity-"));
487
+ let matched = 0;
488
+ const errors = [];
489
+ try {
490
+ // Each worker returns its slug's error list; flattening after the pool keeps report order deterministic.
491
+ const perSlug = await runPool(goldenSlugs, SPAWN_LIMIT, async (slug) => {
492
+ const slugErrors = [];
493
+ const outdir = join(emitRoot, slug);
494
+ const res = await execNode([DERIVE_PERSONALITY, join(TOKENS_DIR, slug), "--emit", outdir]);
495
+ if (res.status !== 0) {
496
+ slugErrors.push(`${slug}: --emit exited ${res.status}: ${firstLine(res.stderr || res.stdout)}`);
497
+ return slugErrors;
498
+ }
499
+ for (const [key, fileName] of [["personality", "personality.css"], ["globals", "globals.css"]]) {
500
+ const css = readSafe(join(outdir, "src/styles", fileName));
501
+ if (css === null) { slugErrors.push(`${slug}: emit produced no src/styles/${fileName}`); continue; }
502
+ const actual = goldenHash(css);
503
+ const expected = goldens[slug] && goldens[slug][key];
504
+ if (actual === expected) matched++;
505
+ else slugErrors.push(`${slug}: ${fileName} hash ${actual} != golden ${expected ?? "(missing key)"}`);
506
+ }
507
+ return slugErrors;
508
+ });
509
+ for (const slugErrors of perSlug) errors.push(...slugErrors);
510
+ } finally {
511
+ rmSync(emitRoot, { recursive: true, force: true });
512
+ }
513
+ return { name: "golden emission match", pass: errors.length === 0, detail: `${matched}/${goldenSlugs.length * 2} hashes match goldens.json`, errors };
514
+ }
515
+
380
516
  // Derive the ground-truth counts from the catalog itself.
381
517
  function deriveGround(catalog) {
382
518
  return {
@@ -386,14 +522,42 @@ function deriveGround(catalog) {
386
522
  };
387
523
  }
388
524
 
525
+ // Run one rule's contrastive fixture pair through the check engine and exit 0/1 on its result.
526
+ function runSingleFixture(catalog, ruleId) {
527
+ const row = catalog.rows.find((r) => r.id === ruleId);
528
+ if (!row) { console.error(`--fixture: no rules.csv row with id "${ruleId}"`); process.exit(2); }
529
+ if (row.fixture_pair !== "yes") { console.error(`--fixture: ${ruleId} has fixture_pair=${row.fixture_pair}; no contrastive pair to run`); process.exit(2); }
530
+ const r = checkContrastiveValidity(catalog, ruleId);
531
+ if (r.errors.length === 0 && r.detail.startsWith("0 ")) {
532
+ console.error(`--fixture: ${ruleId}.flag.* / ${ruleId}.pass.* not found in data/catalog/fixtures/`);
533
+ process.exit(2);
534
+ }
535
+ if (r.pass) { console.log(`PASS fixture pair ${ruleId} — flag reports ${ruleId}, pass reports zero.`); process.exit(0); }
536
+ console.error(`FAIL fixture pair ${ruleId}`);
537
+ for (const e of r.errors) console.error(` - ${e}`);
538
+ process.exit(1);
539
+ }
540
+
389
541
  // Run every check, print the summary table, and exit non-zero on any failure.
390
- function main() {
542
+ async function main() {
543
+ const args = process.argv.slice(2);
544
+ let fixtureId = null;
545
+ let runGoldens = false;
546
+ for (let i = 0; i < args.length; i++) {
547
+ if (args[i] === "--fixture") fixtureId = args[++i] || null;
548
+ else if (args[i].startsWith("--fixture=")) fixtureId = args[i].slice("--fixture=".length);
549
+ else if (args[i] === "--goldens") runGoldens = true;
550
+ }
551
+
391
552
  const results = [];
392
553
  let catalog = null;
393
554
  try { catalog = loadCatalog(); } catch (e) {
394
555
  console.error("FATAL: " + e.message);
395
556
  process.exit(2);
396
557
  }
558
+
559
+ if (fixtureId) runSingleFixture(catalog, fixtureId);
560
+
397
561
  const ground = deriveGround(catalog);
398
562
 
399
563
  const runners = [
@@ -403,9 +567,13 @@ function main() {
403
567
  () => checkContrastiveValidity(catalog),
404
568
  () => checkStatedCounts(catalog, ground),
405
569
  () => checkFileTree(),
570
+ () => checkTokenFileTree(),
571
+ () => checkArtifactValidity(),
572
+ () => checkGoldenMatch(runGoldens),
573
+ () => validateCombinations(COMBINATIONS_DIR),
406
574
  ];
407
575
  for (const run of runners) {
408
- try { results.push(run()); }
576
+ try { results.push(await run()); }
409
577
  catch (e) { results.push({ name: run.name || "check", pass: false, detail: "threw", errors: [e.stack || String(e)] }); }
410
578
  }
411
579
 
@@ -420,7 +588,7 @@ function main() {
420
588
  console.log("self-integrity — design skill");
421
589
  console.log("=".repeat(60));
422
590
  const pad = (s, n) => (s + " ".repeat(n)).slice(0, n);
423
- for (const r of results) console.log(`${r.pass ? "PASS" : "FAIL"} ${pad(r.name, 34)} ${r.detail}`);
591
+ for (const r of results) console.log(`${r.skip ? "SKIP" : r.pass ? "PASS" : "FAIL"} ${pad(r.name, 34)} ${r.detail}`);
424
592
  console.log("=".repeat(60));
425
593
 
426
594
  const failed = results.filter((r) => !r.pass);
@@ -429,4 +597,4 @@ function main() {
429
597
  process.exit(0);
430
598
  }
431
599
 
432
- main();
600
+ main().catch((e) => { console.error("FATAL: " + (e.stack || String(e))); process.exit(2); });
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ // skills/design/scripts/snapshot-goldens.mjs — snapshot golden hashes of the CSS emitted for every design-tokens artifact (data/design-tokens/<slug>/) into data/design-tokens/goldens.json, the byte-match regression oracle for the emission pipeline.
3
+
4
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { fileURLToPath } from "node:url";
8
+ import { spawnSync } from "node:child_process";
9
+ import { goldenHash } from "./lib/golden.mjs";
10
+
11
+ const scriptsDir = dirname(fileURLToPath(import.meta.url));
12
+ const skillDir = dirname(scriptsDir);
13
+ const derivePersonalityScript = join(scriptsDir, "derive-personality.mjs");
14
+ const tokensDir = join(skillDir, "data", "design-tokens");
15
+ const goldensJsonPath = join(tokensDir, "goldens.json");
16
+
17
+ // Where full emitted outputs are kept (one subdir per slug) — GOLDENS_EMIT_ROOT to pin it, else a fresh temp dir.
18
+ const emitRoot = process.env.GOLDENS_EMIT_ROOT || mkdtempSync(join(tmpdir(), "design-goldens-"));
19
+ mkdirSync(emitRoot, { recursive: true });
20
+
21
+ // Every artifact directory under data/design-tokens/ (a directory holding design.tokens.json).
22
+ const entries = readdirSync(tokensDir, { withFileTypes: true })
23
+ .filter((d) => d.isDirectory() && existsSync(join(tokensDir, d.name, "design.tokens.json")))
24
+ .map((d) => ({ slug: d.name, path: join(tokensDir, d.name) }))
25
+ .sort((a, b) => (a.slug < b.slug ? -1 : 1));
26
+
27
+ const goldens = {};
28
+ const failures = [];
29
+
30
+ for (const { slug, path } of entries) {
31
+ const outdir = join(emitRoot, slug);
32
+ const run = spawnSync(process.execPath, [derivePersonalityScript, path, "--emit", outdir], { encoding: "utf8" });
33
+ if (run.status !== 0) {
34
+ failures.push({ slug, error: (run.stderr || run.stdout || `exit status ${run.status}`).trim() });
35
+ console.log(`FAIL ${slug}`);
36
+ continue;
37
+ }
38
+ try {
39
+ const personalityCss = readFileSync(join(outdir, "src", "styles", "personality.css"), "utf8");
40
+ const globalsCss = readFileSync(join(outdir, "src", "styles", "globals.css"), "utf8");
41
+ goldens[slug] = { personality: goldenHash(personalityCss), globals: goldenHash(globalsCss) };
42
+ console.log(`OK ${slug}`);
43
+ } catch (err) {
44
+ failures.push({ slug, error: String((err && err.message) || err) });
45
+ console.log(`FAIL ${slug}`);
46
+ }
47
+ }
48
+
49
+ // Assemble the sorted-key document; "_meta" sorts first (ASCII "_" < "a").
50
+ const document = {
51
+ _meta: {
52
+ description: "Post-carrier golden hashes of the CSS emitted by derive-personality.mjs --emit for every design-tokens artifact (data/design-tokens/<slug>/), dark-inclusive: an artifact shipping design.tokens.dark.json hashes its emitted [data-theme=\"dark\"] block too. Each entry records the sha256 of the comment-stripped personality.css and globals.css (see scripts/lib/golden.mjs). The byte-match regression oracle for the emission pipeline.",
53
+ generator: "scripts/snapshot-goldens.mjs",
54
+ strip: "css-comments",
55
+ },
56
+ };
57
+ for (const slug of Object.keys(goldens).sort()) document[slug] = goldens[slug];
58
+
59
+ mkdirSync(dirname(goldensJsonPath), { recursive: true });
60
+ writeFileSync(goldensJsonPath, JSON.stringify(document, null, 2) + "\n");
61
+
62
+ console.log(`\n${entries.length} entries: ${Object.keys(goldens).length} OK, ${failures.length} FAIL`);
63
+ console.log(`goldens: ${goldensJsonPath}`);
64
+ console.log(`emitted outputs: ${emitRoot}`);
65
+ if (failures.length > 0) {
66
+ for (const f of failures) console.error(`\nFAIL ${f.slug}:\n${f.error}`);
67
+ process.exit(1);
68
+ }