@nurix/etna 0.19.3 → 0.19.4
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/data/harness-index.json +1 -1
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/code/rules/frontend.md +3 -3
- package/data/harnesses/code/skills/design/SKILL.md +20 -8
- package/data/harnesses/code/skills/design/data/catalog/rules.csv +10 -5
- package/data/harnesses/code/skills/design/data/combinations/Readme.md +8 -4
- package/data/harnesses/code/skills/design/data/combinations/accent-presets.md +113 -0
- package/data/harnesses/code/skills/design/data/combinations/fonts-known.json +11 -0
- package/data/harnesses/code/skills/design/data/combinations/manifest.json +57 -10
- package/data/harnesses/code/skills/design/data/combinations/motion.csv +10 -0
- package/data/harnesses/code/skills/design/data/combinations/palettes.csv +92 -49
- package/data/harnesses/code/skills/design/data/combinations/styles.csv +67 -54
- package/data/harnesses/code/skills/design/data/combinations/typography.csv +64 -61
- package/data/harnesses/code/skills/design/data/design-md/library/expo.md +4 -4
- package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +5 -5
- package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +6 -6
- package/data/harnesses/code/skills/design/data/design-md/library/notion.md +4 -4
- package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +4 -4
- package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +2 -2
- package/data/harnesses/code/skills/design/data/design-md/library/slack.md +6 -6
- package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +5 -5
- package/data/harnesses/code/skills/design/data/design-md/nurix.md +11 -7
- package/data/harnesses/code/skills/design/data/design-tokens/SCHEMA.md +256 -141
- package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/apple/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/binance/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/cal/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/clay/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/composio/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/expo/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/figma/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/framer/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/goldens.json +123 -123
- package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.resolver.json +1 -1
- package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.dark.json +10 -0
- package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/meta/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/miro/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/notion/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.tokens.json +307 -2
- package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/resend/design.resolver.json +1 -1
- package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.dark.json +10 -0
- package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.tokens.json +310 -2
- package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/slack/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.tokens.json +313 -1
- package/data/harnesses/code/skills/design/data/design-tokens/uber/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.resolver.json +1 -1
- package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.dark.json +10 -0
- package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/warp/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/wired/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/wise/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.tokens.json +309 -1
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/command-center.tsx +25 -25
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/create-edit.tsx +18 -18
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/dashboard.tsx +82 -105
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/entity-detail.tsx +97 -93
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/list-detail.tsx +16 -15
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/log-stream.tsx +34 -34
- package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +5 -5
- package/data/harnesses/code/skills/design/references/create.md +49 -7
- package/data/harnesses/code/skills/design/references/diagnosis.md +3 -3
- package/data/harnesses/code/skills/design/references/hard-rules.md +25 -3
- package/data/harnesses/code/skills/design/references/improve.md +32 -3
- package/data/harnesses/code/skills/design/references/interaction.md +3 -3
- package/data/harnesses/code/skills/design/references/introspect.md +1 -1
- package/data/harnesses/code/skills/design/references/lifecycle.md +2 -2
- package/data/harnesses/code/skills/design/references/personality.md +280 -133
- package/data/harnesses/code/skills/design/references/structure.md +22 -1
- package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +306 -26
- package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +596 -104
- package/data/harnesses/code/skills/design/scripts/lib/checks.mjs +200 -11
- package/data/harnesses/code/skills/design/scripts/lib/vocabulary.mjs +65 -0
- package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +95 -11
- package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +50 -5
- package/data/harnesses/code/skills/design/scripts/render-rung.mjs +214 -14
- package/data/harnesses/code/skills/design/scripts/scaffold.mjs +242 -25
- package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +81 -6
- package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +101 -5
- package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +142 -17
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
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.
|
|
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/theme/budget vocabularies, symbol-glyph ban, palette hex + WCAG contrast floors, style payload + checklist discipline, typography @fontsource binding, motion token-name discipline, chart token discipline, lucide/font registry membership, accent-preset scale shape, and manifest row/tier-count parity. Importable (validateCombinations(dir)) and runnable as a CLI.
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { join, dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -7,14 +7,15 @@ const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
|
7
7
|
const SKILL_ROOT = join(SCRIPTS_DIR, "..");
|
|
8
8
|
const DEFAULT_DIR = join(SKILL_ROOT, "data/combinations");
|
|
9
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"];
|
|
10
|
+
const CSV_FILES = ["typography.csv", "palettes.csv", "styles.csv", "motion.csv", "product-router.csv", "charts.csv", "icons.csv", "landing.csv"];
|
|
11
|
+
const REQUIRED_FILES = [...CSV_FILES, "manifest.json", "lucide-names.json", "fonts-known.json", "accent-presets.md", "Readme.md"];
|
|
12
12
|
|
|
13
13
|
// Byte-exact, order-exact header contract per dataset.
|
|
14
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"],
|
|
15
|
+
"typography.csv": ["id", "pairing", "tier", "heading_family", "body_family", "mono_family", "contrast", "mood", "best_for", "heading_weights", "body_weights", "google_fonts_css", "notes", "css-import", "tailwind-config", "display-size-hint"],
|
|
16
|
+
"palettes.csv": ["id", "palette", "tier", "product_affinity", "theme", "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", "theme", "voice", "css-recipe", "checklist", "tokens-hint"],
|
|
18
|
+
"motion.csv": ["id", "moment", "tier", "keywords", "trigger", "duration_token", "easing_token", "transform_delta", "archetypes", "budget", "do", "dont", "performance_notes"],
|
|
18
19
|
"product-router.csv": ["id", "product_type", "tier", "keywords", "style_priority", "palette_stance", "type_stance", "density_tier", "key_surfaces", "decision_rules", "anti_patterns", "notes"],
|
|
19
20
|
"charts.csv": ["id", "data_shape", "tier", "keywords", "best_chart", "alternatives", "when", "when_not", "volume_guidance", "series_tokens", "a11y_notes", "fallback", "interaction"],
|
|
20
21
|
"icons.csv": ["id", "intent", "tier", "keywords", "lucide_name", "import_name", "usage", "notes"],
|
|
@@ -24,7 +25,20 @@ const HEADERS = {
|
|
|
24
25
|
const VALID_TIERS = new Set(["operator-active", "dormant-marketing"]);
|
|
25
26
|
const VALID_DENSITIES = new Set(["spacious", "standard", "dense"]);
|
|
26
27
|
const VALID_TYPE_CONTRASTS = new Set(["serif+sans", "sans+sans", "sans+serif", "serif+serif", "display+sans", "mono+sans"]);
|
|
27
|
-
|
|
28
|
+
// The closed six surface archetypes — product-router key_surfaces and motion archetypes both draw from it.
|
|
29
|
+
const VALID_ARCHETYPES = new Set(["list-detail", "dashboard", "entity-detail", "create-edit", "log-stream", "command-center"]);
|
|
30
|
+
const VALID_STYLE_THEMES = new Set(["light", "dark", "both"]);
|
|
31
|
+
const VALID_PALETTE_THEMES = new Set(["light", "dark"]);
|
|
32
|
+
const VALID_BUDGETS = new Set(["baseline", "brand-forward"]);
|
|
33
|
+
// Legal temporal token names per design-tokens SCHEMA.md: the stored duration primitives (+ v2 stagger-unit) and ease-default plus its emit-derived ease-enter / ease-exit pair.
|
|
34
|
+
const MOTION_DURATION_TOKENS = new Set(["duration-unit", "duration-fast", "duration-normal", "duration-slow", "stagger-unit"]);
|
|
35
|
+
const MOTION_EASING_TOKENS = new Set(["ease-default", "ease-enter", "ease-exit"]);
|
|
36
|
+
// The four executable style payload columns — non-empty on every operator-active row.
|
|
37
|
+
const STYLE_PAYLOAD_COLUMNS = ["voice", "css-recipe", "checklist", "tokens-hint"];
|
|
38
|
+
// One @fontsource import statement, as the css-import cell carries them ;-separated.
|
|
39
|
+
const FONTSOURCE_IMPORT = /^@import '@fontsource(?:-variable)?\/([a-z0-9-]+)\/[a-z0-9.-]+\.css'$/;
|
|
40
|
+
const ACCENT_PRESET_SCALES = 12;
|
|
41
|
+
const ACCENT_PRESET_STEPS = 7;
|
|
28
42
|
|
|
29
43
|
const UPPER_HEX = /^#[0-9A-F]{6}$/;
|
|
30
44
|
const RAW_HEX_ANYWHERE = /#[0-9a-fA-F]{3,8}\b/;
|
|
@@ -141,9 +155,11 @@ function checkGeneric(file, csv, errors) {
|
|
|
141
155
|
});
|
|
142
156
|
}
|
|
143
157
|
|
|
144
|
-
// palettes.csv: uppercase 6-digit hex in all 34 color cells + WCAG contrast floors.
|
|
158
|
+
// palettes.csv: theme enum, uppercase 6-digit hex in all 34 color cells + WCAG contrast floors.
|
|
145
159
|
function checkPalettes(csv, errors) {
|
|
146
160
|
for (const row of csv.rows) {
|
|
161
|
+
if (!VALID_PALETTE_THEMES.has(row.theme))
|
|
162
|
+
errors.push(`palettes.csv row ${row._line} (${row.palette}): invalid theme "${row.theme}" (want light|dark)`);
|
|
147
163
|
const valid = {};
|
|
148
164
|
for (const col of PALETTE_COLOR_COLUMNS) {
|
|
149
165
|
const v = row[col];
|
|
@@ -163,6 +179,50 @@ function checkPalettes(csv, errors) {
|
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
181
|
|
|
182
|
+
// styles.csv: theme enum + the payload rule — all four payload cells non-empty on operator-active rows, the checklist carrying 3-7 ;-separated items.
|
|
183
|
+
function checkStyles(csv, errors) {
|
|
184
|
+
for (const row of csv.rows) {
|
|
185
|
+
if (!VALID_STYLE_THEMES.has(row.theme))
|
|
186
|
+
errors.push(`styles.csv row ${row._line} (${row.movement}): invalid theme "${row.theme}" (want light|dark|both)`);
|
|
187
|
+
if (row.tier !== "operator-active") continue;
|
|
188
|
+
for (const col of STYLE_PAYLOAD_COLUMNS)
|
|
189
|
+
if (row[col].trim() === "")
|
|
190
|
+
errors.push(`styles.csv row ${row._line} (${row.movement}): operator-active row has empty ${col} payload`);
|
|
191
|
+
const items = row.checklist.split(";").map((s) => s.trim()).filter((s) => s !== "");
|
|
192
|
+
if (row.checklist.trim() !== "" && (items.length < 3 || items.length > 7))
|
|
193
|
+
errors.push(`styles.csv row ${row._line} (${row.movement}): checklist has ${items.length} items (want 3-7)`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// motion.csv: budget enum, archetypes from the closed six, duration/easing cells referencing only legal temporal token names.
|
|
198
|
+
function checkMotion(csv, errors) {
|
|
199
|
+
for (const row of csv.rows) {
|
|
200
|
+
if (!VALID_BUDGETS.has(row.budget))
|
|
201
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): invalid budget "${row.budget}" (want baseline|brand-forward)`);
|
|
202
|
+
|
|
203
|
+
const archetypes = row.archetypes.split(";").map((s) => s.trim()).filter((s) => s !== "");
|
|
204
|
+
if (archetypes.length === 0)
|
|
205
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): archetypes cell is empty`);
|
|
206
|
+
for (const a of archetypes)
|
|
207
|
+
if (!VALID_ARCHETYPES.has(a))
|
|
208
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): unknown archetype "${a}"`);
|
|
209
|
+
|
|
210
|
+
const durations = row.duration_token.match(/duration-[a-z]+|stagger-unit/g) ?? [];
|
|
211
|
+
if (durations.length === 0)
|
|
212
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): duration_token names no duration token`);
|
|
213
|
+
for (const tok of durations)
|
|
214
|
+
if (!MOTION_DURATION_TOKENS.has(tok))
|
|
215
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): duration token "${tok}" not in the schema vocabulary (${[...MOTION_DURATION_TOKENS].join(",")})`);
|
|
216
|
+
|
|
217
|
+
const easings = row.easing_token.match(/ease-[a-z]+/g) ?? [];
|
|
218
|
+
if (easings.length === 0)
|
|
219
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): easing_token names no easing token`);
|
|
220
|
+
for (const tok of easings)
|
|
221
|
+
if (!MOTION_EASING_TOKENS.has(tok))
|
|
222
|
+
errors.push(`motion.csv row ${row._line} (${row.moment}): easing token "${tok}" not in the schema vocabulary (${[...MOTION_EASING_TOKENS].join(",")})`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
166
226
|
// charts.csv: operator-active only, --chart-N discipline in series_tokens, N in 1..5 everywhere, no raw hex.
|
|
167
227
|
function checkCharts(csv, errors) {
|
|
168
228
|
for (const row of csv.rows) {
|
|
@@ -199,7 +259,7 @@ function checkIcons(csv, dir, errors) {
|
|
|
199
259
|
}
|
|
200
260
|
}
|
|
201
261
|
|
|
202
|
-
// typography.csv rows against the fonts-known.json registry (family -> known weights)
|
|
262
|
+
// typography.csv rows against the fonts-known.json registry (family -> known weights), the contrast vocabulary, and the paste-ready payload columns: css-import parses as @fontsource statements whose package set is exactly the row's kebab-cased families, tailwind-config names all three families, display-size-hint non-empty.
|
|
203
263
|
function checkTypography(csv, dir, errors) {
|
|
204
264
|
const text = readSafe(join(dir, "fonts-known.json"));
|
|
205
265
|
if (text === null) return; // presence already reported
|
|
@@ -224,6 +284,31 @@ function checkTypography(csv, dir, errors) {
|
|
|
224
284
|
}
|
|
225
285
|
if (!VALID_TYPE_CONTRASTS.has(row.contrast))
|
|
226
286
|
errors.push(`typography.csv row ${row._line} (${row.pairing}): invalid contrast "${row.contrast}"`);
|
|
287
|
+
|
|
288
|
+
// The font-binding contract at the data root: every import statement parses, and the imported package set is exactly the row's kebab-cased family set.
|
|
289
|
+
const families = [row.heading_family, row.body_family, row.mono_family];
|
|
290
|
+
const kebabs = new Set(families.map((f) => f.toLowerCase().replace(/ /g, "-")));
|
|
291
|
+
const pkgs = new Set();
|
|
292
|
+
const fragments = row["css-import"].split(";").map((s) => s.trim()).filter((s) => s !== "");
|
|
293
|
+
if (fragments.length === 0)
|
|
294
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): css-import is empty`);
|
|
295
|
+
for (const frag of fragments) {
|
|
296
|
+
const m = FONTSOURCE_IMPORT.exec(frag);
|
|
297
|
+
if (!m) errors.push(`typography.csv row ${row._line} (${row.pairing}): css-import fragment "${frag}" is not an @import '@fontsource/<pkg>/....css' statement`);
|
|
298
|
+
else pkgs.add(m[1]);
|
|
299
|
+
}
|
|
300
|
+
for (const pkg of pkgs)
|
|
301
|
+
if (!kebabs.has(pkg))
|
|
302
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): css-import package "${pkg}" matches none of the row's families`);
|
|
303
|
+
for (const kebab of kebabs)
|
|
304
|
+
if (fragments.length > 0 && !pkgs.has(kebab))
|
|
305
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): family "${kebab}" has no css-import statement`);
|
|
306
|
+
|
|
307
|
+
for (const fam of new Set(families))
|
|
308
|
+
if (!row["tailwind-config"].includes(fam))
|
|
309
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): tailwind-config does not name family "${fam}"`);
|
|
310
|
+
if (row["display-size-hint"].trim() === "")
|
|
311
|
+
errors.push(`typography.csv row ${row._line} (${row.pairing}): display-size-hint is empty`);
|
|
227
312
|
}
|
|
228
313
|
}
|
|
229
314
|
|
|
@@ -236,7 +321,7 @@ function checkDensityAndSurfaces(file, csv, errors) {
|
|
|
236
321
|
errors.push(`${file} row ${row._line} (${row[nameCol]}): invalid ${densityCol} "${row[densityCol]}" (want spacious|standard|dense)`);
|
|
237
322
|
if (file === "product-router.csv" && row.tier === "operator-active") {
|
|
238
323
|
for (const surface of row.key_surfaces.split(",").map((s) => s.trim()).filter((s) => s !== ""))
|
|
239
|
-
if (!
|
|
324
|
+
if (!VALID_ARCHETYPES.has(surface))
|
|
240
325
|
errors.push(`product-router.csv row ${row._line} (${row.product_type}): unknown key_surface "${surface}"`);
|
|
241
326
|
}
|
|
242
327
|
}
|
|
@@ -249,8 +334,32 @@ function checkLanding(csv, errors) {
|
|
|
249
334
|
errors.push(`landing.csv row ${row._line} (${row.pattern}): tier must be dormant-marketing (got "${row.tier}")`);
|
|
250
335
|
}
|
|
251
336
|
|
|
252
|
-
//
|
|
253
|
-
function
|
|
337
|
+
// accent-presets.md: the accent table carries exactly 12 scales, each with 7 oklch step cells; returns the parsed scale count for manifest parity (null when unparseable).
|
|
338
|
+
function checkAccentPresets(dir, errors) {
|
|
339
|
+
const text = readSafe(join(dir, "accent-presets.md"));
|
|
340
|
+
if (text === null) return null; // presence already reported
|
|
341
|
+
const lines = text.split("\n");
|
|
342
|
+
const headerIdx = lines.findIndex((l) => /^\|\s*Accent\s*\|/.test(l));
|
|
343
|
+
if (headerIdx === -1) { errors.push("accent-presets.md: accent table header row (| Accent | ...) not found"); return null; }
|
|
344
|
+
const stepLabels = lines[headerIdx].split("|").map((c) => c.trim()).filter((c) => /^\d+$/.test(c));
|
|
345
|
+
if (stepLabels.length !== ACCENT_PRESET_STEPS)
|
|
346
|
+
errors.push(`accent-presets.md: accent table declares ${stepLabels.length} step columns (want ${ACCENT_PRESET_STEPS})`);
|
|
347
|
+
let scales = 0;
|
|
348
|
+
for (let i = headerIdx + 2; i < lines.length && lines[i].startsWith("|"); i++) {
|
|
349
|
+
const cells = lines[i].split("|").slice(1, -1).map((c) => c.trim());
|
|
350
|
+
const name = cells[0] ?? "";
|
|
351
|
+
scales++;
|
|
352
|
+
const steps = cells.filter((c) => /`oklch\([^)]*\)`/.test(c));
|
|
353
|
+
if (steps.length !== ACCENT_PRESET_STEPS)
|
|
354
|
+
errors.push(`accent-presets.md: scale ${name} carries ${steps.length} oklch steps (want ${ACCENT_PRESET_STEPS})`);
|
|
355
|
+
}
|
|
356
|
+
if (scales !== ACCENT_PRESET_SCALES)
|
|
357
|
+
errors.push(`accent-presets.md: accent table carries ${scales} scales (want ${ACCENT_PRESET_SCALES})`);
|
|
358
|
+
return scales;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// manifest.json: datasets entry per CSV with the required keys and rows.total + tier counts matching the actual CSV, plus an accent-presets.md entry whose scales match the parsed table.
|
|
362
|
+
function checkManifest(dir, csvs, accentScales, errors) {
|
|
254
363
|
const text = readSafe(join(dir, "manifest.json"));
|
|
255
364
|
if (text === null) return; // presence already reported
|
|
256
365
|
let manifest;
|
|
@@ -261,11 +370,24 @@ function checkManifest(dir, csvs, errors) {
|
|
|
261
370
|
if (entry === undefined) { errors.push(`manifest.json: datasets has no entry for ${file}`); continue; }
|
|
262
371
|
for (const key of ["seed", "research", "quality_bar", "rows", "refresh_trigger"])
|
|
263
372
|
if (!(key in entry)) errors.push(`manifest.json: datasets entry for ${file} missing key "${key}"`);
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
373
|
+
if (!entry.rows || typeof entry.rows !== "object") { errors.push(`manifest.json: datasets entry for ${file} has no rows object`); continue; }
|
|
374
|
+
if (!csvs[file]) continue;
|
|
375
|
+
const actual = {
|
|
376
|
+
total: csvs[file].rows.length,
|
|
377
|
+
operator_active: csvs[file].rows.filter((r) => r.tier === "operator-active").length,
|
|
378
|
+
dormant_marketing: csvs[file].rows.filter((r) => r.tier === "dormant-marketing").length,
|
|
379
|
+
};
|
|
380
|
+
for (const key of ["total", "operator_active", "dormant_marketing"]) {
|
|
381
|
+
if (typeof entry.rows[key] !== "number") errors.push(`manifest.json: datasets entry for ${file} has no numeric rows.${key}`);
|
|
382
|
+
else if (entry.rows[key] !== actual[key])
|
|
383
|
+
errors.push(`manifest.json: ${file} rows.${key}=${entry.rows[key]} but the CSV has ${actual[key]}`);
|
|
384
|
+
}
|
|
268
385
|
}
|
|
386
|
+
const accent = manifest.datasets["accent-presets.md"];
|
|
387
|
+
if (accent === undefined) errors.push("manifest.json: datasets has no entry for accent-presets.md");
|
|
388
|
+
else if (typeof accent.scales !== "number") errors.push("manifest.json: accent-presets.md entry has no numeric scales");
|
|
389
|
+
else if (accentScales !== null && accent.scales !== accentScales)
|
|
390
|
+
errors.push(`manifest.json: accent-presets.md scales=${accent.scales} but the table carries ${accentScales}`);
|
|
269
391
|
}
|
|
270
392
|
|
|
271
393
|
// Validate every combination dataset in `dir`; returns a self-integrity-shaped result.
|
|
@@ -286,13 +408,16 @@ export function validateCombinations(dir) {
|
|
|
286
408
|
}
|
|
287
409
|
|
|
288
410
|
if (csvs["palettes.csv"]) checkPalettes(csvs["palettes.csv"], errors);
|
|
411
|
+
if (csvs["styles.csv"]) checkStyles(csvs["styles.csv"], errors);
|
|
412
|
+
if (csvs["motion.csv"]) checkMotion(csvs["motion.csv"], errors);
|
|
289
413
|
if (csvs["charts.csv"]) checkCharts(csvs["charts.csv"], errors);
|
|
290
414
|
checkIcons(csvs["icons.csv"], dir, errors);
|
|
291
415
|
checkTypography(csvs["typography.csv"], dir, errors);
|
|
292
416
|
if (csvs["styles.csv"]) checkDensityAndSurfaces("styles.csv", csvs["styles.csv"], errors);
|
|
293
417
|
if (csvs["product-router.csv"]) checkDensityAndSurfaces("product-router.csv", csvs["product-router.csv"], errors);
|
|
294
418
|
if (csvs["landing.csv"]) checkLanding(csvs["landing.csv"], errors);
|
|
295
|
-
|
|
419
|
+
const accentScales = checkAccentPresets(dir, errors);
|
|
420
|
+
checkManifest(dir, csvs, accentScales, errors);
|
|
296
421
|
|
|
297
422
|
const totalRows = CSV_FILES.reduce((n, f) => n + (csvs[f] ? csvs[f].rows.length : 0), 0);
|
|
298
423
|
return { name, pass: errors.length === 0, detail: errors.length ? `${errors.length} issue(s)` : `${CSV_FILES.length} datasets, ${totalRows} rows validated`, errors };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.4",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|