@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.
- package/README.md +1 -1
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +3 -3
- package/data/harnesses/code/agents/brainstorm.md +1 -1
- package/data/harnesses/code/agents/maintenance.md +8 -7
- 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 +4 -4
- package/data/harnesses/code/skills/brainstorm/SKILL.md +5 -3
- package/data/harnesses/code/skills/brainstorm/exit-artifacts.md +15 -11
- package/data/harnesses/code/skills/brainstorm/templates/source-of-truth.md +5 -5
- 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 +306 -52
- package/data/harnesses/etna/CLAUDE.md +3 -3
- package/data/harnesses/etna/agents/brainstorm.md +1 -1
- package/data/harnesses/etna/agents/maintenance.md +8 -7
- 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 +4 -4
- package/data/harnesses/etna/skills/brainstorm/SKILL.md +5 -3
- package/data/harnesses/etna/skills/brainstorm/exit-artifacts.md +15 -11
- package/data/harnesses/etna/skills/brainstorm/templates/source-of-truth.md +5 -5
- 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 +306 -52
- package/data/harnesses/mobile/CLAUDE.md +3 -3
- package/data/harnesses/mobile/agents/brainstorm.md +1 -1
- package/data/harnesses/mobile/agents/maintenance.md +8 -7
- 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 +4 -4
- package/data/harnesses/mobile/skills/brainstorm/SKILL.md +5 -3
- package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +15 -11
- package/data/harnesses/mobile/skills/brainstorm/templates/source-of-truth.md +5 -5
- 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 +306 -52
- package/package.json +1 -1
- package/data/harnesses/code/skills/design/scripts/hook-config.json +0 -11
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// skills/design/scripts/validate-combinations.mjs — schema validator for the combination datasets (data/combinations/): file presence, byte-exact headers, sequential ids, tier/density/surface vocabularies, symbol-glyph ban, palette hex + WCAG contrast floors, chart token discipline, lucide/font registry membership, and manifest row-count parity. Importable (validateCombinations(dir)) and runnable as a CLI.
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const SKILL_ROOT = join(SCRIPTS_DIR, "..");
|
|
8
|
+
const DEFAULT_DIR = join(SKILL_ROOT, "data/combinations");
|
|
9
|
+
|
|
10
|
+
const CSV_FILES = ["typography.csv", "palettes.csv", "styles.csv", "product-router.csv", "charts.csv", "icons.csv", "landing.csv"];
|
|
11
|
+
const REQUIRED_FILES = [...CSV_FILES, "manifest.json", "lucide-names.json", "fonts-known.json", "Readme.md"];
|
|
12
|
+
|
|
13
|
+
// Byte-exact, order-exact header contract per dataset.
|
|
14
|
+
const HEADERS = {
|
|
15
|
+
"typography.csv": ["id", "pairing", "tier", "heading_family", "body_family", "mono_family", "contrast", "mood", "best_for", "heading_weights", "body_weights", "google_fonts_css", "notes"],
|
|
16
|
+
"palettes.csv": ["id", "palette", "tier", "product_affinity", "mood", "background", "foreground", "card", "card-foreground", "popover", "popover-foreground", "muted", "muted-foreground", "border", "input", "ring", "primary", "primary-foreground", "secondary", "secondary-foreground", "accent", "accent-foreground", "destructive", "destructive-foreground", "success", "warning", "chart-1", "chart-2", "chart-3", "chart-4", "chart-5", "sidebar", "sidebar-foreground", "sidebar-primary", "sidebar-primary-foreground", "sidebar-accent", "sidebar-accent-foreground", "sidebar-border", "sidebar-ring", "notes"],
|
|
17
|
+
"styles.csv": ["id", "movement", "tier", "era", "keywords", "description", "palette_stance", "type_stance", "shape_stance", "motion_stance", "density_affinity", "best_for", "avoid_for", "token_leanings", "operator_note"],
|
|
18
|
+
"product-router.csv": ["id", "product_type", "tier", "keywords", "style_priority", "palette_stance", "type_stance", "density_tier", "key_surfaces", "decision_rules", "anti_patterns", "notes"],
|
|
19
|
+
"charts.csv": ["id", "data_shape", "tier", "keywords", "best_chart", "alternatives", "when", "when_not", "volume_guidance", "series_tokens", "a11y_notes", "fallback", "interaction"],
|
|
20
|
+
"icons.csv": ["id", "intent", "tier", "keywords", "lucide_name", "import_name", "usage", "notes"],
|
|
21
|
+
"landing.csv": ["id", "pattern", "tier", "keywords", "section_order", "cta_placement", "color_strategy", "effects", "conversion_notes"],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const VALID_TIERS = new Set(["operator-active", "dormant-marketing"]);
|
|
25
|
+
const VALID_DENSITIES = new Set(["spacious", "standard", "dense"]);
|
|
26
|
+
const VALID_TYPE_CONTRASTS = new Set(["serif+sans", "sans+sans", "sans+serif", "serif+serif", "display+sans", "mono+sans"]);
|
|
27
|
+
const VALID_KEY_SURFACES = new Set(["list-detail", "dashboard", "entity-detail", "create-edit", "log-stream", "command-center"]);
|
|
28
|
+
|
|
29
|
+
const UPPER_HEX = /^#[0-9A-F]{6}$/;
|
|
30
|
+
const RAW_HEX_ANYWHERE = /#[0-9a-fA-F]{3,8}\b/;
|
|
31
|
+
// Box drawing, geometric shapes, misc symbols, dingbats, misc symbols-and-arrows, VS16, and emoji — covers ✓ ⚡ ◐ ☐ and kin.
|
|
32
|
+
const BANNED_GLYPHS = new RegExp("[\\u2500-\\u25FF\\u2600-\\u27BF\\u2B00-\\u2BFF\\uFE0F]|\\p{Extended_Pictographic}", "u");
|
|
33
|
+
|
|
34
|
+
// Foreground-on-base pairs that must clear WCAG AA text contrast (>= 4.5:1).
|
|
35
|
+
const AA_TEXT_PAIRS = [
|
|
36
|
+
["background", "foreground"], ["card", "card-foreground"], ["popover", "popover-foreground"],
|
|
37
|
+
["muted", "muted-foreground"], ["primary", "primary-foreground"], ["secondary", "secondary-foreground"],
|
|
38
|
+
["accent", "accent-foreground"], ["destructive", "destructive-foreground"],
|
|
39
|
+
["sidebar", "sidebar-foreground"], ["sidebar-primary", "sidebar-primary-foreground"],
|
|
40
|
+
["sidebar-accent", "sidebar-accent-foreground"],
|
|
41
|
+
];
|
|
42
|
+
// Status/series colors that must clear >= 3:1 against background.
|
|
43
|
+
const GRAPHIC_VS_BACKGROUND = ["success", "warning", "chart-1", "chart-2", "chart-3", "chart-4", "chart-5"];
|
|
44
|
+
// The 34 color cells: everything between background and sidebar-ring inclusive in the palettes header.
|
|
45
|
+
const PALETTE_COLOR_COLUMNS = HEADERS["palettes.csv"].slice(
|
|
46
|
+
HEADERS["palettes.csv"].indexOf("background"),
|
|
47
|
+
HEADERS["palettes.csv"].indexOf("sidebar-ring") + 1
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Read a UTF-8 file, returning null when it does not exist.
|
|
51
|
+
function readSafe(path) {
|
|
52
|
+
return existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Parse RFC4180-style CSV text into an array of field arrays (quotes + doubled-quote escapes honored).
|
|
56
|
+
function parseCsv(text) {
|
|
57
|
+
const rows = [];
|
|
58
|
+
let field = "";
|
|
59
|
+
let record = [];
|
|
60
|
+
let inQuotes = false;
|
|
61
|
+
for (let i = 0; i < text.length; i++) {
|
|
62
|
+
const ch = text[i];
|
|
63
|
+
if (inQuotes) {
|
|
64
|
+
if (ch === '"') {
|
|
65
|
+
if (text[i + 1] === '"') { field += '"'; i++; }
|
|
66
|
+
else inQuotes = false;
|
|
67
|
+
} else field += ch;
|
|
68
|
+
} else if (ch === '"') inQuotes = true;
|
|
69
|
+
else if (ch === ",") { record.push(field); field = ""; }
|
|
70
|
+
else if (ch === "\n") { record.push(field); rows.push(record); record = []; field = ""; }
|
|
71
|
+
else if (ch === "\r") { /* skip */ }
|
|
72
|
+
else field += ch;
|
|
73
|
+
}
|
|
74
|
+
if (field.length > 0 || record.length > 0) { record.push(field); rows.push(record); }
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// WCAG 2.x relative luminance of an uppercase #RRGGBB hex.
|
|
79
|
+
function relativeLuminance(hex) {
|
|
80
|
+
const chan = (v) => {
|
|
81
|
+
const c = v / 255;
|
|
82
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
83
|
+
};
|
|
84
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
85
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
86
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
87
|
+
return 0.2126 * chan(r) + 0.7152 * chan(g) + 0.0722 * chan(b);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// WCAG contrast ratio between two #RRGGBB hex colors.
|
|
91
|
+
function contrastRatio(hexA, hexB) {
|
|
92
|
+
const la = relativeLuminance(hexA);
|
|
93
|
+
const lb = relativeLuminance(hexB);
|
|
94
|
+
const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
|
|
95
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Load one CSV into { header, rows: [{...byExpectedColumn, _cells, _line}] }, or null (recording an error) when absent.
|
|
99
|
+
function loadCsv(dir, file, errors) {
|
|
100
|
+
const text = readSafe(join(dir, file));
|
|
101
|
+
if (text === null) return null;
|
|
102
|
+
const raw = parseCsv(text).filter((r) => r.length > 1 || (r.length === 1 && r[0].trim() !== ""));
|
|
103
|
+
if (raw.length === 0) { errors.push(`${file}: file is empty`); return null; }
|
|
104
|
+
const expected = HEADERS[file];
|
|
105
|
+
const rows = raw.slice(1).map((cells, i) => {
|
|
106
|
+
const obj = {};
|
|
107
|
+
expected.forEach((k, idx) => { obj[k] = cells[idx] !== undefined ? cells[idx] : ""; });
|
|
108
|
+
obj._cells = cells;
|
|
109
|
+
obj._line = i + 2; // 1-based, after the header line
|
|
110
|
+
return obj;
|
|
111
|
+
});
|
|
112
|
+
return { header: raw[0], rows };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Generic invariants shared by every dataset: exact header, column counts, sequential ids, unique names, valid tier, no symbol glyphs.
|
|
116
|
+
function checkGeneric(file, csv, errors) {
|
|
117
|
+
const expected = HEADERS[file];
|
|
118
|
+
if (csv.header.length !== expected.length || expected.some((h, i) => csv.header[i] !== h))
|
|
119
|
+
errors.push(`${file}: header mismatch: expected [${expected.join(",")}] got [${csv.header.join(",")}]`);
|
|
120
|
+
|
|
121
|
+
const nameCol = expected[1];
|
|
122
|
+
const seenNames = new Map();
|
|
123
|
+
csv.rows.forEach((row, i) => {
|
|
124
|
+
if (row._cells.length !== expected.length)
|
|
125
|
+
errors.push(`${file} row ${row._line}: has ${row._cells.length} columns, expected ${expected.length}`);
|
|
126
|
+
|
|
127
|
+
if (!/^\d+$/.test(row.id) || parseInt(row.id, 10) !== i + 1)
|
|
128
|
+
errors.push(`${file} row ${row._line}: id "${row.id}" breaks 1..N sequence (expected ${i + 1})`);
|
|
129
|
+
|
|
130
|
+
const name = row[nameCol];
|
|
131
|
+
if (seenNames.has(name)) errors.push(`${file} row ${row._line}: duplicate ${nameCol} "${name}" (first at row ${seenNames.get(name)})`);
|
|
132
|
+
else seenNames.set(name, row._line);
|
|
133
|
+
|
|
134
|
+
if (!VALID_TIERS.has(row.tier))
|
|
135
|
+
errors.push(`${file} row ${row._line}: invalid tier "${row.tier}" (want operator-active|dormant-marketing)`);
|
|
136
|
+
|
|
137
|
+
for (const cell of row._cells) {
|
|
138
|
+
const m = BANNED_GLYPHS.exec(cell);
|
|
139
|
+
if (m) { errors.push(`${file} row ${row._line}: cell contains banned symbol glyph "${m[0]}"`); break; }
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// palettes.csv: uppercase 6-digit hex in all 34 color cells + WCAG contrast floors.
|
|
145
|
+
function checkPalettes(csv, errors) {
|
|
146
|
+
for (const row of csv.rows) {
|
|
147
|
+
const valid = {};
|
|
148
|
+
for (const col of PALETTE_COLOR_COLUMNS) {
|
|
149
|
+
const v = row[col];
|
|
150
|
+
if (!UPPER_HEX.test(v)) errors.push(`palettes.csv row ${row._line} (${row.palette}): ${col} "${v}" is not uppercase #RRGGBB`);
|
|
151
|
+
else valid[col] = v;
|
|
152
|
+
}
|
|
153
|
+
for (const [base, fg] of AA_TEXT_PAIRS) {
|
|
154
|
+
if (!valid[base] || !valid[fg]) continue;
|
|
155
|
+
const ratio = contrastRatio(valid[base], valid[fg]);
|
|
156
|
+
if (ratio < 4.5) errors.push(`palettes.csv row ${row._line} (${row.palette}): ${fg} on ${base} contrast ${ratio.toFixed(2)}:1 < 4.5:1`);
|
|
157
|
+
}
|
|
158
|
+
for (const col of GRAPHIC_VS_BACKGROUND) {
|
|
159
|
+
if (!valid[col] || !valid.background) continue;
|
|
160
|
+
const ratio = contrastRatio(valid[col], valid.background);
|
|
161
|
+
if (ratio < 3) errors.push(`palettes.csv row ${row._line} (${row.palette}): ${col} vs background contrast ${ratio.toFixed(2)}:1 < 3:1`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// charts.csv: operator-active only, --chart-N discipline in series_tokens, N in 1..5 everywhere, no raw hex.
|
|
167
|
+
function checkCharts(csv, errors) {
|
|
168
|
+
for (const row of csv.rows) {
|
|
169
|
+
if (row.tier !== "operator-active")
|
|
170
|
+
errors.push(`charts.csv row ${row._line} (${row.data_shape}): tier must be operator-active (got "${row.tier}")`);
|
|
171
|
+
if (!/--chart-\d/.test(row.series_tokens))
|
|
172
|
+
errors.push(`charts.csv row ${row._line} (${row.data_shape}): series_tokens mentions no --chart-N token`);
|
|
173
|
+
if (RAW_HEX_ANYWHERE.test(row.series_tokens))
|
|
174
|
+
errors.push(`charts.csv row ${row._line} (${row.data_shape}): series_tokens contains raw hex "${RAW_HEX_ANYWHERE.exec(row.series_tokens)[0]}"`);
|
|
175
|
+
for (const cell of row._cells) {
|
|
176
|
+
for (const m of cell.matchAll(/--chart-(\d+)/g)) {
|
|
177
|
+
const n = parseInt(m[1], 10);
|
|
178
|
+
if (n < 1 || n > 5) errors.push(`charts.csv row ${row._line} (${row.data_shape}): --chart-${m[1]} out of range 1..5`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// icons.csv rows against the lucide-names.json registry (kebab -> Pascal).
|
|
185
|
+
function checkIcons(csv, dir, errors) {
|
|
186
|
+
const text = readSafe(join(dir, "lucide-names.json"));
|
|
187
|
+
if (text === null) return; // presence already reported
|
|
188
|
+
let reg;
|
|
189
|
+
try { reg = JSON.parse(text); } catch { errors.push("lucide-names.json does not parse as JSON"); return; }
|
|
190
|
+
for (const key of ["lucide_react_version", "fetched", "source", "icons"])
|
|
191
|
+
if (!(key in reg)) errors.push(`lucide-names.json: missing key "${key}"`);
|
|
192
|
+
if (typeof reg.icons !== "object" || reg.icons === null) { errors.push("lucide-names.json: \"icons\" is not an object"); return; }
|
|
193
|
+
if (!csv) return;
|
|
194
|
+
for (const row of csv.rows) {
|
|
195
|
+
if (!(row.lucide_name in reg.icons))
|
|
196
|
+
errors.push(`icons.csv row ${row._line} (${row.intent}): lucide_name "${row.lucide_name}" not in lucide-names.json`);
|
|
197
|
+
else if (row.import_name !== reg.icons[row.lucide_name])
|
|
198
|
+
errors.push(`icons.csv row ${row._line} (${row.intent}): import_name "${row.import_name}" != registry "${reg.icons[row.lucide_name]}"`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// typography.csv rows against the fonts-known.json registry (family -> known weights) + contrast vocabulary.
|
|
203
|
+
function checkTypography(csv, dir, errors) {
|
|
204
|
+
const text = readSafe(join(dir, "fonts-known.json"));
|
|
205
|
+
if (text === null) return; // presence already reported
|
|
206
|
+
let reg;
|
|
207
|
+
try { reg = JSON.parse(text); } catch { errors.push("fonts-known.json does not parse as JSON"); return; }
|
|
208
|
+
for (const key of ["fetched", "source", "families"])
|
|
209
|
+
if (!(key in reg)) errors.push(`fonts-known.json: missing key "${key}"`);
|
|
210
|
+
if (typeof reg.families !== "object" || reg.families === null) { errors.push("fonts-known.json: \"families\" is not an object"); return; }
|
|
211
|
+
if (!csv) return;
|
|
212
|
+
for (const row of csv.rows) {
|
|
213
|
+
for (const col of ["heading_family", "body_family", "mono_family"])
|
|
214
|
+
if (!(row[col] in reg.families))
|
|
215
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): ${col} "${row[col]}" not in fonts-known.json`);
|
|
216
|
+
for (const [weightsCol, familyCol] of [["heading_weights", "heading_family"], ["body_weights", "body_family"]]) {
|
|
217
|
+
const known = reg.families[row[familyCol]];
|
|
218
|
+
if (!Array.isArray(known)) continue; // family membership error already reported
|
|
219
|
+
for (const tok of row[weightsCol].split(";").map((t) => t.trim()).filter((t) => t !== "")) {
|
|
220
|
+
if (!/^\d+$/.test(tok)) errors.push(`typography.csv row ${row._line} (${row.pairing}): ${weightsCol} entry "${tok}" is not an integer`);
|
|
221
|
+
else if (!known.includes(parseInt(tok, 10)))
|
|
222
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): weight ${tok} not a known weight of ${row[familyCol]} (known: ${known.join(",")})`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!VALID_TYPE_CONTRASTS.has(row.contrast))
|
|
226
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): invalid contrast "${row.contrast}"`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// styles.csv / product-router.csv density vocabulary + product-router key_surfaces vocabulary.
|
|
231
|
+
function checkDensityAndSurfaces(file, csv, errors) {
|
|
232
|
+
const densityCol = file === "styles.csv" ? "density_affinity" : "density_tier";
|
|
233
|
+
const nameCol = HEADERS[file][1];
|
|
234
|
+
for (const row of csv.rows) {
|
|
235
|
+
if (!VALID_DENSITIES.has(row[densityCol]))
|
|
236
|
+
errors.push(`${file} row ${row._line} (${row[nameCol]}): invalid ${densityCol} "${row[densityCol]}" (want spacious|standard|dense)`);
|
|
237
|
+
if (file === "product-router.csv" && row.tier === "operator-active") {
|
|
238
|
+
for (const surface of row.key_surfaces.split(",").map((s) => s.trim()).filter((s) => s !== ""))
|
|
239
|
+
if (!VALID_KEY_SURFACES.has(surface))
|
|
240
|
+
errors.push(`product-router.csv row ${row._line} (${row.product_type}): unknown key_surface "${surface}"`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// landing.csv: dormant-marketing on every row.
|
|
246
|
+
function checkLanding(csv, errors) {
|
|
247
|
+
for (const row of csv.rows)
|
|
248
|
+
if (row.tier !== "dormant-marketing")
|
|
249
|
+
errors.push(`landing.csv row ${row._line} (${row.pattern}): tier must be dormant-marketing (got "${row.tier}")`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// manifest.json: datasets entry per CSV with the required keys and a rows.total matching the actual row count.
|
|
253
|
+
function checkManifest(dir, csvs, errors) {
|
|
254
|
+
const text = readSafe(join(dir, "manifest.json"));
|
|
255
|
+
if (text === null) return; // presence already reported
|
|
256
|
+
let manifest;
|
|
257
|
+
try { manifest = JSON.parse(text); } catch { errors.push("manifest.json does not parse as JSON"); return; }
|
|
258
|
+
if (typeof manifest.datasets !== "object" || manifest.datasets === null) { errors.push("manifest.json: missing \"datasets\" object"); return; }
|
|
259
|
+
for (const file of CSV_FILES) {
|
|
260
|
+
const entry = manifest.datasets[file] ?? manifest.datasets[file.replace(/\.csv$/, "")];
|
|
261
|
+
if (entry === undefined) { errors.push(`manifest.json: datasets has no entry for ${file}`); continue; }
|
|
262
|
+
for (const key of ["seed", "research", "quality_bar", "rows", "refresh_trigger"])
|
|
263
|
+
if (!(key in entry)) errors.push(`manifest.json: datasets entry for ${file} missing key "${key}"`);
|
|
264
|
+
const total = entry.rows && typeof entry.rows === "object" ? entry.rows.total : undefined;
|
|
265
|
+
if (typeof total !== "number") errors.push(`manifest.json: datasets entry for ${file} has no numeric rows.total`);
|
|
266
|
+
else if (csvs[file] && total !== csvs[file].rows.length)
|
|
267
|
+
errors.push(`manifest.json: ${file} rows.total=${total} but the CSV has ${csvs[file].rows.length} data rows`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Validate every combination dataset in `dir`; returns a self-integrity-shaped result.
|
|
272
|
+
export function validateCombinations(dir) {
|
|
273
|
+
const name = "combination-data validation";
|
|
274
|
+
if (!existsSync(dir))
|
|
275
|
+
return { name, pass: false, detail: "combinations dir missing", errors: [`${dir} does not exist`] };
|
|
276
|
+
|
|
277
|
+
const errors = [];
|
|
278
|
+
for (const file of REQUIRED_FILES)
|
|
279
|
+
if (!existsSync(join(dir, file))) errors.push(`missing ${file}`);
|
|
280
|
+
|
|
281
|
+
const csvs = {};
|
|
282
|
+
for (const file of CSV_FILES) {
|
|
283
|
+
const csv = loadCsv(dir, file, errors);
|
|
284
|
+
csvs[file] = csv;
|
|
285
|
+
if (csv) checkGeneric(file, csv, errors);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (csvs["palettes.csv"]) checkPalettes(csvs["palettes.csv"], errors);
|
|
289
|
+
if (csvs["charts.csv"]) checkCharts(csvs["charts.csv"], errors);
|
|
290
|
+
checkIcons(csvs["icons.csv"], dir, errors);
|
|
291
|
+
checkTypography(csvs["typography.csv"], dir, errors);
|
|
292
|
+
if (csvs["styles.csv"]) checkDensityAndSurfaces("styles.csv", csvs["styles.csv"], errors);
|
|
293
|
+
if (csvs["product-router.csv"]) checkDensityAndSurfaces("product-router.csv", csvs["product-router.csv"], errors);
|
|
294
|
+
if (csvs["landing.csv"]) checkLanding(csvs["landing.csv"], errors);
|
|
295
|
+
checkManifest(dir, csvs, errors);
|
|
296
|
+
|
|
297
|
+
const totalRows = CSV_FILES.reduce((n, f) => n + (csvs[f] ? csvs[f].rows.length : 0), 0);
|
|
298
|
+
return { name, pass: errors.length === 0, detail: errors.length ? `${errors.length} issue(s)` : `${CSV_FILES.length} datasets, ${totalRows} rows validated`, errors };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// CLI entry: node validate-combinations.mjs [--dir <path>]
|
|
302
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
303
|
+
const args = process.argv.slice(2);
|
|
304
|
+
let dir = DEFAULT_DIR;
|
|
305
|
+
for (let i = 0; i < args.length; i++) {
|
|
306
|
+
if (args[i] === "--dir") dir = resolve(args[++i] || "");
|
|
307
|
+
else if (args[i].startsWith("--dir=")) dir = resolve(args[i].slice("--dir=".length));
|
|
308
|
+
}
|
|
309
|
+
const r = validateCombinations(dir);
|
|
310
|
+
if (r.pass) { console.log(`PASS ${r.name} — ${r.detail}`); process.exit(0); }
|
|
311
|
+
console.error(`FAIL ${r.name} — ${r.detail}`);
|
|
312
|
+
for (const e of r.errors) console.error(` - ${e}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: documentation
|
|
3
|
-
description: Template & generator library for technical docs — the skill agents call to produce a doc from a template. Use to (1) document a feature or module (the feature doc is the feature's LLD — low-level design — written in LLD Mode), (2) create or update a changelog entry, (3) document data flows or Redux state, (4) add to protected files, (5) record an architecture decision (
|
|
3
|
+
description: Template & generator library for technical docs — the skill agents call to produce a doc from a template. Use to (1) document a feature or module (the feature doc is the feature's LLD — low-level design — written in LLD Mode), (2) create or update a changelog entry, (3) document data flows or Redux state, (4) add to protected files, (5) record or revise an architecture decision (an AD), (6) create or update the project root README, (7) create or update a plan, (8) derive a feature doc's `owns:` frontmatter (the code-path globs it owns) from code, (9) scaffold a standard doc (README, structure.md, CHANGELOG, backlog, memory) from its template, (10) document an operations procedure (setup, deployment, maintenance) or a one-shot migration runbook, (11) write a steering doc (system overview, domain model, glossary, non-goals, security overview, testing strategy), (12) catalog the API surface or the environment/config registry, or (13) co-author any technical doc. Baselining / reconciling a repo's docs to the installed harness version is the **`maintenance` agent (baseline mode)** (which calls this skill for each template) — this skill does not orchestrate baseline. Triggers on "document this", "create docs", "update changelog", "add to protected files", "AD", "ADR", "architecture decision", "root README", "plan", "feature doc", "LLD", "glossary", "domain model", "runbook", "API reference".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Technical Documentation Skill
|
|
@@ -9,12 +9,31 @@ description: Template & generator library for technical docs — the skill agent
|
|
|
9
9
|
|
|
10
10
|
Document contracts and architecture, not implementation details. Show function signatures and API shapes — never paste full implementations (they rot fast). Use ASCII diagrams over prose for flows. Only document state at persistence boundaries (Redux persistence, DB schemas, API shapes), not component-level or transient state. Mark deprecated fields with `// UNUSED:` comments. Breaking changes belong in changelogs only, not module docs.
|
|
11
11
|
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## The Two Natures
|
|
15
|
+
|
|
16
|
+
Every document is exactly one of two things. Decide which before writing a word — every rule in this skill derives from the split.
|
|
17
|
+
|
|
18
|
+
| | **Truth document** | **Record** |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| States | Absolutes — how the system is, **now** | What happened — a dated account of activity |
|
|
21
|
+
| Named by | Topic: `auth.md`, `AD-003-persistence.md` | Date: `YYYY-MM-DD-slug.md` (or append position) |
|
|
22
|
+
| On change | Body rewritten in place the moment reality changes | Never edited — frozen at completion or append-only |
|
|
23
|
+
| Read to | Act correctly today | Trace provenance, diagnose, audit |
|
|
24
|
+
| Lives in | `docs/features/`, `docs/architecture/`, `docs/operations/`, `docs/tests/`, READMEs | `docs/history/` (plans, changelog, brainstorms, research, migrations), `docs/research/`, `docs/memory/` |
|
|
25
|
+
|
|
26
|
+
The two guarantees are complementary and both break on mixing: a reader holding a **truth document** must never need history to interpret it; a reader holding a **record** must be able to trust it was never rewritten. So the natures never share a document body.
|
|
27
|
+
|
|
28
|
+
- **Truth documents never narrate history.** No migration/fold stories, no dated "shipped/verified on YYYY-MM-DD" markers, no "was X → now Y" journeys, no provenance asides ("ex-`@nurix/X`, folded 2026-06-12"). When the system changes, the change-story goes to `docs/history/` (a changelog entry) and the truth document simply becomes correct in present tense. When you find historical narration in a truth document, rewrite it to the present-tense fact and let the record carry the story.
|
|
29
|
+
- **Records are never edited to match the present.** A completed plan stays wrong-in-hindsight; a changelog entry describes close-time state; a scope change gets a *new* dated record that links back, never an edit to a frozen one.
|
|
30
|
+
- **The one bridge — head is truth, tail is provenance.** An **Architecture Decision (AD)** ends with a **Decision Changelog**: the single sanctioned dated-history section inside a truth document, because for a decision the journey (what each revision reversed, and what triggered it) is part of the reasoning. The AD body stays current-truth-only; the tail carries the dates and links to the plans that implemented each revision. No other truth document carries dated history.
|
|
31
|
+
- **Truth documents cite records for the _why_, never the _when_.** `Provenance: AD-002` is good; "decided in AD-002 and shipped 2026-06-13" is not. Routing/index docs point only to truth documents — never to a plan or changelog entry as the "read first" source.
|
|
32
|
+
|
|
12
33
|
**No empty stubs.** Never leave a template section as an empty placeholder — omit any section with nothing real to say (each template declares its required core; everything else is optional). Write an explicit "None" only where the absence itself is information (e.g., a plan's Cross-Module Contracts Affected, an LLD's Ownership Conflicts). An empty table stub reads as "not yet researched"; an omitted section reads as "doesn't apply" — keep those meanings distinct.
|
|
13
34
|
|
|
14
35
|
**No redirect docs.** When moving a doc to a new location, delete the old file entirely. Update all references (cross-doc links, memory files) to point to the new path. Never leave a stub file that says "Moved to X" — redirect docs rot, clutter the repo, and create false search hits.
|
|
15
36
|
|
|
16
|
-
**Present tense, current state only.** Active docs (everything outside `docs/history/`) describe the system _as it is now_ — what exists and how it works today. They never narrate how it got there: no migration/fold history, no dated "shipped/verified/built on YYYY-MM-DD" markers, no "ex-`@nurix/X`, folded 2026-06-12" provenance, no "was X → now Y" change journeys, no "removed/deleted in the <date> fold". The historical record — when and how things changed — lives **only** in `docs/history/` (changelog, plans); `docs/memory/` is the agent's dated log and is likewise exempt, and an ADR's **Decision Changelog** section is the one sanctioned dated-history section inside an active doc (the ADR body itself stays current-truth-only). A doc may cite an ADR for the _why_ (the decision rationale), but must not narrate the _when/how it changed_. Routing/index docs point only to current authority docs — never to a historical plan or changelog as the "read first" source. When you find historical narration in an active doc, rewrite it to the present-tense fact and let `docs/history/` carry the story.
|
|
17
|
-
|
|
18
37
|
---
|
|
19
38
|
|
|
20
39
|
## Decision Tree
|
|
@@ -24,23 +43,39 @@ User request
|
|
|
24
43
|
│
|
|
25
44
|
├─ "Baseline/reconcile docs" ──────▶ maintenance agent (baseline mode) (it calls this skill per template)
|
|
26
45
|
│
|
|
27
|
-
├─ "Document feature/module (LLD)" ▶
|
|
46
|
+
├─ "Document feature/module (LLD)" ▶ Feature (LLD) Template
|
|
28
47
|
│
|
|
29
|
-
├─ "Create/update changelog" ──────▶ Changelog Template
|
|
48
|
+
├─ "Create/update changelog" ──────▶ Changelog Template (record)
|
|
30
49
|
│
|
|
31
|
-
├─ "Document data flow" ───────────▶
|
|
50
|
+
├─ "Document data flow" ───────────▶ Feature Template (Architecture section)
|
|
32
51
|
│
|
|
33
|
-
├─ "Document Redux/state" ─────────▶
|
|
52
|
+
├─ "Document Redux/state" ─────────▶ Feature Template (State section)
|
|
34
53
|
│
|
|
35
54
|
├─ "Add to protected files" ───────▶ Protected Files Template
|
|
36
55
|
│
|
|
37
|
-
├─ "Record architecture
|
|
56
|
+
├─ "Record/revise architecture
|
|
57
|
+
│ decision" ────────────────────▶ AD Template (one per subject; revisions update it in place)
|
|
58
|
+
│
|
|
59
|
+
├─ "System at a glance" ───────────▶ System Overview Template
|
|
60
|
+
│
|
|
61
|
+
├─ "Domain model / glossary /
|
|
62
|
+
│ non-goals" ───────────────────▶ Steering Templates
|
|
63
|
+
│
|
|
64
|
+
├─ "Catalog the API surface" ──────▶ API Reference Template
|
|
65
|
+
│
|
|
66
|
+
├─ "Document env vars / config" ───▶ Environment Registry Template
|
|
67
|
+
│
|
|
68
|
+
├─ "Security / threat model" ──────▶ Security Overview Template
|
|
69
|
+
│
|
|
70
|
+
├─ "Testing strategy" ─────────────▶ Testing Strategy Template
|
|
71
|
+
│
|
|
72
|
+
├─ "Recurring procedure/setup" ────▶ Operations Procedure Template
|
|
38
73
|
│
|
|
39
|
-
├─ "
|
|
74
|
+
├─ "One-shot migration/upgrade" ───▶ Migration Runbook Template (record)
|
|
40
75
|
│
|
|
41
76
|
├─ "Create/update root README" ────▶ Project Root README Template
|
|
42
77
|
│
|
|
43
|
-
├─ "Create/update plan" ───────────▶ Plan Template
|
|
78
|
+
├─ "Create/update plan" ───────────▶ Plan Template (record)
|
|
44
79
|
│
|
|
45
80
|
├─ "Update existing doc" ──────────▶ Preserve existing structure
|
|
46
81
|
│
|
|
@@ -51,10 +86,19 @@ User request
|
|
|
51
86
|
|
|
52
87
|
## Naming & Lifecycle
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
| Document | Nature | Name | Lifecycle |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| Feature (LLD) | Truth | `kebab-case.md` | Living; `status:` `draft` → `active` → `deprecated` |
|
|
92
|
+
| Architecture Decision | Truth | `AD-NNN-kebab-case.md` | Living; `Proposed` → `Accepted` → `Deprecated`; revisions rewrite the body + append to its Decision Changelog |
|
|
93
|
+
| Steering docs (overview, domain model, glossary, non-goals, security, testing strategy) | Truth | fixed names | Living |
|
|
94
|
+
| API reference / environment registry | Truth | fixed names | Living |
|
|
95
|
+
| Operations procedure | Truth | `kebab-case.md` | Living |
|
|
96
|
+
| Protected files | Truth | `protected-files.md` | Living |
|
|
97
|
+
| Plan | Record | `YYYY-MM-DD-kebab-case.md` | Freezes at `Complete` / `Abandoned` |
|
|
98
|
+
| Changelog entry | Record | `YYYY-MM-DD-kebab-case.md` | Frozen; master index append-only |
|
|
99
|
+
| Migration runbook | Record | `YYYY-MM-DD-kebab-case.md` | Freezes once executed, outcome stamped |
|
|
100
|
+
| Research | Record | `history/research/{topic}/` | Frozen snapshot |
|
|
101
|
+
| Memory | Record | `{agent-name}.md` | Append-only dated ledger |
|
|
58
102
|
|
|
59
103
|
---
|
|
60
104
|
|
|
@@ -65,7 +109,7 @@ When creating or updating a feature doc, derive its **`owns:` frontmatter** —
|
|
|
65
109
|
### When to run
|
|
66
110
|
|
|
67
111
|
- **Baseline / reconcile** — for every feature doc whose code paths aren't yet declared in its `owns:` frontmatter (the `maintenance` agent (baseline mode) calls this)
|
|
68
|
-
- **New feature doc** — when creating a doc via the Feature
|
|
112
|
+
- **New feature doc** — when creating a doc via the Feature template, project its `owns:` globs
|
|
69
113
|
- **Doc update** — when the documented scope changes, update the doc's `owns:` globs
|
|
70
114
|
|
|
71
115
|
### How to derive the frontmatter
|
|
@@ -123,6 +167,6 @@ For docs that don't match templates:
|
|
|
123
167
|
|
|
124
168
|
| File | Use For |
|
|
125
169
|
| --- | --- |
|
|
126
|
-
| `references/templates.md` | docs/README.md, structure.md, the feature-frontmatter contract, scaffolds (CHANGELOG, backlog, memory), feature, changelog, plan, operations procedure, protected files,
|
|
127
|
-
| `references/style-guide.md` | Writing rules, formatting conventions |
|
|
170
|
+
| `references/templates.md` | docs/README.md, structure.md, the feature-frontmatter contract, scaffolds (CHANGELOG, backlog, memory), feature, changelog, plan, operations procedure, migration runbook, protected files, AD, steering docs (system overview, domain model, glossary, non-goals, security, testing strategy), API reference, environment registry, project root README templates |
|
|
171
|
+
| `references/style-guide.md` | Writing rules, formatting conventions, the two-natures tense rules |
|
|
128
172
|
| `references/examples.md` | Good vs bad documentation examples |
|
|
@@ -151,6 +151,46 @@ When the page loads, the useAppInit hook calls the syncData function. This funct
|
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
154
|
+
## Example 5: Architecture Decision revision
|
|
155
|
+
|
|
156
|
+
### ❌ Bad: Supersession chain
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
docs/architecture/decisions/
|
|
160
|
+
├── AD-002-auth-keycloak.md (status: Superseded)
|
|
161
|
+
├── AD-006-auth-better-auth.md (status: Superseded, "supersedes AD-002")
|
|
162
|
+
└── AD-011-auth-better-auth-v2.md (status: Accepted, "supersedes AD-006")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Problems:** Three files hold one subject; the reader must diff a chain to learn the current truth; two of the three are wrong-but-present (the same pathology as keeping `report_v1.sql` beside the live `report_v2.sql`); the reasoning that killed Keycloak is stranded in a dead file nobody opens.
|
|
166
|
+
|
|
167
|
+
### ✅ Good: One subject, updated in place
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
docs/architecture/decisions/
|
|
171
|
+
└── AD-002-auth.md (status: Accepted, last-updated bumped)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```markdown
|
|
175
|
+
## Decision
|
|
176
|
+
|
|
177
|
+
We use Better Auth with one-shot credential issuance… ← current truth, no journey
|
|
178
|
+
|
|
179
|
+
…
|
|
180
|
+
|
|
181
|
+
## Decision Changelog
|
|
182
|
+
|
|
183
|
+
- 2026-05-02 — Moved issuance to one-shot tokens because refresh-token theft
|
|
184
|
+
surfaced in review. Implemented by [plan](…). (Previously: long-lived refresh tokens.)
|
|
185
|
+
- 2026-03-18 — Replaced Keycloak with Better Auth because self-hosting cost
|
|
186
|
+
outweighed SSO needs. Implemented by [plan](…). (Previously: Keycloak.)
|
|
187
|
+
- 2026-01-10 — Initial decision accepted. Implemented by [plan](…).
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Why it works:** The body answers "how does auth work and why" without archaeology; the tail preserves every reversal with its trigger; the rejected alternatives stay findable in the one file a reader actually opens.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
154
194
|
## Quick Comparison
|
|
155
195
|
|
|
156
196
|
| Aspect | Bad | Good |
|
|
@@ -159,4 +199,5 @@ When the page loads, the useAppInit hook calls the syncData function. This funct
|
|
|
159
199
|
| State | Every occurrence | Persistence points only |
|
|
160
200
|
| Flows | Prose paragraphs | ASCII diagrams |
|
|
161
201
|
| Changelogs | "Updated X" | Function names + migrations |
|
|
202
|
+
| Decisions | New doc per revision | One AD per subject, updated in place |
|
|
162
203
|
| Length | 100+ lines | 20-40 lines |
|
|
@@ -220,26 +220,31 @@ Use for structured info:
|
|
|
220
220
|
|
|
221
221
|
---
|
|
222
222
|
|
|
223
|
-
## Tense & History
|
|
223
|
+
## The Two Natures — Tense & History
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
Every document is a **truth document** (states current absolutes, updated in place) or a **record** (dated account of activity, frozen or append-only) — see `SKILL.md` § The Two Natures. The tense rules follow directly:
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
**Truth documents describe the present, not the past.** Everything outside `docs/history/` states what the system _is_ and how it works _today_. It must not narrate how it got there. The journey — the dates, the migrations — lives **only** in records (`docs/history/`: changelog, plans, migrations). Two sanctioned exceptions: `docs/memory/` is the agent's own dated ledger, and an AD's **Decision Changelog** tail carries that decision's dated revision history (the AD body stays current-truth-only — head is truth, tail is provenance).
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
**Records are never edited to match the present.** A frozen plan stays wrong-in-hindsight; a changelog entry describes what shipped as it shipped. A scope change gets a new dated record that links back.
|
|
230
|
+
|
|
231
|
+
Strip this language from truth documents:
|
|
232
|
+
|
|
233
|
+
| ❌ Historical narration (belongs in a record) | ✅ Present-tense fact (belongs in the truth doc) |
|
|
230
234
|
| --- | --- |
|
|
231
235
|
| "Folded in from the `NuStack-X` repo (2026-06-12)." | "Vendor-connector catalogue; one subpath per vendor." |
|
|
232
236
|
| "Built 2026-06-14; shipped + verified 2026-06-13." | "`agentSessionWorkflow` runs an outer turn loop over `runAgentLoop`." |
|
|
233
237
|
| "The legacy worker was deleted in the 2026-06-14 fold." | "One worker interprets `WorkflowDefinition`." |
|
|
234
238
|
| "ex-`@nurix/mcp-runtime`, M9 Phase C; was X, now Y." | "MCP client runtime, wired into `agentTriggerWorkflow`." |
|
|
235
|
-
| "Read first: `docs/history/plans/2026-…-fold.md`" (routing) | "Read first: `docs/architecture/<current-
|
|
239
|
+
| "Read first: `docs/history/plans/2026-…-fold.md`" (routing) | "Read first: `docs/architecture/<current-truth-doc>.md`" |
|
|
236
240
|
|
|
237
241
|
Rules:
|
|
238
242
|
|
|
239
|
-
- **No dates in
|
|
240
|
-
- **Cite the
|
|
241
|
-
- **Routing/index docs point only to
|
|
242
|
-
- **When a doc changes because the system changed, the change-story goes in `docs/history/` (a changelog entry), and the
|
|
243
|
+
- **No dates in truth documents** for when something shipped, was verified, folded, or was removed. A bare status marker (`✅ shipped`, `⏳ pending`) is fine; the _date_ it shipped is not.
|
|
244
|
+
- **Cite the AD for the _why_, never the _when_.** `Provenance: AD-002` is good (it points to the decision rationale). "Decided in AD-002 and shipped 2026-06-13" is not.
|
|
245
|
+
- **Routing/index docs point only to truth documents.** A row whose only "read first" target is a historical plan or changelog is a redirect-for-history — repoint it to the current truth doc, or drop the row.
|
|
246
|
+
- **When a doc changes because the system changed, the change-story goes in `docs/history/` (a changelog entry), and the truth document simply becomes correct in present tense.** Never leave the diff narrated in place.
|
|
247
|
+
- **A decision that moves is an edit, not a new document.** Rewrite the AD's affected sections to the new truth and append the revision to its Decision Changelog — never mint a parallel doc and never leave the old choice narrated in the body.
|
|
243
248
|
|
|
244
249
|
---
|
|
245
250
|
|