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