references/artifact-index.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://styleseed.dev/schemas/artifact-index.schema.json",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "artifacts"],
"properties": {
"schemaVersion": { "const": 1 },
"artifacts": {
"type": "array", "minItems": 1,
"items": {
"type": "object", "additionalProperties": false, "required": ["id", "config"],
"properties": { "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" }, "config": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}\\.json$" } }
}
}
}
}
references/artifact.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://styleseed.dev/schemas/artifact.schema.json",
"type": "object", "additionalProperties": false,
"required": ["schemaVersion", "id", "target", "selection", "decisions", "implementation", "validation"],
"properties": {
"schemaVersion": { "const": 1 },
"id": { "$ref": "#/$defs/id" },
"target": { "type": "object", "additionalProperties": false, "required": ["kind", "locator"], "properties": { "kind": { "enum": ["route", "component", "document", "deck", "carousel", "single-frame"] }, "locator": { "type": "string", "minLength": 1, "maxLength": 240, "pattern": "^[^\\u0000-\\u001f\\u007f?#]+$" } } },
"selection": {
"type": "object", "additionalProperties": false, "required": ["grammar", "adapter", "domain", "page", "recipe", "palette", "profile", "fallback"],
"properties": {
"grammar": { "enum": ["commerce-conversion", "consumer-service", "editorial-reading", "expressive-marketing", "institutional-service", "operations-console", "sequential-story", "technical-instrument"] },
"adapter": { "enum": [null, "document-report", "product-ui", "single-frame", "slide-deck", "social-carousel"] },
"domain": { "enum": [null, "ai-assistant", "booking", "content", "developer-tools", "ecommerce", "education", "fintech", "health", "marketplace", "none", "productivity", "saas", "social"] },
"page": { "enum": ["dashboard", "detail", "form", "landing", "list", "none", "onboarding", "settings"] },
"recipe": { "enum": [null, "calm-consumer", "commerce-operator", "creative-professional", "developer-platform", "editorial-authority", "enterprise-workbench", "expressive-brand", "native-mobile", "public-service"] },
"palette": { "enum": [null, "civic-blue", "cobalt-instrument", "deep-lime-studio", "editorial-ink", "nocturne-violet", "quiet-mineral", "signal-coral", "warm-clay-commerce"] },
"profile": { "enum": [null, "brutalist-lite", "editorial", "minimal-mono", "none", "swiss", "technical", "warm-dtc"] },
"fallback": { "enum": [null, "commerce-conversion", "consumer-service", "editorial-reading", "expressive-marketing", "institutional-service", "operations-console", "sequential-story", "technical-instrument"] }
}
},
"decisions": { "type": "object", "additionalProperties": false, "required": ["primaryDecision", "primaryAction", "signatureMove"], "properties": { "primaryDecision": { "$ref": "#/$defs/text" }, "primaryAction": { "$ref": "#/$defs/text" }, "signatureMove": { "$ref": "#/$defs/text" } } },
"implementation": { "type": "object", "additionalProperties": false, "required": ["sourceRoots", "tokenFiles"], "properties": { "sourceRoots": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/path" } }, "tokenFiles": { "type": "array", "items": { "$ref": "#/$defs/path" } } } },
"validation": {
"type": "object", "additionalProperties": false, "required": ["scoreFloor", "requiredRenders", "temporal", "humanAcceptance"],
"properties": {
"scoreFloor": { "type": "integer", "minimum": 80, "maximum": 100 },
"requiredRenders": {
"type": "array", "minItems": 1,
"items": {
"type": "object", "additionalProperties": false, "required": ["id", "state", "viewport"],
"properties": {
"id": { "$ref": "#/$defs/id" },
"state": { "enum": ["loaded", "loading", "empty", "error", "success", "disabled", "focused", "reduced-motion"] },
"viewport": {
"type": "object", "additionalProperties": false, "required": ["width", "height"],
"properties": { "width": { "type": "integer", "minimum": 1, "maximum": 10000 }, "height": { "type": "integer", "minimum": 1, "maximum": 10000 } }
}
}
}
},
"temporal": { "type": "object", "additionalProperties": false, "required": ["required", "scenarios"], "properties": { "required": { "type": "boolean" }, "scenarios": { "type": "array", "items": { "$ref": "#/$defs/id" } } } },
"humanAcceptance": { "type": "boolean" }
}
}
},
"$defs": {
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" },
"text": { "type": "string", "minLength": 1, "maxLength": 600, "pattern": "^[^\\u0000-\\u001f\\u007f]+$" },
"path": { "type": "string", "minLength": 1, "maxLength": 240, "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*[\\u0000-\\u001f\\u007f]).+$" }
}
}
references/catalog.json
{
"schemaVersion": 5,
"engineVersion": "4.1.0",
"engineRevision": "sha256:9e707554af2244788bb7c7ebcafb1ba32c364265ceefb879a509a1a2271ba815",
"distributionSource": {
"schemaVersion": 1,
"channel": "edge",
"updateManifest": "https://styleseed-demo.vercel.app/version.json",
"install": "npx skills add bitjaru/styleseed"
},
"distributions": {
"core": {
"revision": "sha256:9e707554af2244788bb7c7ebcafb1ba32c364265ceefb879a509a1a2271ba815",
"files": [
{
"path": "LICENSE",
"sha256": "2de9d43f4c5a690a3a8e01a58fd906c259f2b70e7903805b702982ec52f23b43",
"bytes": 1079
},
{
"path": "SECURITY.md",
"sha256": "535c1660f8bf135cd5cf9bccc497cce35255165eec7f352bc57c439a2239b8b6",
"bytes": 1951
},
{
"path": "engine/.claude/skills/ss-a11y/SKILL.md",
"sha256": "866c41a61b6b295b84d000938c3380171f527c7610ac17367c93e3eb7111595f",
"bytes": 3405
},
{
"path": "engine/.claude/skills/ss-audit/SKILL.md",
"sha256": "77dd98c3cc465a1013f3f8d9dd19451c611449c25c302ca3a095946193083ab6",
"bytes": 5270
},
{
"path": "engine/.claude/skills/ss-build/SKILL.md",
"sha256": "6da3ee935fc1c86d52017f90343f67923c3a596dcee44ff7b055c584e9b58b03",
"bytes": 4233
},
{
"path": "engine/.claude/skills/ss-component/SKILL.md",
"sha256": "ef3259b045b0e35b1d843614bd81290d2cdca454b9a14ed4c137b7d630cdf0b4",
"bytes": 3060
},
{
"path": "engine/.claude/skills/ss-copy/SKILL.md",
"sha256": "1e946cca20fe590814a40650880838a0d6fdb8e8432135d12dd56c077a89305a",
"bytes": 3774
},
{
"path": "engine/.claude/skills/ss-dial/SKILL.md",
"sha256": "cb2961f041fc3ece017fec2835c197d5d4d8eb806c70ca1ecedaea1aa422dfbe",
"bytes": 9914
},
{
"path": "engine/.claude/skills/ss-feedback/SKILL.md",
"sha256": "0668bc271a8c4e45ff412c1671cf5b11c88073b5c0af8d2549303dd102d1e3e6",
"bytes": 3689
},
{
"path": "engine/.claude/skills/ss-flow/SKILL.md",
"sha256": "f4c16d0daa05b4ccfe03a03b8aaa960dcc84f87a3bac2c7563858d22a3fcc1d1",
"bytes": 3219
},
{
"path": "engine/.claude/skills/ss-lint/SKILL.md",
"sha256": "d7949e53db1dbd5a9324f5938d8da8366a47d5ffb91d0f417c430d87d56bae9b",
"bytes": 4619
},
{
"path": "engine/.claude/skills/ss-motion/SKILL.md",
"sha256": "50aa240d681a68cec4c493dd0230e3046d8ed2b62a66334a68f540353f88f0c9",
"bytes": 10417
},
{
"path": "engine/.claude/skills/ss-page/SKILL.md",
"sha256": "261403a913978cd289e06686c99e0f8d3d8d0674749121ec1ca528b63ed1f9e9",
"bytes": 3766
},
{
"path": "engine/.claude/skills/ss-pattern/SKILL.md",
"sha256": "9532150f6732c541a00fdedf428b07a7e9895b47ecd56d4b02d17020a2b5bcca",
"bytes": 3086
},
{
"path": "engine/.claude/skills/ss-reference/SKILL.md",
"sha256": "1575bbb98c05958c01a0b80e59be650a40f877eba3ef0ed37023e7a7df567846",
"bytes": 2638
},
{
"path": "engine/.claude/skills/ss-resolve/SKILL.md",
"sha256": "678a94a773ab39a002288aa3c586cee7ded9ad3f6ee06c61d56de89b725dc306",
"bytes": 4258
},
{
"path": "engine/.claude/skills/ss-resolve/references/artifact-index.schema.json",
"sha256": "22239cff0acc869e333fef72437d323a6e7c708fefbd6a51e6ea35189660fa38",
"bytes": 639
},
{
"path": "engine/.claude/skills/ss-resolve/references/artifact.schema.json",
"sha256": "53e7755ea2956ea0a29477aa35ad28796ec09fca05dacc8ce855ff31a5bcb1d2",
"bytes": 4535
},
{
"path": "engine/.claude/skills/ss-resolve/references/manifest.schema.json",
"sha256": "894f9bff3146df9ad1d7391e1de87c69339cae69b58086d3819d7568abf486de",
"bytes": 2221
},
{
"path": "engine/.claude/skills/ss-resolve/references/project.schema.json",
"sha256": "ec1fc187d0ebe2c627d68e322a8dd07c74a1696504a82054d1725bd9ad341b23",
"bytes": 3279
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/compiler.mjs",
"sha256": "0a0469345d9c553b2912b09ff0663297dfd8e0db60b294e6c48406cfd737749b",
"bytes": 19123
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/distribution-integrity.mjs",
"sha256": "067f80bb2f6b881e840c5c3e7ffe00bc1d744fb477059c887ea23cabc981e1ba",
"bytes": 6480
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/migrate-project.mjs",
"sha256": "3dd61030a9925f4128c95d3f8948986b082af77ed5f313400846e80b072dc6d3",
"bytes": 17044
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/palette-generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/project-registry.mjs",
"sha256": "bceb386c75778fdfd5f91253af4cbb5f349c28c063392bb986a8a6e6df6217c5",
"bytes": 3449
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/resolve-context.mjs",
"sha256": "834b9acc27137a86b1fbd210703a00d8a32e62df9d74760a1a7d3550b790e92a",
"bytes": 16677
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/runtime-contract.mjs",
"sha256": "d1e26fe07911b6dd640bf7514b955b41c159264dfa6dc6b0cd60103cef40c611",
"bytes": 17130
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/styleseed-doctor.mjs",
"sha256": "4765d74f759ef10e8c9adb9807d65467e3f08a3f238bbb6d3ec4bf95ccb5bc1f",
"bytes": 10161
},
{
"path": "engine/.claude/skills/ss-restyle/SKILL.md",
"sha256": "fa451824b42a802728897111a02820fb4b4573d9f48be4c0dbfd40a2bb85a07e",
"bytes": 2202
},
{
"path": "engine/.claude/skills/ss-review/SKILL.md",
"sha256": "e543a87448660c79abd66576768847348fde0200543a28a1a8f9be815ffba6b1",
"bytes": 5520
},
{
"path": "engine/.claude/skills/ss-score/SKILL.md",
"sha256": "c8a8bb0e903afdf66b583f148f9392fec56ee09bb96d2b63da2fd604f243f346",
"bytes": 12500
},
{
"path": "engine/.claude/skills/ss-score/references/acceptance-report.schema.json",
"sha256": "e7214f274ebced91c0dae084f7306a9b42a186ea64cb75aaa2fd9eb2a87ed46f",
"bytes": 434
},
{
"path": "engine/.claude/skills/ss-score/references/code-report.schema.json",
"sha256": "318bab68589e7b065cb4944d65d7dcf0070ca0861d9e94fc958a30347d7d66ce",
"bytes": 344
},
{
"path": "engine/.claude/skills/ss-score/references/deterministic-report.schema.json",
"sha256": "0c37c923772377ed2d770cedb4de8dd6d26ff773ff6b596d6a840114b665a985",
"bytes": 350
},
{
"path": "engine/.claude/skills/ss-score/references/gate-run.schema.json",
"sha256": "6327078f855176822d6e3f4013ddf6bf23849633c072565e5a3a2f79518d7181",
"bytes": 1738
},
{
"path": "engine/.claude/skills/ss-score/references/temporal-report.schema.json",
"sha256": "44b5c39926719d2022a2e5c057c2c3ed1fd7f65397e5b5fb07a4090fde2cba95",
"bytes": 276
},
{
"path": "engine/.claude/skills/ss-score/references/visual-report.schema.json",
"sha256": "5dc1fd0eb0015c56c7a1be244f6f20933349b873378c852553d20f9e1a167159",
"bytes": 301
},
{
"path": "engine/.claude/skills/ss-score/scripts/evidence-contract.mjs",
"sha256": "6a74b4a692963cbb8a58b19ead4cfa16b9ed8b990c99c4cb4ba5cdcf2e0b38be",
"bytes": 14337
},
{
"path": "engine/.claude/skills/ss-score/scripts/evidence-gate.mjs",
"sha256": "2d01a036c8d2007e37c94fd91de26d5445951e13f233a92d0ecd8fa13c6b55a7",
"bytes": 16858
},
{
"path": "engine/.claude/skills/ss-score/scripts/styleseed-check.mjs",
"sha256": "6c06648e9925bcff5e80180ddeff477ada11b54f9b86fbbbdf775836988bb1ea",
"bytes": 13478
},
{
"path": "engine/.claude/skills/ss-setup/SKILL.md",
"sha256": "fd25cbebb6d32e2c9f7c30f089d0b8f11019954e7c2c9c3896a0f49960b3e9ed",
"bytes": 6493
},
{
"path": "engine/.claude/skills/ss-studio/SKILL.md",
"sha256": "4e97714d2f757c06ee3b3ba8692ef5edace25cedc4e97b21d8033c265825b513",
"bytes": 7034
},
{
"path": "engine/.claude/skills/ss-studio/agents/openai.yaml",
"sha256": "03001a5bba8a50f13c1b36f1624b60794f25521f43ed680133803b87af0d5f82",
"bytes": 372
},
{
"path": "engine/.claude/skills/ss-studio/references/artifact-contract.md",
"sha256": "98a20c0a7cd4299ab6a81852d710f4540b6b1a73cfc53b5d52a6744010065766",
"bytes": 4393
},
{
"path": "engine/.claude/skills/ss-studio/references/provider-adapters.md",
"sha256": "2447093b277509bfa9f73a194bf90e136cac3464085a23b0ddb3ba27e39ad421",
"bytes": 1803
},
{
"path": "engine/.claude/skills/ss-studio/scripts/studio-run.mjs",
"sha256": "4369429176cc35ce88d88c90981045f2070a49868dc5685d063c15ffa0a2189b",
"bytes": 19179
},
{
"path": "engine/.claude/skills/ss-tokens/SKILL.md",
"sha256": "7b2d47255cb83f7674cefa79991ccb2b0b6a716c3c6536ee2aa7e3ff6c2d0583",
"bytes": 4073
},
{
"path": "engine/.claude/skills/ss-tokens/scripts/generate-palette.mjs",
"sha256": "001de08269e3065d7e08e79bd64530f1bc56c4b4f0aaa6dd40ee0ae7f539a819",
"bytes": 2545
},
{
"path": "engine/.claude/skills/ss-tokens/scripts/generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": "engine/.claude/skills/ss-update/SKILL.md",
"sha256": "daeaa0c0a210e951ac55e2e03161efd3be3ad74c6928a1c620a25690bfe06e6e",
"bytes": 9159
},
{
"path": "engine/.claude/skills/ss-update/scripts/artifact-impact.mjs",
"sha256": "e10b5da5a58ebd73552c162cd5b7b1cbb0a97a7c1bac33f22026e9836c21969d",
"bytes": 6241
},
{
"path": "engine/.claude/skills/ss-update/scripts/check-update.mjs",
"sha256": "642331d8d91d06109e537eea76a8ad97ec9913b8ecdf64e285a3b7547dea3595",
"bytes": 11238
},
{
"path": "engine/.claude/skills/ss-verify/SKILL.md",
"sha256": "55446eb821129be6bac83a5a1ebd6bcfccb67649156d7ea92d8418770aa60a3f",
"bytes": 8632
},
{
"path": "engine/.claude/skills/styleseed/SKILL.md",
"sha256": "3837248e3a82505a140818c9043882b17255891e285c907132b2551c6ab18bfc",
"bytes": 4246
},
{
"path": "engine/.cursorrules",
"sha256": "f78397089017acad4a4c012d324778c770d933c09c34b15ff8b6db8e60b9e82a",
"bytes": 2769
},
{
"path": "engine/ADAPTERS.md",
"sha256": "e9f295540d331a517edbf956a1549d24077943cccc7b54532e9a1f7255380d7f",
"bytes": 5921
},
{
"path": "engine/AGENTS.md",
"sha256": "87066141feec85f1986ca8b9ebdafcba045f6097adcefc919ab6d72f70a59bfd",
"bytes": 8043
},
{
"path": "engine/APP-PLAYBOOKS.md",
"sha256": "cfa8709498024cab62de55879c1a15e60f2e005a2923f542da7ec3ae574161ed",
"bytes": 13880
},
{
"path": "engine/ARCHITECTURE.md",
"sha256": "58301e3ad7aebba1ef9ed9d058042e632d836db47e1bff90f03e4c31d361945d",
"bytes": 8858
},
{
"path": "engine/BRAND-RECIPES.md",
"sha256": "c942ad7138e00659c757ae9bedb3fb28775265de77e26f1b31017bce7bf1706f",
"bytes": 15861
},
{
"path": "engine/CLAUDE.md",
"sha256": "4c3ac7b80cf9c22c8f0d4b6679603eb52e0910183c05f09a28febd739f6e9580",
"bytes": 50740
},
{
"path": "engine/CRAFT-BASELINE.md",
"sha256": "6b5ac0ee36ae20654d421ac71c373cfe63dbbd626082b62dbe17584fc87f3aa1",
"bytes": 3565
},
{
"path": "engine/DESIGN-LANGUAGE.md",
"sha256": "d38a33f86be228caf3f55ac79ec204e9bb5839d139f0400b9cfbcffc07b9a4ab",
"bytes": 107295
},
{
"path": "engine/METHODOLOGY.md",
"sha256": "cc5154aa5b688c8b74ee4f794c1675b81157c9c879272ad20b419fb29a7f487e",
"bytes": 16113
},
{
"path": "engine/PAGE-TYPES.md",
"sha256": "49e072506100820ac9d779ef8d54b328919a09c8c8ce322c271b99ed8419f0ea",
"bytes": 7962
},
{
"path": "engine/PALETTE-RECIPES.md",
"sha256": "4c0e7537d577b183ed9acbc7be5f7477c6178dbae93f5d7a9d302e7e9b28a435",
"bytes": 6978
},
{
"path": "engine/PRESETS.md",
"sha256": "51e3678855dacf8811668acd5ed6e736e3fea908458b87ab1a64942e900c0378",
"bytes": 1597
},
{
"path": "engine/PRODUCT-PRINCIPLES.md",
"sha256": "260aee0f57764b8dc53752ddc1755466a88a0a0ab21f1523f5e8a65038104109",
"bytes": 5459
},
{
"path": "engine/README.md",
"sha256": "2cf8d5a6a721f270c427d676a3b45ba1a24243afd31d1533ba612e2b6c22d8e4",
"bytes": 1902
},
{
"path": "engine/REFERENCE-COMPILER.md",
"sha256": "17cced4ab383ceda245c094ae457d9ca29b98155d00c4c68d8bd5958e1855e82",
"bytes": 3737
},
{
"path": "engine/RULESETS.md",
"sha256": "7ef0d0286f1b80c0069f931bdbc62ea63fa6fe68f416d5a0c977723f59c0c270",
"bytes": 10680
},
{
"path": "engine/STUDIO-PIPELINE.md",
"sha256": "4df215044c319ea0c909763a48d30ff18cc3236f7b8694ee126e743a0bfb8be8",
"bytes": 6445
},
{
"path": "engine/UPDATE.md",
"sha256": "2eafeb45c0f207b9c3df6fc84d095be74c758f9e8a1d41abb62fd3bd0d5c3b30",
"bytes": 4725
},
{
"path": "engine/UX-WRITING.md",
"sha256": "8762afc773f31bfef606e2ddf7dcf5c68a648cfb087822f80b54bddfd063090c",
"bytes": 9830
},
{
"path": "engine/VERSION",
"sha256": "3b10b6ad566eadbcacadb33c591f1ec629593d6adf47442e56e0f61996829ef7",
"bytes": 6
},
{
"path": "engine/VISUAL-CRAFT.md",
"sha256": "cf1e820187e4b754a6e36d84e5d6ede54794cad784a0f8685ce9a884f6bae5bf",
"bytes": 25351
},
{
"path": "engine/color/generate-palette.mjs",
"sha256": "001de08269e3065d7e08e79bd64530f1bc56c4b4f0aaa6dd40ee0ae7f539a819",
"bytes": 2545
},
{
"path": "engine/color/generator.d.mts",
"sha256": "2a047fa4c03b6b0a2764e507d31d8f1047533a70b304674a7459a680a71be64d",
"bytes": 1780
},
{
"path": "engine/color/generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": "engine/color/index.ts",
"sha256": "18a236a1a65b97b8ddda81b060fa1482814a24cc7b8ae13e92428bc71dc6c466",
"bytes": 370
},
{
"path": "engine/color/palettes.json",
"sha256": "d4d52423a078140f672820f13378b8e63f1ab97431542feced7f903927099dc5",
"bytes": 8967
}
]
},
"skills": {
"revision": "sha256:780df1cbc5abb4673fa75c0d1e39c876305dd7aec978a0c97f30f396e485a9a2",
"files": [
{
"path": "engine/.claude/skills/ss-a11y/SKILL.md",
"sha256": "866c41a61b6b295b84d000938c3380171f527c7610ac17367c93e3eb7111595f",
"bytes": 3405
},
{
"path": "engine/.claude/skills/ss-audit/SKILL.md",
"sha256": "77dd98c3cc465a1013f3f8d9dd19451c611449c25c302ca3a095946193083ab6",
"bytes": 5270
},
{
"path": "engine/.claude/skills/ss-build/SKILL.md",
"sha256": "6da3ee935fc1c86d52017f90343f67923c3a596dcee44ff7b055c584e9b58b03",
"bytes": 4233
},
{
"path": "engine/.claude/skills/ss-component/SKILL.md",
"sha256": "ef3259b045b0e35b1d843614bd81290d2cdca454b9a14ed4c137b7d630cdf0b4",
"bytes": 3060
},
{
"path": "engine/.claude/skills/ss-copy/SKILL.md",
"sha256": "1e946cca20fe590814a40650880838a0d6fdb8e8432135d12dd56c077a89305a",
"bytes": 3774
},
{
"path": "engine/.claude/skills/ss-dial/SKILL.md",
"sha256": "cb2961f041fc3ece017fec2835c197d5d4d8eb806c70ca1ecedaea1aa422dfbe",
"bytes": 9914
},
{
"path": "engine/.claude/skills/ss-feedback/SKILL.md",
"sha256": "0668bc271a8c4e45ff412c1671cf5b11c88073b5c0af8d2549303dd102d1e3e6",
"bytes": 3689
},
{
"path": "engine/.claude/skills/ss-flow/SKILL.md",
"sha256": "f4c16d0daa05b4ccfe03a03b8aaa960dcc84f87a3bac2c7563858d22a3fcc1d1",
"bytes": 3219
},
{
"path": "engine/.claude/skills/ss-lint/SKILL.md",
"sha256": "d7949e53db1dbd5a9324f5938d8da8366a47d5ffb91d0f417c430d87d56bae9b",
"bytes": 4619
},
{
"path": "engine/.claude/skills/ss-motion/SKILL.md",
"sha256": "50aa240d681a68cec4c493dd0230e3046d8ed2b62a66334a68f540353f88f0c9",
"bytes": 10417
},
{
"path": "engine/.claude/skills/ss-page/SKILL.md",
"sha256": "261403a913978cd289e06686c99e0f8d3d8d0674749121ec1ca528b63ed1f9e9",
"bytes": 3766
},
{
"path": "engine/.claude/skills/ss-pattern/SKILL.md",
"sha256": "9532150f6732c541a00fdedf428b07a7e9895b47ecd56d4b02d17020a2b5bcca",
"bytes": 3086
},
{
"path": "engine/.claude/skills/ss-reference/SKILL.md",
"sha256": "1575bbb98c05958c01a0b80e59be650a40f877eba3ef0ed37023e7a7df567846",
"bytes": 2638
},
{
"path": "engine/.claude/skills/ss-resolve/SKILL.md",
"sha256": "678a94a773ab39a002288aa3c586cee7ded9ad3f6ee06c61d56de89b725dc306",
"bytes": 4258
},
{
"path": "engine/.claude/skills/ss-resolve/references/artifact-index.schema.json",
"sha256": "22239cff0acc869e333fef72437d323a6e7c708fefbd6a51e6ea35189660fa38",
"bytes": 639
},
{
"path": "engine/.claude/skills/ss-resolve/references/artifact.schema.json",
"sha256": "53e7755ea2956ea0a29477aa35ad28796ec09fca05dacc8ce855ff31a5bcb1d2",
"bytes": 4535
},
{
"path": "engine/.claude/skills/ss-resolve/references/manifest.schema.json",
"sha256": "894f9bff3146df9ad1d7391e1de87c69339cae69b58086d3819d7568abf486de",
"bytes": 2221
},
{
"path": "engine/.claude/skills/ss-resolve/references/project.schema.json",
"sha256": "ec1fc187d0ebe2c627d68e322a8dd07c74a1696504a82054d1725bd9ad341b23",
"bytes": 3279
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/compiler.mjs",
"sha256": "0a0469345d9c553b2912b09ff0663297dfd8e0db60b294e6c48406cfd737749b",
"bytes": 19123
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/distribution-integrity.mjs",
"sha256": "067f80bb2f6b881e840c5c3e7ffe00bc1d744fb477059c887ea23cabc981e1ba",
"bytes": 6480
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/migrate-project.mjs",
"sha256": "3dd61030a9925f4128c95d3f8948986b082af77ed5f313400846e80b072dc6d3",
"bytes": 17044
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/palette-generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/project-registry.mjs",
"sha256": "bceb386c75778fdfd5f91253af4cbb5f349c28c063392bb986a8a6e6df6217c5",
"bytes": 3449
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/resolve-context.mjs",
"sha256": "834b9acc27137a86b1fbd210703a00d8a32e62df9d74760a1a7d3550b790e92a",
"bytes": 16677
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/runtime-contract.mjs",
"sha256": "d1e26fe07911b6dd640bf7514b955b41c159264dfa6dc6b0cd60103cef40c611",
"bytes": 17130
},
{
"path": "engine/.claude/skills/ss-resolve/scripts/styleseed-doctor.mjs",
"sha256": "4765d74f759ef10e8c9adb9807d65467e3f08a3f238bbb6d3ec4bf95ccb5bc1f",
"bytes": 10161
},
{
"path": "engine/.claude/skills/ss-restyle/SKILL.md",
"sha256": "fa451824b42a802728897111a02820fb4b4573d9f48be4c0dbfd40a2bb85a07e",
"bytes": 2202
},
{
"path": "engine/.claude/skills/ss-review/SKILL.md",
"sha256": "e543a87448660c79abd66576768847348fde0200543a28a1a8f9be815ffba6b1",
"bytes": 5520
},
{
"path": "engine/.claude/skills/ss-score/SKILL.md",
"sha256": "c8a8bb0e903afdf66b583f148f9392fec56ee09bb96d2b63da2fd604f243f346",
"bytes": 12500
},
{
"path": "engine/.claude/skills/ss-score/references/acceptance-report.schema.json",
"sha256": "e7214f274ebced91c0dae084f7306a9b42a186ea64cb75aaa2fd9eb2a87ed46f",
"bytes": 434
},
{
"path": "engine/.claude/skills/ss-score/references/code-report.schema.json",
"sha256": "318bab68589e7b065cb4944d65d7dcf0070ca0861d9e94fc958a30347d7d66ce",
"bytes": 344
},
{
"path": "engine/.claude/skills/ss-score/references/deterministic-report.schema.json",
"sha256": "0c37c923772377ed2d770cedb4de8dd6d26ff773ff6b596d6a840114b665a985",
"bytes": 350
},
{
"path": "engine/.claude/skills/ss-score/references/gate-run.schema.json",
"sha256": "6327078f855176822d6e3f4013ddf6bf23849633c072565e5a3a2f79518d7181",
"bytes": 1738
},
{
"path": "engine/.claude/skills/ss-score/references/temporal-report.schema.json",
"sha256": "44b5c39926719d2022a2e5c057c2c3ed1fd7f65397e5b5fb07a4090fde2cba95",
"bytes": 276
},
{
"path": "engine/.claude/skills/ss-score/references/visual-report.schema.json",
"sha256": "5dc1fd0eb0015c56c7a1be244f6f20933349b873378c852553d20f9e1a167159",
"bytes": 301
},
{
"path": "engine/.claude/skills/ss-score/scripts/evidence-contract.mjs",
"sha256": "6a74b4a692963cbb8a58b19ead4cfa16b9ed8b990c99c4cb4ba5cdcf2e0b38be",
"bytes": 14337
},
{
"path": "engine/.claude/skills/ss-score/scripts/evidence-gate.mjs",
"sha256": "2d01a036c8d2007e37c94fd91de26d5445951e13f233a92d0ecd8fa13c6b55a7",
"bytes": 16858
},
{
"path": "engine/.claude/skills/ss-score/scripts/styleseed-check.mjs",
"sha256": "6c06648e9925bcff5e80180ddeff477ada11b54f9b86fbbbdf775836988bb1ea",
"bytes": 13478
},
{
"path": "engine/.claude/skills/ss-setup/SKILL.md",
"sha256": "fd25cbebb6d32e2c9f7c30f089d0b8f11019954e7c2c9c3896a0f49960b3e9ed",
"bytes": 6493
},
{
"path": "engine/.claude/skills/ss-studio/SKILL.md",
"sha256": "4e97714d2f757c06ee3b3ba8692ef5edace25cedc4e97b21d8033c265825b513",
"bytes": 7034
},
{
"path": "engine/.claude/skills/ss-studio/agents/openai.yaml",
"sha256": "03001a5bba8a50f13c1b36f1624b60794f25521f43ed680133803b87af0d5f82",
"bytes": 372
},
{
"path": "engine/.claude/skills/ss-studio/references/artifact-contract.md",
"sha256": "98a20c0a7cd4299ab6a81852d710f4540b6b1a73cfc53b5d52a6744010065766",
"bytes": 4393
},
{
"path": "engine/.claude/skills/ss-studio/references/provider-adapters.md",
"sha256": "2447093b277509bfa9f73a194bf90e136cac3464085a23b0ddb3ba27e39ad421",
"bytes": 1803
},
{
"path": "engine/.claude/skills/ss-studio/scripts/studio-run.mjs",
"sha256": "4369429176cc35ce88d88c90981045f2070a49868dc5685d063c15ffa0a2189b",
"bytes": 19179
},
{
"path": "engine/.claude/skills/ss-tokens/SKILL.md",
"sha256": "7b2d47255cb83f7674cefa79991ccb2b0b6a716c3c6536ee2aa7e3ff6c2d0583",
"bytes": 4073
},
{
"path": "engine/.claude/skills/ss-tokens/scripts/generate-palette.mjs",
"sha256": "001de08269e3065d7e08e79bd64530f1bc56c4b4f0aaa6dd40ee0ae7f539a819",
"bytes": 2545
},
{
"path": "engine/.claude/skills/ss-tokens/scripts/generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": "engine/.claude/skills/ss-update/SKILL.md",
"sha256": "daeaa0c0a210e951ac55e2e03161efd3be3ad74c6928a1c620a25690bfe06e6e",
"bytes": 9159
},
{
"path": "engine/.claude/skills/ss-update/scripts/artifact-impact.mjs",
"sha256": "e10b5da5a58ebd73552c162cd5b7b1cbb0a97a7c1bac33f22026e9836c21969d",
"bytes": 6241
},
{
"path": "engine/.claude/skills/ss-update/scripts/check-update.mjs",
"sha256": "642331d8d91d06109e537eea76a8ad97ec9913b8ecdf64e285a3b7547dea3595",
"bytes": 11238
},
{
"path": "engine/.claude/skills/ss-verify/SKILL.md",
"sha256": "55446eb821129be6bac83a5a1ebd6bcfccb67649156d7ea92d8418770aa60a3f",
"bytes": 8632
},
{
"path": "engine/.claude/skills/styleseed/SKILL.md",
"sha256": "3837248e3a82505a140818c9043882b17255891e285c907132b2551c6ab18bfc",
"bytes": 4246
}
]
}
},
"distributionFiles": [
{
"path": "root/LICENSE",
"sha256": "2de9d43f4c5a690a3a8e01a58fd906c259f2b70e7903805b702982ec52f23b43",
"bytes": 1079
},
{
"path": "root/SECURITY.md",
"sha256": "535c1660f8bf135cd5cf9bccc497cce35255165eec7f352bc57c439a2239b8b6",
"bytes": 1951
},
{
"path": ".claude/skills/ss-a11y/SKILL.md",
"sha256": "866c41a61b6b295b84d000938c3380171f527c7610ac17367c93e3eb7111595f",
"bytes": 3405
},
{
"path": ".claude/skills/ss-audit/SKILL.md",
"sha256": "77dd98c3cc465a1013f3f8d9dd19451c611449c25c302ca3a095946193083ab6",
"bytes": 5270
},
{
"path": ".claude/skills/ss-build/SKILL.md",
"sha256": "6da3ee935fc1c86d52017f90343f67923c3a596dcee44ff7b055c584e9b58b03",
"bytes": 4233
},
{
"path": ".claude/skills/ss-component/SKILL.md",
"sha256": "ef3259b045b0e35b1d843614bd81290d2cdca454b9a14ed4c137b7d630cdf0b4",
"bytes": 3060
},
{
"path": ".claude/skills/ss-copy/SKILL.md",
"sha256": "1e946cca20fe590814a40650880838a0d6fdb8e8432135d12dd56c077a89305a",
"bytes": 3774
},
{
"path": ".claude/skills/ss-dial/SKILL.md",
"sha256": "cb2961f041fc3ece017fec2835c197d5d4d8eb806c70ca1ecedaea1aa422dfbe",
"bytes": 9914
},
{
"path": ".claude/skills/ss-feedback/SKILL.md",
"sha256": "0668bc271a8c4e45ff412c1671cf5b11c88073b5c0af8d2549303dd102d1e3e6",
"bytes": 3689
},
{
"path": ".claude/skills/ss-flow/SKILL.md",
"sha256": "f4c16d0daa05b4ccfe03a03b8aaa960dcc84f87a3bac2c7563858d22a3fcc1d1",
"bytes": 3219
},
{
"path": ".claude/skills/ss-lint/SKILL.md",
"sha256": "d7949e53db1dbd5a9324f5938d8da8366a47d5ffb91d0f417c430d87d56bae9b",
"bytes": 4619
},
{
"path": ".claude/skills/ss-motion/SKILL.md",
"sha256": "50aa240d681a68cec4c493dd0230e3046d8ed2b62a66334a68f540353f88f0c9",
"bytes": 10417
},
{
"path": ".claude/skills/ss-page/SKILL.md",
"sha256": "261403a913978cd289e06686c99e0f8d3d8d0674749121ec1ca528b63ed1f9e9",
"bytes": 3766
},
{
"path": ".claude/skills/ss-pattern/SKILL.md",
"sha256": "9532150f6732c541a00fdedf428b07a7e9895b47ecd56d4b02d17020a2b5bcca",
"bytes": 3086
},
{
"path": ".claude/skills/ss-reference/SKILL.md",
"sha256": "1575bbb98c05958c01a0b80e59be650a40f877eba3ef0ed37023e7a7df567846",
"bytes": 2638
},
{
"path": ".claude/skills/ss-resolve/SKILL.md",
"sha256": "678a94a773ab39a002288aa3c586cee7ded9ad3f6ee06c61d56de89b725dc306",
"bytes": 4258
},
{
"path": ".claude/skills/ss-resolve/references/artifact-index.schema.json",
"sha256": "22239cff0acc869e333fef72437d323a6e7c708fefbd6a51e6ea35189660fa38",
"bytes": 639
},
{
"path": ".claude/skills/ss-resolve/references/artifact.schema.json",
"sha256": "53e7755ea2956ea0a29477aa35ad28796ec09fca05dacc8ce855ff31a5bcb1d2",
"bytes": 4535
},
{
"path": ".claude/skills/ss-resolve/references/manifest.schema.json",
"sha256": "894f9bff3146df9ad1d7391e1de87c69339cae69b58086d3819d7568abf486de",
"bytes": 2221
},
{
"path": ".claude/skills/ss-resolve/references/project.schema.json",
"sha256": "ec1fc187d0ebe2c627d68e322a8dd07c74a1696504a82054d1725bd9ad341b23",
"bytes": 3279
},
{
"path": ".claude/skills/ss-resolve/scripts/compiler.mjs",
"sha256": "0a0469345d9c553b2912b09ff0663297dfd8e0db60b294e6c48406cfd737749b",
"bytes": 19123
},
{
"path": ".claude/skills/ss-resolve/scripts/distribution-integrity.mjs",
"sha256": "067f80bb2f6b881e840c5c3e7ffe00bc1d744fb477059c887ea23cabc981e1ba",
"bytes": 6480
},
{
"path": ".claude/skills/ss-resolve/scripts/migrate-project.mjs",
"sha256": "3dd61030a9925f4128c95d3f8948986b082af77ed5f313400846e80b072dc6d3",
"bytes": 17044
},
{
"path": ".claude/skills/ss-resolve/scripts/palette-generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": ".claude/skills/ss-resolve/scripts/project-registry.mjs",
"sha256": "bceb386c75778fdfd5f91253af4cbb5f349c28c063392bb986a8a6e6df6217c5",
"bytes": 3449
},
{
"path": ".claude/skills/ss-resolve/scripts/resolve-context.mjs",
"sha256": "834b9acc27137a86b1fbd210703a00d8a32e62df9d74760a1a7d3550b790e92a",
"bytes": 16677
},
{
"path": ".claude/skills/ss-resolve/scripts/runtime-contract.mjs",
"sha256": "d1e26fe07911b6dd640bf7514b955b41c159264dfa6dc6b0cd60103cef40c611",
"bytes": 17130
},
{
"path": ".claude/skills/ss-resolve/scripts/styleseed-doctor.mjs",
"sha256": "4765d74f759ef10e8c9adb9807d65467e3f08a3f238bbb6d3ec4bf95ccb5bc1f",
"bytes": 10161
},
{
"path": ".claude/skills/ss-restyle/SKILL.md",
"sha256": "fa451824b42a802728897111a02820fb4b4573d9f48be4c0dbfd40a2bb85a07e",
"bytes": 2202
},
{
"path": ".claude/skills/ss-review/SKILL.md",
"sha256": "e543a87448660c79abd66576768847348fde0200543a28a1a8f9be815ffba6b1",
"bytes": 5520
},
{
"path": ".claude/skills/ss-score/SKILL.md",
"sha256": "c8a8bb0e903afdf66b583f148f9392fec56ee09bb96d2b63da2fd604f243f346",
"bytes": 12500
},
{
"path": ".claude/skills/ss-score/references/acceptance-report.schema.json",
"sha256": "e7214f274ebced91c0dae084f7306a9b42a186ea64cb75aaa2fd9eb2a87ed46f",
"bytes": 434
},
{
"path": ".claude/skills/ss-score/references/code-report.schema.json",
"sha256": "318bab68589e7b065cb4944d65d7dcf0070ca0861d9e94fc958a30347d7d66ce",
"bytes": 344
},
{
"path": ".claude/skills/ss-score/references/deterministic-report.schema.json",
"sha256": "0c37c923772377ed2d770cedb4de8dd6d26ff773ff6b596d6a840114b665a985",
"bytes": 350
},
{
"path": ".claude/skills/ss-score/references/gate-run.schema.json",
"sha256": "6327078f855176822d6e3f4013ddf6bf23849633c072565e5a3a2f79518d7181",
"bytes": 1738
},
{
"path": ".claude/skills/ss-score/references/temporal-report.schema.json",
"sha256": "44b5c39926719d2022a2e5c057c2c3ed1fd7f65397e5b5fb07a4090fde2cba95",
"bytes": 276
},
{
"path": ".claude/skills/ss-score/references/visual-report.schema.json",
"sha256": "5dc1fd0eb0015c56c7a1be244f6f20933349b873378c852553d20f9e1a167159",
"bytes": 301
},
{
"path": ".claude/skills/ss-score/scripts/evidence-contract.mjs",
"sha256": "6a74b4a692963cbb8a58b19ead4cfa16b9ed8b990c99c4cb4ba5cdcf2e0b38be",
"bytes": 14337
},
{
"path": ".claude/skills/ss-score/scripts/evidence-gate.mjs",
"sha256": "2d01a036c8d2007e37c94fd91de26d5445951e13f233a92d0ecd8fa13c6b55a7",
"bytes": 16858
},
{
"path": ".claude/skills/ss-score/scripts/styleseed-check.mjs",
"sha256": "6c06648e9925bcff5e80180ddeff477ada11b54f9b86fbbbdf775836988bb1ea",
"bytes": 13478
},
{
"path": ".claude/skills/ss-setup/SKILL.md",
"sha256": "fd25cbebb6d32e2c9f7c30f089d0b8f11019954e7c2c9c3896a0f49960b3e9ed",
"bytes": 6493
},
{
"path": ".claude/skills/ss-studio/SKILL.md",
"sha256": "4e97714d2f757c06ee3b3ba8692ef5edace25cedc4e97b21d8033c265825b513",
"bytes": 7034
},
{
"path": ".claude/skills/ss-studio/agents/openai.yaml",
"sha256": "03001a5bba8a50f13c1b36f1624b60794f25521f43ed680133803b87af0d5f82",
"bytes": 372
},
{
"path": ".claude/skills/ss-studio/references/artifact-contract.md",
"sha256": "98a20c0a7cd4299ab6a81852d710f4540b6b1a73cfc53b5d52a6744010065766",
"bytes": 4393
},
{
"path": ".claude/skills/ss-studio/references/provider-adapters.md",
"sha256": "2447093b277509bfa9f73a194bf90e136cac3464085a23b0ddb3ba27e39ad421",
"bytes": 1803
},
{
"path": ".claude/skills/ss-studio/scripts/studio-run.mjs",
"sha256": "4369429176cc35ce88d88c90981045f2070a49868dc5685d063c15ffa0a2189b",
"bytes": 19179
},
{
"path": ".claude/skills/ss-tokens/SKILL.md",
"sha256": "7b2d47255cb83f7674cefa79991ccb2b0b6a716c3c6536ee2aa7e3ff6c2d0583",
"bytes": 4073
},
{
"path": ".claude/skills/ss-tokens/scripts/generate-palette.mjs",
"sha256": "001de08269e3065d7e08e79bd64530f1bc56c4b4f0aaa6dd40ee0ae7f539a819",
"bytes": 2545
},
{
"path": ".claude/skills/ss-tokens/scripts/generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": ".claude/skills/ss-update/SKILL.md",
"sha256": "daeaa0c0a210e951ac55e2e03161efd3be3ad74c6928a1c620a25690bfe06e6e",
"bytes": 9159
},
{
"path": ".claude/skills/ss-update/scripts/artifact-impact.mjs",
"sha256": "e10b5da5a58ebd73552c162cd5b7b1cbb0a97a7c1bac33f22026e9836c21969d",
"bytes": 6241
},
{
"path": ".claude/skills/ss-update/scripts/check-update.mjs",
"sha256": "642331d8d91d06109e537eea76a8ad97ec9913b8ecdf64e285a3b7547dea3595",
"bytes": 11238
},
{
"path": ".claude/skills/ss-verify/SKILL.md",
"sha256": "55446eb821129be6bac83a5a1ebd6bcfccb67649156d7ea92d8418770aa60a3f",
"bytes": 8632
},
{
"path": ".claude/skills/styleseed/SKILL.md",
"sha256": "3837248e3a82505a140818c9043882b17255891e285c907132b2551c6ab18bfc",
"bytes": 4246
},
{
"path": ".cursorrules",
"sha256": "f78397089017acad4a4c012d324778c770d933c09c34b15ff8b6db8e60b9e82a",
"bytes": 2769
},
{
"path": "ADAPTERS.md",
"sha256": "e9f295540d331a517edbf956a1549d24077943cccc7b54532e9a1f7255380d7f",
"bytes": 5921
},
{
"path": "AGENTS.md",
"sha256": "87066141feec85f1986ca8b9ebdafcba045f6097adcefc919ab6d72f70a59bfd",
"bytes": 8043
},
{
"path": "APP-PLAYBOOKS.md",
"sha256": "cfa8709498024cab62de55879c1a15e60f2e005a2923f542da7ec3ae574161ed",
"bytes": 13880
},
{
"path": "ARCHITECTURE.md",
"sha256": "58301e3ad7aebba1ef9ed9d058042e632d836db47e1bff90f03e4c31d361945d",
"bytes": 8858
},
{
"path": "BRAND-RECIPES.md",
"sha256": "c942ad7138e00659c757ae9bedb3fb28775265de77e26f1b31017bce7bf1706f",
"bytes": 15861
},
{
"path": "CLAUDE.md",
"sha256": "4c3ac7b80cf9c22c8f0d4b6679603eb52e0910183c05f09a28febd739f6e9580",
"bytes": 50740
},
{
"path": "CRAFT-BASELINE.md",
"sha256": "6b5ac0ee36ae20654d421ac71c373cfe63dbbd626082b62dbe17584fc87f3aa1",
"bytes": 3565
},
{
"path": "DESIGN-LANGUAGE.md",
"sha256": "d38a33f86be228caf3f55ac79ec204e9bb5839d139f0400b9cfbcffc07b9a4ab",
"bytes": 107295
},
{
"path": "METHODOLOGY.md",
"sha256": "cc5154aa5b688c8b74ee4f794c1675b81157c9c879272ad20b419fb29a7f487e",
"bytes": 16113
},
{
"path": "PAGE-TYPES.md",
"sha256": "49e072506100820ac9d779ef8d54b328919a09c8c8ce322c271b99ed8419f0ea",
"bytes": 7962
},
{
"path": "PALETTE-RECIPES.md",
"sha256": "4c0e7537d577b183ed9acbc7be5f7477c6178dbae93f5d7a9d302e7e9b28a435",
"bytes": 6978
},
{
"path": "PRESETS.md",
"sha256": "51e3678855dacf8811668acd5ed6e736e3fea908458b87ab1a64942e900c0378",
"bytes": 1597
},
{
"path": "PRODUCT-PRINCIPLES.md",
"sha256": "260aee0f57764b8dc53752ddc1755466a88a0a0ab21f1523f5e8a65038104109",
"bytes": 5459
},
{
"path": "README.md",
"sha256": "2cf8d5a6a721f270c427d676a3b45ba1a24243afd31d1533ba612e2b6c22d8e4",
"bytes": 1902
},
{
"path": "REFERENCE-COMPILER.md",
"sha256": "17cced4ab383ceda245c094ae457d9ca29b98155d00c4c68d8bd5958e1855e82",
"bytes": 3737
},
{
"path": "RULESETS.md",
"sha256": "7ef0d0286f1b80c0069f931bdbc62ea63fa6fe68f416d5a0c977723f59c0c270",
"bytes": 10680
},
{
"path": "STUDIO-PIPELINE.md",
"sha256": "4df215044c319ea0c909763a48d30ff18cc3236f7b8694ee126e743a0bfb8be8",
"bytes": 6445
},
{
"path": "UPDATE.md",
"sha256": "2eafeb45c0f207b9c3df6fc84d095be74c758f9e8a1d41abb62fd3bd0d5c3b30",
"bytes": 4725
},
{
"path": "UX-WRITING.md",
"sha256": "8762afc773f31bfef606e2ddf7dcf5c68a648cfb087822f80b54bddfd063090c",
"bytes": 9830
},
{
"path": "VERSION",
"sha256": "3b10b6ad566eadbcacadb33c591f1ec629593d6adf47442e56e0f61996829ef7",
"bytes": 6
},
{
"path": "VISUAL-CRAFT.md",
"sha256": "cf1e820187e4b754a6e36d84e5d6ede54794cad784a0f8685ce9a884f6bae5bf",
"bytes": 25351
},
{
"path": "color/generate-palette.mjs",
"sha256": "001de08269e3065d7e08e79bd64530f1bc56c4b4f0aaa6dd40ee0ae7f539a819",
"bytes": 2545
},
{
"path": "color/generator.d.mts",
"sha256": "2a047fa4c03b6b0a2764e507d31d8f1047533a70b304674a7459a680a71be64d",
"bytes": 1780
},
{
"path": "color/generator.mjs",
"sha256": "b66ee274ddefbd8a8e4ab6bebeb35d5474e772bd05174e592b16ba41bf81e131",
"bytes": 14209
},
{
"path": "color/index.ts",
"sha256": "18a236a1a65b97b8ddda81b060fa1482814a24cc7b8ae13e92428bc71dc6c466",
"bytes": 370
},
{
"path": "color/palettes.json",
"sha256": "d4d52423a078140f672820f13378b8e63f1ab97431542feced7f903927099dc5",
"bytes": 8967
}
],
"generatedFrom": [
"PRODUCT-PRINCIPLES.md",
"CRAFT-BASELINE.md",
"RULESETS.md",
"ADAPTERS.md",
"APP-PLAYBOOKS.md",
"PAGE-TYPES.md",
"BRAND-RECIPES.md",
"PALETTE-RECIPES.md",
"color/palettes.json",
"PRESETS.md"
],
"core": "# StyleSeed Product Constitution\n\nStyleSeed is an **AI design-method engine**. Its primary job is to make a coding agent\nreason like a strong UI/UX designer and apply that reasoning consistently. Components,\nskins, scores, and screenshots support that job; they do not define it.\n\n## The fixed method, not one fixed look\n\nStyleSeed enforces a stable way of judging design. It does **not** force every product to\nlook like Toss, a SaaS dashboard, or the StyleSeed demo. A trustworthy consumer-finance\nhome, an observability console, an editorial story, and a product-detail page solve\ndifferent jobs and therefore need different design grammars.\n\nThe effective rule set is composed:\n\n```text\nCore judgment\n × one output grammar from RULESETS.md (built-in or reference-compiled)\n × one surface adapter from ADAPTERS.md\n × one domain playbook from APP-PLAYBOOKS.md\n × one page type from PAGE-TYPES.md\n × one brand recipe from BRAND-RECIPES.md\n × one semantic palette posture plus an optional key-color generation from PALETTE-RECIPES.md\n × optional aesthetic profile from PRESETS.md\n × bounded project tokens from STYLESEED.md\n= the rules for this screen\n```\n\n## Authority order\n\nWhen instructions disagree, use this order:\n\n1. **Core invariants** below.\n2. **Output grammar** — the functional visual language selected in `RULESETS.md`, or a\n project-local grammar compiled by `REFERENCE-COMPILER.md`.\n3. **Surface adapter** — the renderer contract and physical constraints in `ADAPTERS.md`.\n4. **Domain playbook** and **page/artifact type**.\n5. **Brand recipe** — a reusable morphology and component-selection contract from\n `BRAND-RECIPES.md`.\n6. **Palette system** — a maintained semantic posture plus deterministic key-color generation,\n role mapping, gamut handling, and contrast correction from `PALETTE-RECIPES.md`.\n7. **Aesthetic profile** — an optional coordinated restyle from `PRESETS.md`.\n8. **Design lock** — records selections and bounded project parameters.\n9. **Skins and components** — implementation material, not sources of judgment.\n10. **Score and visual verification** — evidence that the method was applied.\n\n`STYLESEED.md` is persistence, not permission. An arbitrary value in the lock never turns\na violation into a sound design decision. Unknown values are resolver errors, not permission\nto silently choose a different grammar. Use only an explicitly configured, supported fallback.\n\n## Core invariants\n\nThese apply to every grammar, profile, domain, page, skin, and agent:\n\n- One deliberate visual system per product: radius, spacing, elevation, icon language,\n typography, color roles, imagery, and motion agree.\n- One focal point per screen and a clear information hierarchy.\n- Color communicates role or meaning. One primary action remains identifiable even when\n semantic, categorical, or product colors are present.\n- Semantic tokens replace component-level hardcoded colors.\n- Spacing follows a repeatable scale; proximity communicates grouping.\n- Typography fits the surface and task, with readable measure, contrast, and hierarchy.\n- Data surfaces include useful loading, empty, and error states.\n- Controls remain operable: visible focus, sufficient targets, labels, reduced-motion\n support, and no dark patterns.\n- Motion fits the surface and never delays comprehension or action.\n- Distinctiveness comes from the product, its content, and its selected grammar — not a\n copied demo, generic indigo, repeated icon chips, emoji chrome, or template uniformity.\n\n## What the lock may select\n\nThe lock may select only inputs the engine understands:\n\n- domain, surface adapter, page/artifact type, and one output grammar;\n- one maintained brand recipe, one semantic palette posture, optional bounded key-color generation,\n one optional aesthetic profile, and one skin or project token implementation;\n- primary action/accent, type pairing, density, radius, elevation, imagery, and motion values\n inside the selected grammar's allowed ranges;\n- one product-specific signature move that still obeys the invariants;\n- a project-local reference grammar with provenance and confidence recorded by the compiler.\n\nThe lock may not invent a palette mode, waive accessibility, legalize mixed systems, or\noverride a core invariant. A reusable built-in grammar requires research, examples, and\nregression evaluation. A reference-derived grammar stays project-local until it meets that bar.\n\n## The product loop\n\n```text\nunderstand the job → choose or compile a grammar → lock bounded decisions\n→ build with judgment → score the implementation → visually verify rendered output\n→ present the result and evidence\n```\n\nThe build method is the product. Scoring and verification are auxiliary gates: they find\ndrift, but they never choose or rewrite the design philosophy after the fact.\n\n## Learning without surveillance\n\nStyleSeed may preserve a design lesson only when a person explicitly asks it to capture an\naccepted correction. The stored candidate contains generalized conditions, counterexamples, and\nmeasured evidence—not source code, prompts, screenshots, brand tokens, product identity, or user\ncontent. Local review and export approval are separate decisions. Preparing a package does not\ntransmit it. Returning it through MCP requires another one-time human grant and must be reported as\nclient/model exposure. No candidate may automatically modify project or core rules.",
"craft": "# StyleSeed Compact Craft Baseline\n\nThis is the small, task-independent craft layer used by the Context Compiler. It preserves\nthe highest-leverage rules without injecting the full design handbook into every agent turn.\nThe selected output grammar, adapter, domain, page type, brand recipe, profile, and project lock narrow these\ndefaults. `PRODUCT-PRINCIPLES.md` remains the higher authority.\n\n## Coherence\n\n- Choose one coordinated family for radius, elevation, borders, icon style, typography, motion,\n control heights, and interaction states. Encode each family as semantic tokens and reuse it.\n- A coherent system may contain multiple component shapes and colors, but every variation must\n have a repeated role. Local decoration is not a role.\n- Preserve strengths and already-set project decisions. Do not restyle unrelated surfaces while\n fixing one screen.\n\n## Hierarchy and composition\n\n- Give every screen one dominant user decision or reading promise. Supporting content must recede\n through scale, position, contrast, density, or disclosure—not random color.\n- Proximity communicates grouping: space around a group should normally be at least twice the\n space within it. Repeated sections need deliberate variation in height, density, or composition.\n- Use real product content and evidence. Placeholder dashboards, fake metrics, stock chat panels,\n and equal card walls make otherwise clean work look generic.\n- Keep prose near 50–75 characters per line. Let data surfaces use width, but bound any reading\n column inside them.\n\n## Spacing and type\n\n- Start from `{4, 8, 12, 16, 24, 32, 40, 48, 64, 80, 96}` and use off-scale values only for a\n documented optical correction.\n- Default body text to 16px on general surfaces. Dense desktop UI may use 13–14px for metadata,\n table chrome, timestamps, or identifiers; decisions and explanatory copy stay readable.\n- Use no more than two type families and one intentional weight ramp. Use tabular numerals for\n money, tables, timers, and comparable metrics.\n- Tighten leading as type grows: body about 1.5, headings 1.2–1.35, display 1.0–1.2.\n\n## Components and interaction\n\n- Use the lightest separation that works: whitespace, then tone, then hairline, then shadow.\n Do not combine a visible border and floating shadow without a functional reason.\n- Buttons, inputs, and selects in one context share a height. Touch surfaces provide at least\n 44px targets; pointer-first dense desktop controls may be smaller when still operable.\n- Labels remain visible. Errors name the problem and recovery and never rely on color alone.\n- Use one icon family, fill mode, and stroke family. Do not use emoji as application chrome.\n- Loading, empty, error, success, disabled, and permission states preserve layout and provide a\n useful next action where the surface owns data or mutation.\n\n## Color, accessibility, and motion\n\n- Use semantic tokens. Keep one identifiable primary action while allowing stable semantic,\n categorical, or brand roles required by the selected grammar.\n- Body text targets WCAG AA contrast; large text and meaningful graphics meet their applicable\n threshold. Focus is visible, controls have names, and keyboard order follows visual order.\n- Motion uses one duration/easing family, communicates state or sequence, and never delays the\n first read or an action. Provide a complete reduced-motion result.\n- Inspect the real render at the target viewport. Source review cannot certify optical balance,\n font loading, crop, responsive transformation, or visual states.",
"grammars": {
"consumer-service": "### `consumer-service`\n\n- **Job:** understand personal state without anxiety and take one useful next action.\n- **Attention:** one dominant balance, status, benefit, or briefing; secondary services recede.\n- **Composition:** short contextual heading → dominant summary → small set of next actions →\n progressive detail. Mobile-first, thumb-reachable, explicit bottom navigation where needed.\n- **Density/type:** comfortable to airy; friendly sans; large plain-language numbers; short copy.\n- **Color/surface:** calm neutral base, one brand action color, semantic colors only for real\n status. Soft grouped surfaces are common but a card is used only when it groups a decision.\n- **Feedback:** immediate, reassuring, reversible; money and health values do not animate\n theatrically.\n- **Reject:** every service as equal card, promotional clutter above state, rainbow categories,\n cute language in serious moments, or copying Toss component-for-component.",
"operations-console": "### `operations-console`\n\n- **Job:** scan system or business state, compare changes, find exceptions, act efficiently.\n- **Attention:** one operational focal panel; supporting KPIs form hierarchy rather than a\n uniform card wall.\n- **Composition:** persistent navigation → scope/time controls → focal trend or queue →\n comparable metrics → actionable table/list. Detail lives below or on demand.\n- **Density/type:** comfortable to compact; neutral grotesk; tabular numerals; labels may be\n small but body and decisions remain readable on desktop.\n- **Color/surface:** restrained brand accent; categorical colors only for stable, repeated data\n semantics. Tone, alignment, and grouping carry more hierarchy than decoration.\n- **Feedback:** filters and mutations respond instantly; loading preserves layout; bulk action\n consequences are explicit.\n- **Reject:** same-weight KPI tiles everywhere, decorative gradients, mobile-tight type on a\n 1440px canvas, or charts without a decision they support.",
"technical-instrument": "### `technical-instrument`\n\n- **Job:** observe live state, isolate anomalies, and diagnose a system.\n- **Attention:** current incident, trace, monitor, or performance state dominates.\n- **Composition:** stable navigation and scope → live status/timeline → dense evidence table or\n trace → drill-down. Group by diagnostic relationship, not visual symmetry.\n- **Density/type:** compact to dense; sans for UI, mono only for identifiers, timestamps, code,\n and aligned numeric evidence.\n- **Color/surface:** neutral light or dark tonal ramp; one signal accent; status colors are\n semantic and sparse. Dark mode uses tonal levels and hairlines, not floating shadows.\n- **Feedback:** near-instant, non-blocking, time-aware; paused/live state is unmistakable.\n- **Reject:** decorative terminal cosplay, neon rainbow telemetry, excessive mono body copy,\n or animation that competes with changing data.",
"editorial-reading": "### `editorial-reading`\n\n- **Job:** understand, retain, and navigate a narrative or argument.\n- **Attention:** title/deck/byline or the opening visual establishes the reading promise.\n- **Composition:** strong title rhythm → bounded reading column → meaningful media/pull quote →\n related context after the narrative. Chrome recedes.\n- **Density/type:** airy around the story; 45–90 characters per line, normally near 60–70;\n comfortable body size/leading; serif is optional and role-specific, never an automatic theme.\n- **Color/surface:** ink and paper-like neutrals may be used without faking print; one quiet\n accent for links and editorial signals. Whitespace and type replace gratuitous cards.\n- **Feedback:** reading position, save, share, and footnotes are calm and unobtrusive.\n- **Reject:** forcing all content into cards, app-dashboard chrome around prose, overly wide\n measures, serif everywhere, or interaction patterns that interrupt reading.",
"commerce-conversion": "### `commerce-conversion`\n\n- **Job:** evaluate an offer, resolve uncertainty, select a variant, and commit.\n- **Attention:** product/service evidence leads; price, availability, primary CTA, and trust\n information remain easy to connect.\n- **Composition:** media → identity/price/proof → variants → primary action → delivery/returns →\n details and comparison. On mobile, keep the commitment action reachable without hiding terms.\n- **Density/type:** comfortable; product information is highly scannable; images are evidence,\n not decorative filler.\n- **Color/surface:** brand color supports the purchase action; ratings, discounts, stock, and\n errors use stable semantics rather than a promotional rainbow.\n- **Feedback:** variant and cart changes are immediate and reversible; costs and constraints\n appear before commitment.\n- **Reject:** urgency dark patterns, buried shipping/returns, competing CTAs, tiny variant\n targets, or a lifestyle mood that obscures the actual product.",
"institutional-service": "### `institutional-service`\n\n- **Job:** understand eligibility or obligation, provide accurate information, and complete a\n consequential task with confidence.\n- **Attention:** task title, current step, and required action dominate over branding.\n- **Composition:** plain-language intro → prerequisites → one step at a time → review → clear\n confirmation and recovery path.\n- **Density/type:** comfortable, highly legible, robust at zoom; labels and help stay adjacent\n to controls; long content uses bounded measure.\n- **Color/surface:** conservative semantic palette with high contrast; errors never rely on\n color alone; decorative color is rare.\n- **Feedback:** preserve entered data, explain errors, expose progress, and provide reference\n numbers/next steps after completion.\n- **Reject:** novelty over comprehension, ambiguous icons, low-contrast minimalism, hidden\n requirements, or motion that makes a serious workflow feel unstable.",
"expressive-marketing": "### `expressive-marketing`\n\n- **Job:** understand the proposition, believe the proof, and choose a next step.\n- **Attention:** one unmistakable promise paired with real product or brand evidence.\n- **Composition:** proposition → proof → differentiated mechanism → examples/outcomes → CTA.\n Section forms vary to create rhythm; repetition is deliberate, not template filler.\n- **Density/type:** airy with confident display hierarchy; body copy remains readable.\n- **Color/surface:** wider brand palette and expressive media are allowed when roles remain\n coherent and one action is primary.\n- **Feedback:** cinematic motion may support sequence, but first read and CTA work without it;\n reduced motion yields a complete page.\n- **Reject:** generic gradient headline, equal three-card feature rows, fake metrics, copied\n demo visuals, scroll-jacking, or motion used as a substitute for a proposition.",
"sequential-story": "### `sequential-story`\n\n- **Job:** make one idea understandable and worth continuing across a bounded visual sequence.\n- **Attention:** slide/frame one earns the next gesture; each following frame has one dominant\n message and a visible place in the sequence.\n- **Composition:** hook → context/why → evidence or mechanism → useful action → reframe/close.\n Alternate big type, evidence, image, and utility frames so the sequence has rhythm.\n- **Density/type:** designed for the adapter's viewing distance and dwell time; one message per\n frame; body copy is bounded and never shrunk to rescue an overloaded slide.\n- **Color/surface:** project grammar and brand tokens carry across all frames; emphasis roles\n remain stable. Continuity comes from a system, not identical templates.\n- **Imagery:** crops, captions, and sources are intentional; generated-image text/logos are\n replaced during deterministic rendering.\n- **Feedback/navigation:** folio, progress, swipe cue, and CTA fit the publishing surface without\n occupying its unsafe UI zones.\n- **Reject:** ten identical cards, a weak explanatory cover, one composition repeated on every\n frame, content that works only as a caption, missing source attribution, or platform crop loss."
},
"adapters": {
"product-ui": "## `product-ui`\n\n- **Canvas:** responsive viewports declared in the design lock; verify at least one narrow and\n one wide target when both are in scope.\n- **Primitives:** semantic HTML and project-native components. Preserve framework conventions,\n routes, state, and real content rather than replacing the application with a mock.\n- **States:** loading, empty, error, success, disabled, focus, and responsive navigation are part\n of the artifact when the screen owns those behaviors.\n- **Export:** source code plus reproducible dev/build commands and screenshot paths.\n- **QA:** run project checks, render the real route, inspect target viewports and important states,\n and record what could not be exercised.",
"social-carousel": "## `social-carousel`\n\nUse `sequential-story` unless another grammar clearly owns the content. The installed Claude\n`carousel-build` skill is the canonical companion when available:\n\n1. StyleSeed reads or compiles the grammar and writes brand/type/motion/content rules to\n `STYLESEED.md`.\n2. `carousel-build` reads that lock, then applies deterministic engineering constraints:\n 3:4 `1080×1440`, platform safe zones, crop behavior, 8px rhythm, type scale, gradient\n banding prevention, available font weights, and reproducible PIL rendering.\n3. Copy and publishing skills may add their own contracts without altering visual authority.\n4. `/ss-score` checks sequence coherence and grammar fit; `/ss-verify` opens every exported\n frame and checks crop, safe zone, typography, rhythm, false text, and continuity.\n\nRecommended lock additions:\n\n```markdown\n- Surface adapter: social-carousel\n- Artifact type: information-carousel\n- Canvas: 1080x1440\n- Sequence grammar: hook → why → evidence → action → reframe → CTA\n- Renderer: carousel-build\n- Safe zone contract: adapter:ADAPTERS.md#social-carousel-integration\n```\n\nDo not duplicate the renderer's rapidly changing platform measurements inside a visual grammar.\nThe adapter/companion owns them; StyleSeed owns the visual judgment applied within them.",
"slide-deck": "## `slide-deck`\n\n- **Canvas:** use the presentation renderer's declared aspect ratio and safe area. Assume distance\n viewing; body copy that works on a web page is usually too small or dense on a slide.\n- **Sequence:** one claim per slide, visible narrative progression, and deliberate alternation\n between statement, evidence, comparison, and action.\n- **Assets:** use real charts, diagrams, screenshots, and cited imagery. Never use a text-heavy\n card grid as a substitute for a presentation.\n- **Export:** editable deck plus rendered PDF or page images.\n- **QA:** render every slide, inspect overflow and contrast, and compare repeated masters,\n alignment, folios, and speaker-facing information.",
"document-report": "## `document-report`\n\n- **Canvas:** paginated output with explicit paper size, margins, headers/footers, and print-safe\n color. Reading measure and page breaks take priority over app-like chrome.\n- **Structure:** title and executive summary establish the decision; headings, figures, tables,\n notes, and appendices expose evidence at the right depth.\n- **Assets:** captions and sources remain attached to figures and tables. Repeated components use\n document styles rather than local formatting.\n- **Export:** editable source plus visually verified PDF.\n- **QA:** render every page and inspect widows/orphans, clipped tables, broken links, page numbers,\n accessibility structure, and cross-page consistency.",
"single-frame": "## `single-frame`\n\n- **Canvas:** declare exact dimensions, crop variants, safe zones, and intended viewing distance.\n- **Composition:** one dominant message and one supporting action or proof. The frame must work\n without surrounding caption text unless the publishing contract says otherwise.\n- **Assets:** logos, product imagery, and text are deterministic and legible. Generated imagery\n must not supply final logos or critical text.\n- **Export:** source plus final PNG/JPEG/SVG variants and an asset manifest.\n- **QA:** inspect at native size and thumbnail size, verify crop variants, contrast, text safety,\n and platform compression risk."
},
"domains": {
"fintech": "## 1. Fintech / Banking / Payments\n\n- **Color:** maximum restraint — the brand accent is reserved for *money in\n motion* (CTAs, positive trend, the active balance). Greens/reds only for\n up/down, never decoration. Trust reads as calm, not loud.\n- **Density:** medium-low. One number should dominate each card. Whitespace\n signals \"we're careful with your money.\"\n- **Typography:** numbers first — hero balance at 48px with 24px unit (2:1).\n Currency, dates, and labels recede.\n- **Motion:** **Spring** (Toss-style confident bounce on CTAs) or **Silk** for\n balances and sheets. Never bouncy on a number that represents real money\n changing — it reads as unstable.\n- **Signature patterns:** HeroCard (balance), transaction `ListItem` with status\n dot, KPI `StatCard` with trend, period toggle, donut for spend breakdown.\n- **Anti-patterns:** playful/rainbow palettes, gradients on amounts, fake\n precision (`$8,400.0000`), shadows that float cards \"off the page,\" motion that\n delays seeing a balance.\n- **Brand recipe:** `calm-consumer`; use `native-mobile` for a platform-first utility.\n- **Skin material:** custom brand tokens; Toss and Stripe remain optional token references.",
"saas": "## 2. SaaS / B2B Dashboard / Analytics\n\n- **Color:** disciplined grayscale with one accent for the primary action and\n selected state. Data viz gets a small, fixed categorical scale — not a new\n color per metric.\n- **Density:** high. Power users want information per pixel: tables, filters,\n multi-KPI rows. Density is a feature, not clutter — but keep rhythm.\n- **Typography:** balanced numbers + labels. Tables need tabular-nums and tight\n line-height; headings stay quiet.\n- **Motion:** **Snap** (Linear-style instant). Power users hate waiting on\n animation. Layout/FLIP for reordering, near-zero entrance.\n- **Signature patterns:** ChartCard (period toggle + bottom stats), dense KPI\n grid, filterable list/table, segmented control, RankedList.\n- **Anti-patterns:** decorative motion, oversized hero numbers that waste space,\n 6 accent colors in a chart legend, modal-heavy flows (prefer inline).\n- **Brand recipe:** `enterprise-workbench`; use `developer-platform` for technical evidence.\n- **Skin material:** custom brand tokens; Linear and Vercel remain optional token references.",
"ecommerce": "## 3. E-commerce / Retail\n\n- **Color:** product imagery is the hero — UI chrome stays neutral so photos pop.\n The accent drives one thing: **add-to-cart / buy**. Price gets weight, not color.\n- **Density:** medium. Browsing = generous product cards; PDP = focused, one CTA\n above the fold.\n- **Typography:** price prominent (2:1 with currency), product title bold, specs\n quiet. Imagery > text in the hierarchy.\n- **Motion:** **Spring** (energetic, Arc-style) for add-to-cart, quantity stepper,\n cart badge pop (`pop-in`, `confetti-pop` on purchase is fair). Hover lift on\n product cards.\n- **Signature patterns:** product card grid, cart badge, quantity stepper, sticky\n buy bar, price + strikethrough, ranked/featured rails.\n- **Anti-patterns:** burying the CTA, more than one primary action on a PDP,\n shouty discount colors everywhere (dilutes urgency), motion that blocks adding\n to cart.\n- **Brand recipe:** `commerce-operator` for merchant/product operations; `calm-consumer` for a\n lightweight consumer purchase flow.\n- **Skin material:** custom brand tokens; Arc and Toss remain optional token references.",
"social": "## 4. Social / Community / Feed\n\n- **Color:** energetic and warm — the accent can appear more often (likes,\n mentions, online dots). Still one accent, but a livelier hue.\n- **Density:** medium, content-led. Avatars and media drive the layout; chrome is\n minimal.\n- **Typography:** names bold, timestamps tiny and tertiary, body readable. Let\n user content (images, text) lead.\n- **Motion:** **Pulse** (alive, rhythmic) for likes/reactions (`like-burst`),\n live indicators (`pulse-beat`), and new-content arrival (`stagger-cascade`).\n- **Signature patterns:** feed list, avatar + name + time row, like/comment/share\n actions, like-burst, bottom-sheet composer, story rail.\n- **Anti-patterns:** heavy chrome that competes with user content, slow entrance\n on a feed (kills scroll), engagement bait colors, inconsistent avatar shapes.\n- **Brand recipe:** `native-mobile` for app-first feeds; `expressive-brand` for creator surfaces.\n- **Skin material:** custom warm accent; Arc remains an optional token reference.",
"content": "## 5. Content / Media / News / Blog / Docs\n\n- **Color:** near-monochrome; the accent is for links and one CTA. Reading is the\n product — color is a distraction.\n- **Density:** low in the reading column (measure ~60–75ch), denser in nav/index.\n- **Typography:** **typography is the design.** Strong type scale, comfortable\n line-height (1.5–1.65 for body), real hierarchy between H/body/caption.\n- **Motion:** **Silk** (smooth, continuous) — `reveal-blur`/`reveal-rise` on\n headlines, subtle. Nothing that interrupts reading.\n- **Signature patterns:** article hero, readable body column, table of contents,\n pull quotes, related-content rail, code blocks (for docs).\n- **Anti-patterns:** full-width body text, decorative motion mid-article, multiple\n accents, cramped line-height, autoplaying anything.\n- **Brand recipe:** `editorial-authority`.\n- **Skin material:** custom editorial tokens; Notion and Vercel remain optional token references.",
"productivity": "## 6. Productivity / Tools / Workspace\n\n- **Color:** quiet grayscale, one accent for primary action + active item.\n Calm so the user's *work* is the focus.\n- **Density:** high but organized — sidebars, panels, keyboard-first. Reward\n power use.\n- **Typography:** compact, tabular where needed, clear active/hover states.\n- **Motion:** **Snap** (instant, precise). Layout transitions for reorder; avoid\n anything that adds latency to a frequent action.\n- **Signature patterns:** sidebar + content + (optional) inspector, command\n palette, list/board toggle, inline edit, segmented control, keyboard hints.\n- **Anti-patterns:** slow motion on frequent actions, modal overload, decorative\n color, hiding power features behind too much progressive disclosure.\n- **Brand recipe:** `creative-professional` or `developer-platform` by task.\n- **Skin material:** custom brand tokens; Linear, Notion, and Vercel remain optional references.",
"health": "## 7. Health / Wellness / Fitness\n\n- **Color:** calm and reassuring — soft accent, vivid only for *metric rings /\n progress* on a calm base. Avoid alarming reds except for genuine alerts.\n- **Density:** low-medium. One clear focus per screen (today's rings, next\n action). Reduce cognitive load — users may be tired or stressed.\n- **Typography:** big friendly numbers (2:1), gentle labels, generous spacing.\n- **Motion:** **Float** (weightless, gentle) for reveals; **Pulse** only for live\n heartbeat/recording. Motion should feel calm, never urgent.\n- **Signature patterns:** activity rings, big-metric hero, streak/progress, large\n touch targets (≥44px, often bigger), weekly history.\n- **Anti-patterns:** dense data dumps, alarming colors for normal states, tiny\n targets, jittery motion, shame-y empty states.\n- **Brand recipe:** `calm-consumer`; use `native-mobile` for tracking and capture.\n- **Skin material:** custom soft accent; Toss remains an optional token reference.",
"education": "## 8. Education / Learning / Courses\n\n- **Color:** encouraging — accent marks progress and the next step. Use color to\n reward (completed = success tone), not to decorate.\n- **Density:** low per step (one concept at a time), denser in course index.\n- **Typography:** clear hierarchy, readable body, prominent \"what's next.\"\n- **Motion:** **Spring** for rewards/completion (`pop-in`, `confetti-pop` on\n finishing a module), `stagger-cascade` for lesson lists.\n- **Signature patterns:** progress bar/ring, step list with states, lesson card,\n next-action CTA, achievement/streak, empty→first-lesson onboarding.\n- **Anti-patterns:** showing the whole curriculum at once (overwhelm), unclear\n next step, punishing empty/incomplete states, no sense of progress.\n- **Brand recipe:** `calm-consumer` for learners; `editorial-authority` for reading-heavy courses.\n- **Skin material:** custom learning tokens; Notion and Arc remain optional references.",
"developer-tools": "## 9. Developer Tools / Infra / API\n\n- **Color:** dark-first, restrained, one accent for primary/active. Syntax colors\n are their own controlled scale.\n- **Density:** high — devs read dense screens fine. Monospace where it belongs\n (code, IDs, logs).\n- **Typography:** mono for code/identifiers, tight tabular numbers, quiet UI text.\n- **Motion:** **Snap** (instant, precise). Devs distrust slow/flashy UI. Minimal,\n functional only.\n- **Signature patterns:** code blocks with copy, logs/terminal, status badges,\n command palette, key-value config, dark base.\n- **Anti-patterns:** flashy motion (reads as unserious to devs), light-only\n themes, decorative gradients, hiding the CLI/copy affordance.\n- **Brand recipe:** `developer-platform`.\n- **Skin material:** custom technical tokens; Raycast, Vercel, and Linear remain optional references.",
"marketplace": "## 10. Marketplace / Listings (two-sided)\n\n- **Color:** trust + conversion — neutral chrome, accent for the primary action\n (book/contact/buy) and for ratings/verified signals.\n- **Density:** medium — scannable listing grid, focused detail page.\n- **Typography:** listing title bold, price/rating prominent, meta quiet.\n- **Motion:** **Silk** for transitions, hover lift on listing cards, `pop-in` for\n saved/favorited.\n- **Signature patterns:** listing card (image + title + price + rating), filters,\n map/list toggle, detail with sticky action, reviews, verified badge.\n- **Anti-patterns:** untrustworthy density (too much, too fast), weak rating/trust\n signals, more than one primary action, buried contact/CTA.\n- **Brand recipe:** `commerce-operator`.\n- **Skin material:** custom marketplace tokens; Stripe and Notion remain optional references.",
"booking": "## 11. Booking / Travel / Reservations\n\n- **Color:** confident and visual — imagery (places, rooms) leads; accent drives\n the booking step. Calm so users feel sure about committing money/time.\n- **Density:** low-medium, step-focused. Dates and availability must be obvious.\n- **Typography:** clear dates/times, price prominent, friendly labels.\n- **Motion:** **Float** / **Silk** — smooth, reassuring transitions between steps;\n `reveal-blur` on imagery. Confidence, not flash.\n- **Signature patterns:** date/range picker, availability calendar, multi-step\n booking with progress, map, price summary, confirmation.\n- **Anti-patterns:** hidden total/fees, unclear current step, jarring motion mid-\n booking, tiny date controls, anxiety-inducing scarcity spam.\n- **Brand recipe:** `commerce-operator`; use `native-mobile` for an app-first itinerary.\n- **Skin material:** custom travel tokens; Arc and Notion remain optional references.",
"ai-assistant": "## 12. AI / Chat / Assistant\n\n- **Color:** minimal chrome so the conversation is the focus; one accent for the\n send/primary action. Let content (the answer) lead.\n- **Density:** low chrome, high content. Generous reading width for responses.\n- **Typography:** readable body for answers, mono for code, quiet UI around the\n thread.\n- **Motion:** **Silk** — streaming/typing feel, smooth message entrance\n (`reveal-blur`/`stagger`), nothing that competes with reading the answer.\n- **Signature patterns:** message thread, streaming response, composer with\n send, prompt suggestions, copy-on-message, minimal top bar.\n- **Anti-patterns:** heavy chrome around the chat, flashy motion on every token,\n cramped reading width, burying the input, multiple accents.\n- **Brand recipe:** `creative-professional` for workbench assistants; `calm-consumer` for simple\n personal assistants.\n- **Skin material:** custom assistant tokens; Raycast and Notion remain optional references.\n\n---"
},
"pages": {
"dashboard": "## Dashboard / Home / Overview\n\n- **Job:** answer \"how are things?\" in 3 seconds, then let the user drill down.\n- **Structure:** information pyramid — one hero metric → a small KPI set →\n supporting list/chart. Vary section types (Rule 14/61): never four identical\n cards in a row.\n- **Hierarchy:** density increases as you scroll. Top = 48px hero number; bottom\n = 13–14px detailed rows.\n- **Patterns:** HeroCard, KPI grid (mixed: trend / progress / comparison),\n ChartCard, RankedList, BriefingCarousel for alerts.\n- **Anti-patterns:** wall of identical KPIs, no clear primary metric, charts with\n 6 legend colors, everything the same visual weight.\n- **Mobile:** single column, hero first, horizontal-scroll carousels for KPI sets,\n `mx-6` single cards / `px-6` grids.",
"form": "## Form / Create / Edit\n\n- **Job:** get accurate input with the least friction and the most confidence.\n- **Structure:** **single column**, logical grouping into `SectionCard`s, one\n clear primary action (bottom or sticky). Labels **above** fields, not beside.\n- **Hierarchy:** the field you're on > the next field > everything else. Primary\n action visually dominant; secondary/cancel quiet.\n- **Patterns:** labeled inputs, inline validation (validate on blur, not on every\n keystroke), grouped sections, sticky save bar, `wiggle` + helper text on error,\n clear required/optional marking.\n- **Anti-patterns:** multi-column forms (eye zig-zags), placeholder-as-label,\n validating before the user finishes, two primary buttons, error states with no\n recovery guidance, disabled submit with no explanation.\n- **Mobile:** full-width fields, ≥44px touch targets, numeric keyboards for number\n fields, sticky primary above the keyboard.",
"landing": "## Landing / Marketing / Home (public)\n\n- **Job:** in one screen, say what it is + why it matters + one next action.\n- **Structure:** hero (headline + subhead + single primary CTA) → proof\n (features/social proof) → CTA again. One conversion goal per page.\n- **Hierarchy:** headline dominates; CTA is the single brightest element; supporting\n text recedes. The accent belongs to the CTA.\n- **Patterns:** hero with one CTA, feature grid, social proof (logos/stats/quotes),\n before/after, comparison, closing CTA.\n- **Motion — this is the ONE surface where the Cinematic tier applies (DESIGN-LANGUAGE §43).**\n Unlike an app/dashboard, a brand page *should* use motion as craft: **scroll-LINKED reveals,\n pinned/sticky sections, sequential \"the product assembles as you scroll\" choreography, subtle\n parallax, a 3D/tilt hero or showcase card, an animated gradient/mesh or video background, rich\n hover** (magnetic/glow/lift). This is how family.co / stripe.com / linear.app read premium —\n don't apply dashboard restraint here. Guardrails still hold: purposeful (not jitter), 60fps\n (`transform`/`opacity` only), never blocks the first read or the CTA/LCP, `prefers-reduced-motion`\n leaves a complete static page, one motion language.\n- **Anti-patterns:** multiple competing CTAs, wall of text, every section the same rhythm,\n autoplaying *audio*, motion that **delays the headline or hides content until you scroll**,\n **scroll-JACKING** (hijacking scroll speed / trapping — different from scroll-linked, which is\n fine), six accent colors, no reduced-motion fallback.\n- **Mobile:** stack everything, keep the CTA reachable, don't shrink the headline\n into mush.",
"detail": "## Detail / Profile / Item\n\n- **Job:** show one thing deeply, with its primary action obvious.\n- **Structure:** identity header (title/image/status) → key facts → body/sections →\n one primary action (often sticky on mobile).\n- **Hierarchy:** the subject's name/title and its primary action win. Metadata is\n tertiary.\n- **Patterns:** hero/identity block, key-value facts, tabbed or sectioned body,\n sticky primary action, related items rail, status indicators.\n- **Anti-patterns:** burying the primary action, equal weight on everything, no\n clear \"what is this,\" metadata louder than the subject.\n- **Mobile:** sticky action bar, collapse long sections, single column.",
"list": "## List / Index / Browse / Search\n\n- **Job:** scan many items fast, find the right one, act.\n- **Structure:** optional filter/search bar → consistent rows/cards → clear\n per-item action or tap target. Consistency beats variety here.\n- **Hierarchy:** the item's identifying field (name/title) is boldest; status and\n meta are quieter; the value/amount is emphasized on the trailing edge.\n- **Patterns:** `ListItem` (title + status dot + trailing value), filterable list,\n segmented control, swipe actions on mobile, empty state, `stagger-cascade`\n entrance, skeleton while loading.\n- **Anti-patterns:** rows that vary wildly, no empty/loading state, status by color\n alone (pair with text/dot), tiny tap targets, pagination where infinite scroll\n fits (or vice-versa).\n- **Mobile:** full-width rows, swipe-to-act, ≥44px rows, sticky filter.",
"settings": "## Settings / Account / Preferences\n\n- **Job:** let the user find and change one setting with confidence, safely.\n- **Structure:** grouped `SectionCard`s by topic (profile / notifications /\n billing / danger), each setting a labeled row with its control on the trailing\n edge. Destructive actions isolated and clearly marked.\n- **Hierarchy:** flat and scannable — settings are equal-weight within a group;\n group titles orient. Danger zone visually separated.\n- **Patterns:** toggle rows, select rows, account header, billing card, **danger\n zone** (the one place a functional border + destructive tone is right),\n inline-save or explicit-save (be consistent).\n- **Anti-patterns:** destructive actions next to benign ones, unclear save model,\n settings with no labels, toggles whose state is ambiguous, no confirmation on\n irreversible actions.\n- **Mobile:** full-width rows, controls reachable with the thumb, ≥44px toggles.",
"onboarding": "## Onboarding / First-run / Empty\n\n- **Job:** get the user to their first win with minimal steps, never a dead end.\n- **Structure:** few steps with visible progress, one decision per step, a strong\n \"what's next.\" Empty states are onboarding in disguise — always offer the next\n action.\n- **Hierarchy:** the current step and its single action dominate; progress is\n visible but quiet.\n- **Patterns:** stepper/progress, one-question-per-screen, `EmptyState` (icon +\n title + description + action), `pop-in`/`confetti-pop` on completion, contextual\n tips, skip/defer where fair.\n- **Anti-patterns:** asking everything up front, empty screens with no next action,\n no sense of progress, punishing or shaming empty states, forcing setup before\n any value.\n- **Mobile:** one step per screen, big primary, progress at top.\n\n---"
},
"recipes": {
"calm-consumer": "## `calm-consumer`\n\n- **Best fit:** personal state, reassurance, benefits, lightweight health, and short recurring\n tasks. Strong with `consumer-service`; usable for a gentle `commerce-conversion`.\n- **Containment:** tonal groups and a few soft cards. One summary may float; secondary rows\n should not become an equal card wall.\n- **Geometry:** 12–20px outer radius, smaller nested radius, restrained shadow or tonal\n separation, comfortable 20–24px gutters.\n- **Type/icons:** friendly sans, plain-language values, simple line icons without repeated\n tinted icon chips.\n- **Controls/collections:** reachable primary action, short rows or grouped sections, pill\n controls only for true compact choices.\n- **Motion:** immediate and reassuring; spring may be restrained; consequential values do not\n count theatrically.\n- **Characteristic move:** one calm contextual briefing connected to the user's current state.\n- **Reject:** copying Toss layouts, every service as a rounded card, blue as an automatic brand,\n cute language in serious states, or chips as decoration.\n- **Lineage:** StyleSeed consumer-service research; brand examples are evidence, not sources to\n copy.",
"native-mobile": "## `native-mobile`\n\n- **Best fit:** focused mobile utilities, capture, communication, media, and one-handed tasks.\n Compatible with any grammar whose primary surface is a mobile app.\n- **Containment:** content is primary; navigation and controls recede into platform-familiar\n bars, sheets, lists, and grouped regions.\n- **Geometry:** platform-aware radii and materials, 16–20px screen gutters, touch targets at\n least 44px, no desktop card grid squeezed into a phone.\n- **Type/icons:** dynamic type and platform conventions; system symbols when licensed for the\n target platform, otherwise one consistent open icon family.\n- **Controls/collections:** few visible controls, secondary actions disclosed nearby, common\n gestures have visible alternatives, important actions remain reachable.\n- **Motion/responsive:** adapt to orientation, dark mode, text scaling, and device insets;\n transitions preserve spatial continuity.\n- **Characteristic move:** one content-first task with controls concentrated in the reachable\n middle or lower region.\n- **Reject:** decorative glass imitation, copying Apple assets, invisible gesture-only actions,\n tiny targets, or a web navbar pretending to be native.\n- **Lineage:** [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines).",
"enterprise-workbench": "## `enterprise-workbench`\n\n- **Best fit:** admin, analytics, operations, collaboration, and multi-step B2B work. Strong\n with `operations-console`.\n- **Containment:** persistent shell, aligned panels, toolbars, tables, and detail regions.\n Group by work relationship; use card grids sparingly.\n- **Geometry:** 0–8px radius, 1px boundaries or tonal layers, minimal floating shadow, 16–24px\n page rhythm on an explicit grid.\n- **Type/icons:** neutral UI sans, tabular numbers, compact labels, one functional icon family.\n- **Controls/collections:** rectangular controls, visible filters, bulk actions, dense rows,\n comparison tables, and preserved loading geometry.\n- **Motion/responsive:** fast state changes; panels reflow or collapse by task priority rather\n than merely shrinking.\n- **Characteristic move:** one operational focal panel connected directly to an actionable\n queue or evidence table.\n- **Reject:** identical KPI cards, oversized mobile typography on desktop, decorative\n gradients, hidden scope controls, or floating shadows on every panel.\n- **Lineage:** [IBM Carbon 2x Grid](https://carbondesignsystem.com/elements/2x-grid/overview/),\n [Microsoft Fluent 2](https://fluent2.microsoft.design/design-principles),\n [Atlassian foundations](https://atlassian.design/foundations).",
"developer-platform": "## `developer-platform`\n\n- **Best fit:** repositories, observability, infrastructure, APIs, and security. Strong with\n `technical-instrument`; usable for technical `operations-console`.\n- **Containment:** stable shell, compact toolbars, hairline-separated rows, code/evidence\n regions, and contextual drill-down.\n- **Geometry:** 4–8px radius, hairline borders, little or no shadow, compact 12–20px rhythm.\n- **Type/icons:** sans for UI; mono only for identifiers, timestamps, logs, commands, and aligned\n numeric evidence. Icons are small and functional.\n- **Controls/collections:** rectangular or lightly rounded controls, status-aware tables,\n timelines, command palettes, and copyable evidence.\n- **Motion/responsive:** near-instant, non-blocking, time-aware; live/paused state is explicit.\n- **Characteristic move:** one inspectable evidence region that connects status to diagnosis.\n- **Reject:** terminal cosplay, neon telemetry, mono body copy, rounded feature-card grids, or\n animation that competes with live data.\n- **Lineage:** [GitHub Primer foundations](https://primer.style/product/getting-started/foundations/)\n and [Primer pattern guidance](https://primer.style/product/contribute/design/).",
"commerce-operator": "## `commerce-operator`\n\n- **Best fit:** merchant admin, inventory, orders, fulfillment, customer support, and complex\n purchase operations. Strong with `operations-console` and `commerce-conversion`.\n- **Containment:** resource index → filters → actionable rows → contextual detail. Cards group\n merchant decisions, not every datum.\n- **Geometry:** 8–12px radius, clear boundaries, moderate density, restrained elevation for\n overlays and temporary context only.\n- **Type/icons:** highly scannable labels and values; product imagery is evidence; status text\n accompanies semantic color.\n- **Controls/collections:** filter bars, resource tables/lists, contextual actions, variant and\n fulfillment state, reversible mutations.\n- **Motion/responsive:** preserve selection and filters; mobile promotes the current task and\n moves secondary columns into detail.\n- **Characteristic move:** a resource row or order state exposes the next operational action\n without opening a decorative card.\n- **Reject:** promotional rainbow admin UI, buried costs or constraints, ambiguous statuses,\n equal-weight analytics tiles, or urgency dark patterns.\n- **Lineage:** [Shopify Polaris](https://polaris-react.shopify.com/) and StyleSeed commerce\n research. Reuse concepts, not Shopify trade dress.",
"public-service": "## `public-service`\n\n- **Best fit:** eligibility, applications, regulated forms, healthcare workflows, and civic\n services. Strong with `institutional-service`.\n- **Containment:** flat document flow, bounded reading measure, explicit sections, one step at a\n time, review, confirmation, and recovery.\n- **Geometry:** 0–4px radius, strong visible boundaries, no decorative shadow, generous vertical\n separation, high-contrast focus.\n- **Type/icons:** robust sans, plain language, adjacent labels/help, icons never replace critical\n words.\n- **Controls/collections:** rectangular buttons and inputs, clear legends, summaries and error\n links, preserved entered data, reference number on completion.\n- **Motion/responsive:** minimal and non-essential; layout survives zoom, reflow, text scaling,\n keyboard, and assistive technology.\n- **Characteristic move:** the current task and its requirements are more visually prominent\n than the organization brand.\n- **Reject:** low-contrast minimalism, novelty navigation, hidden prerequisites, rounded app\n cards, altered control meanings, or motion that makes the workflow feel unstable.\n- **Lineage:** [GOV.UK patterns](https://design-system.service.gov.uk/patterns/) and\n [community principles](https://design-system.service.gov.uk/community/community-principles/).",
"creative-professional": "## `creative-professional`\n\n- **Best fit:** creation, editing, asset management, media, and expert cross-platform tools.\n Compatible with `operations-console`, `technical-instrument`, and `sequential-story`.\n- **Containment:** focused canvas or artifact, quiet utility chrome, named tool groups, inspectors,\n and progressive disclosure.\n- **Geometry:** 4–8px radius, crisp boundaries, restrained layers, density adapts between\n pointer and touch.\n- **Type/icons:** rational, compact, internationalizable labels; icons support trained use but\n critical actions retain text or discoverable labels.\n- **Controls/collections:** toolbars, property panels, asset grids, contextual controls, and\n explicit selection state.\n- **Motion/responsive:** desktop and mobile scale independently while sharing one language;\n motion explains selection, mode, and spatial change.\n- **Characteristic move:** the user's work occupies the focal field while tools form a\n disciplined supporting frame.\n- **Reject:** decoration around the canvas, one scale stretched across all inputs, unlabeled\n mystery tools, or copying Adobe product chrome.\n- **Lineage:** [Adobe Spectrum principles](https://spectrum.adobe.com/page/principles/) and\n [platform scale](https://spectrum.adobe.com/page/platform-scale/).",
"editorial-authority": "## `editorial-authority`\n\n- **Best fit:** journalism, reports, research, policy, and documentation. Strong with\n `editorial-reading`; usable for evidence-heavy `expressive-marketing`.\n- **Containment:** type, whitespace, rules, captions, and bounded columns replace app cards.\n Chrome recedes after orientation.\n- **Geometry:** 0–4px radius, hairline rules, little or no shadow, generous section rhythm and\n 45–90 character reading measures.\n- **Type/icons:** deliberate display/body roles, serif optional and role-specific, captions and\n sources remain attached to evidence.\n- **Controls/collections:** calm inline actions, table of contents, footnotes, save/share,\n figures, pull quotes, and related context after the narrative.\n- **Motion/responsive:** reading position and disclosure only; narrow screens preserve hierarchy\n and source relationships rather than stacking arbitrary cards.\n- **Characteristic move:** an authoritative opening promise followed by inspectable evidence.\n- **Reject:** dashboard chrome around prose, every section in a card, overly wide measure, serif\n everywhere, fake print texture, or interruption-heavy interactions.\n- **Lineage:** StyleSeed editorial research and public-content accessibility guidance.",
"expressive-brand": "## `expressive-brand`\n\n- **Best fit:** campaigns, launches, portfolios, brand pages, posters, and social stories.\n Strong with `expressive-marketing` and `sequential-story`.\n- **Containment:** section form varies with the narrative; proof, product media, and display type\n create rhythm instead of a repeated feature-card template.\n- **Geometry:** project-specific and intentionally consistent; one signature geometry may be\n sharp, soft, or sculptural, but nested and control shapes stay coherent.\n- **Type/icons:** distinctive display hierarchy plus readable body; commissioned or licensed\n imagery carries identity more than generic icons.\n- **Controls/collections:** one identifiable CTA, product demonstrations, outcomes, and examples;\n repeated collections earn their repetition.\n- **Motion/responsive:** cinematic choreography is allowed when native scroll remains under user\n control and reduced motion yields a complete result.\n- **Characteristic move:** one product-specific visual mechanism that could not be swapped into\n an unrelated landing page.\n- **Reject:** generic gradient headlines, equal three-card rows, fake metrics, scroll-jacking,\n copied campaign compositions, or motion that substitutes for a proposition.\n- **Lineage:** independent brand/campaign research; compile supplied references with\n `ss-reference` when a particular visual language matters.\n\n## Legal and promotion boundary\n\n- Reference-family names document provenance; never present a recipe as official, endorsed, or\n pixel-compatible with those systems.\n- Do not redistribute protected logos, proprietary icons, fonts, illustrations, product copy,\n screenshots, or trademarked arrangements.\n- A project-local reference grammar remains the correct path for a specific brand. Promote it to\n a built-in recipe only after multiple independent sources and transfer tests prove that the\n morphology is reusable and not a clone."
},
"palettes": {
"quiet-mineral": "### quiet-mineral\n\n- Name: Quiet mineral\n- Mode: light\n- Best for: Calm consumer products, health, benefits, and personal utilities\n- Recipe bias: calm-consumer, native-mobile\n- Semantic roles: background=#F3F0E8 · surface=#FFFDF8 · chrome=#DCE7E0 · foreground=#19211F · mutedForeground=#56635F · chromeForeground=#19211F · border=#BFC9C2 · primary=#276B5E · primaryForeground=#FFFFFF · accent=#D56A45 · accentForeground=#211713 · focus=#155EEF · success=#1E6F50 · successForeground=#FFFFFF · warning=#8A5700 · warningForeground=#FFFFFF · danger=#B42318 · dangerForeground=#FFFFFF\n- Usage: Let warm neutrals carry most of the canvas. Reserve green for the primary action and current-state reassurance; keep coral below ten percent.\n- Generated-media anchors: #F3F0E8, #276B5E, #D56A45\n- Avoid in generated media: neon blue, rainbow gradients, blue-purple AI glow\n- Re-run deterministic contrast validation after any project override.",
"deep-lime-studio": "### deep-lime-studio\n\n- Name: Deep lime studio\n- Mode: dark\n- Best for: Creative work, focused productivity, and high-craft prototypes\n- Recipe bias: creative-professional, expressive-brand\n- Semantic roles: background=#0B0D0F · surface=#15191D · chrome=#262C31 · foreground=#F5F4ED · mutedForeground=#A8AFAB · chromeForeground=#F5F4ED · border=#465057 · primary=#B8FF5A · primaryForeground=#10140C · accent=#7AA7FF · accentForeground=#0B1020 · focus=#B8FF5A · success=#75E6A4 · successForeground=#0B1A10 · warning=#FFC857 · warningForeground=#1B1300 · danger=#FF766E · dangerForeground=#200704\n- Usage: Use lime for selection and one decisive action, never as body text. Layer chrome one step lighter than the canvas; let generated imagery remain low-luminance.\n- Generated-media anchors: #0B0D0F, #B8FF5A, #7AA7FF\n- Avoid in generated media: pure-white bloom, multicolor neon, lime on light backgrounds\n- Re-run deterministic contrast validation after any project override.",
"cobalt-instrument": "### cobalt-instrument\n\n- Name: Cobalt instrument\n- Mode: light\n- Best for: Developer platforms, data products, and operational workbenches\n- Recipe bias: developer-platform, enterprise-workbench\n- Semantic roles: background=#F1F5FA · surface=#FFFFFF · chrome=#101828 · foreground=#101828 · mutedForeground=#475467 · chromeForeground=#F9FAFB · border=#C7D1E0 · primary=#175CD3 · primaryForeground=#FFFFFF · accent=#6941C6 · accentForeground=#FFFFFF · focus=#175CD3 · success=#067647 · successForeground=#FFFFFF · warning=#934F00 · warningForeground=#FFFFFF · danger=#B42318 · dangerForeground=#FFFFFF\n- Usage: Use neutral layers for evidence and cobalt for action/selection. Violet belongs to secondary categorization, not a second primary action.\n- Generated-media anchors: #101828, #175CD3, #F1F5FA\n- Avoid in generated media: cyan terminal glow, decorative gradients, low-contrast gray text\n- Re-run deterministic contrast validation after any project override.",
"warm-clay-commerce": "### warm-clay-commerce\n\n- Name: Warm clay commerce\n- Mode: light\n- Best for: Commerce, hospitality, booking, and human service brands\n- Recipe bias: commerce-operator, calm-consumer\n- Semantic roles: background=#FFF8F2 · surface=#FFFFFF · chrome=#2B201B · foreground=#2B201B · mutedForeground=#6B5549 · chromeForeground=#FFF8F2 · border=#DCCBC0 · primary=#A64005 · primaryForeground=#FFFFFF · accent=#6441C4 · accentForeground=#FFFFFF · focus=#1D5FD0 · success=#187451 · successForeground=#FFFFFF · warning=#8A5700 · warningForeground=#FFFFFF · danger=#B42318 · dangerForeground=#FFFFFF\n- Usage: Let product imagery and warm paper carry identity. Clay marks purchase intent; violet is reserved for saved or personalized context.\n- Generated-media anchors: #FFF8F2, #A64005, #2B201B\n- Avoid in generated media: orange sale walls, high-saturation red urgency, generic purple gradient\n- Re-run deterministic contrast validation after any project override.",
"civic-blue": "### civic-blue\n\n- Name: Civic blue\n- Mode: light\n- Best for: Public services, regulated applications, and consequential workflows\n- Recipe bias: public-service, enterprise-workbench\n- Semantic roles: background=#F3F5F7 · surface=#FFFFFF · chrome=#003078 · foreground=#0B0C0C · mutedForeground=#4C6272 · chromeForeground=#FFFFFF · border=#B1B4B6 · primary=#1D5F91 · primaryForeground=#FFFFFF · accent=#00703C · accentForeground=#FFFFFF · focus=#B45F06 · success=#00703C · successForeground=#FFFFFF · warning=#8A5700 · warningForeground=#FFFFFF · danger=#B42318 · dangerForeground=#FFFFFF\n- Usage: Use blue for institution and primary action, green only for confirmed success, and amber focus with a visible outline. Pair every status with text.\n- Generated-media anchors: #003078, #F3F5F7, #00703C\n- Avoid in generated media: pastel minimalism, glass navigation, color-only status\n- Re-run deterministic contrast validation after any project override.",
"editorial-ink": "### editorial-ink\n\n- Name: Editorial ink\n- Mode: light\n- Best for: Research, journalism, reports, and authoritative reading\n- Recipe bias: editorial-authority, public-service\n- Semantic roles: background=#F5F1E8 · surface=#FFFCF6 · chrome=#111111 · foreground=#111111 · mutedForeground=#5B564E · chromeForeground=#FFFFFF · border=#C9C0B2 · primary=#8F2C21 · primaryForeground=#FFFFFF · accent=#1D4E9E · accentForeground=#FFFFFF · focus=#1D4E9E · success=#176B45 · successForeground=#FFFFFF · warning=#805200 · warningForeground=#FFFFFF · danger=#A12B22 · dangerForeground=#FFFFFF\n- Usage: Use ink and paper for the reading field. Red marks editorial emphasis sparingly; blue owns links and focus.\n- Generated-media anchors: #F5F1E8, #111111, #8F2C21\n- Avoid in generated media: fake newspaper texture, sepia photos, card-grid chrome\n- Re-run deterministic contrast validation after any project override.",
"signal-coral": "### signal-coral\n\n- Name: Signal coral\n- Mode: light\n- Best for: Expressive launches, social products, and consumer discovery\n- Recipe bias: expressive-brand, native-mobile\n- Semantic roles: background=#FFF3EC · surface=#FFFFFF · chrome=#1A1423 · foreground=#1A1423 · mutedForeground=#645A6C · chromeForeground=#FFFFFF · border=#D8C9D0 · primary=#C83D2C · primaryForeground=#FFFFFF · accent=#5B3EC8 · accentForeground=#FFFFFF · focus=#5B3EC8 · success=#19724E · successForeground=#FFFFFF · warning=#875400 · warningForeground=#FFFFFF · danger=#A92924 · dangerForeground=#FFFFFF\n- Usage: Let coral own the hook and selected moment. Violet supports spatial or media context; never let both compete as primary actions.\n- Generated-media anchors: #FFF3EC, #C83D2C, #5B3EC8\n- Avoid in generated media: pink-purple gradient wash, rainbow icon chips, coral body text\n- Re-run deterministic contrast validation after any project override.",
"nocturne-violet": "### nocturne-violet\n\n- Name: Nocturne violet\n- Mode: dark\n- Best for: Media, AI, experimental professional tools, and immersive utilities\n- Recipe bias: creative-professional, developer-platform\n- Semantic roles: background=#0D0B16 · surface=#171327 · chrome=#2A2341 · foreground=#F6F2FF · mutedForeground=#B3A9C9 · chromeForeground=#F6F2FF · border=#554A70 · primary=#A98BFF · primaryForeground=#160F28 · accent=#4BE0C3 · accentForeground=#06251F · focus=#D1C2FF · success=#65D6A2 · successForeground=#062118 · warning=#F4BF55 · warningForeground=#241700 · danger=#FF8078 · dangerForeground=#2A0705\n- Usage: Use violet for primary control and mint for creation/progress. Keep media dark enough to preserve chrome and avoid synthetic AI-glow overload.\n- Generated-media anchors: #0D0B16, #A98BFF, #4BE0C3\n- Avoid in generated media: electric blue haze, purple gradient headlines, white bloom behind text\n- Re-run deterministic contrast validation after any project override."
},
"profiles": {
"swiss": "### swiss\n\n- Coordinate: sharp · structured · compact · restrained · snap\n- Signature: asymmetric grid and typographic alignment\n- This profile does not permit: illegible tiny type or ornament-free sameness\n- Apply the coordinate coherently across radius, density, typography, palette temperature, elevation, motion, composition, and one signature move.",
"editorial": "### editorial\n\n- Coordinate: soft/sharp · airy · warm-muted · silk\n- Signature: one display-type moment\n- This profile does not permit: serif body on every product surface\n- Apply the coordinate coherently across radius, density, typography, palette temperature, elevation, motion, composition, and one signature move.",
"technical": "### technical\n\n- Coordinate: sharp · dense · cool · still\n- Signature: mono evidence and tonal layers\n- This profile does not permit: decorative terminal cosplay\n- Apply the coordinate coherently across radius, density, typography, palette temperature, elevation, motion, composition, and one signature move.",
"warm-dtc": "### warm-dtc\n\n- Coordinate: soft/pill · airy · warm-vivid · spring\n- Signature: tactile product/image moment\n- This profile does not permit: competing warm colors or pill-everything\n- Apply the coordinate coherently across radius, density, typography, palette temperature, elevation, motion, composition, and one signature move.",
"minimal-mono": "### minimal-mono\n\n- Coordinate: sharp/soft · airy · near-monochrome · snap/silk\n- Signature: one precise typographic gesture\n- This profile does not permit: missing hierarchy or invisible controls\n- Apply the coordinate coherently across radius, density, typography, palette temperature, elevation, motion, composition, and one signature move.",
"brutalist-lite": "### brutalist-lite\n\n- Coordinate: sharp · comfortable · high-contrast · snap\n- Signature: one hard structural move\n- This profile does not permit: broken states, mixed border weights, or inaccessible contrast\n- Apply the coordinate coherently across radius, density, typography, palette temperature, elevation, motion, composition, and one signature move."
},
"agents": {
"claude": "## Claude Code execution contract\n\n- Invoke StyleSeed skills with `/ss-*`.\n- Read this compiled bundle before implementation; do not load the full handbook unless a named ambiguity remains.\n- Use the selected renderer, run `/ss-score`, then inspect rendered output with `/ss-verify`.",
"codex": "## Codex execution contract\n\n- Invoke StyleSeed skills with `$ss-*` or the Skills picker.\n- Read this compiled bundle before implementation; do not load the full handbook unless a named ambiguity remains.\n- Run project checks, then `$ss-score`; render and inspect the actual artifact with `$ss-verify` before claiming completion.",
"cursor": "## Cursor execution contract\n\n- Use the installed StyleSeed skills from the skill picker or follow this bundle directly.\n- Keep decisions in project files, run the project checks, and inspect the real rendered artifact before delivery.",
"generic": "## Agent execution contract\n\n- Treat this compiled bundle as the active StyleSeed context.\n- Keep decisions in project files, run available project checks, and inspect the real rendered artifact before delivery."
}
}
references/manifest.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://styleseed.dev/schemas/manifest.schema.json",
"type": "object", "additionalProperties": false,
"required": ["schemaVersion", "artifactId", "engineVersion", "engineRevision", "distributionIntegrity", "selection", "inputs", "sources", "methodHash", "validationHash", "validationSlices", "bundle", "outputs"],
"properties": {
"schemaVersion": { "const": 2 }, "artifactId": { "$ref": "#/$defs/id" }, "engineVersion": { "type": "string" },
"engineRevision": { "$ref": "#/$defs/hash" }, "distributionIntegrity": { "enum": ["verified", "unverified"] },
"selection": { "type": "object" }, "inputs": { "type": "array", "items": { "$ref": "#/$defs/namedFile" } },
"sources": { "type": "array", "items": { "$ref": "#/$defs/namedFile" } }, "methodHash": { "$ref": "#/$defs/hash" },
"validationHash": { "$ref": "#/$defs/hash" },
"validationSlices": {
"type": "object", "additionalProperties": false,
"required": ["target", "implementation", "scoreFloor", "requiredRenders", "temporal", "humanAcceptance"],
"properties": {
"target": { "$ref": "#/$defs/hash" }, "implementation": { "$ref": "#/$defs/hash" },
"scoreFloor": { "$ref": "#/$defs/hash" }, "requiredRenders": { "$ref": "#/$defs/hash" },
"temporal": { "$ref": "#/$defs/hash" }, "humanAcceptance": { "$ref": "#/$defs/hash" }
}
},
"bundle": { "$ref": "#/$defs/file" }, "outputs": { "type": "array", "items": { "$ref": "#/$defs/file" } }
},
"$defs": {
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" }, "hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"file": { "type": "object", "additionalProperties": false, "required": ["path", "sha256", "bytes"], "properties": { "path": { "type": "string" }, "sha256": { "$ref": "#/$defs/hash" }, "bytes": { "type": "integer", "minimum": 0 } } },
"namedFile": { "type": "object", "additionalProperties": false, "required": ["id", "path", "sha256", "bytes"], "properties": { "id": { "type": "string" }, "path": { "type": "string" }, "sha256": { "$ref": "#/$defs/hash" }, "bytes": { "type": "integer", "minimum": 0 } } }
}
}
references/project.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://styleseed.dev/schemas/project.schema.json",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "projectId", "defaults", "brand"],
"properties": {
"schemaVersion": { "const": 1 },
"projectId": { "$ref": "#/$defs/id" },
"defaults": {
"type": "object", "additionalProperties": false,
"required": ["agent", "domain", "adapter", "recipe", "palette", "profile", "fallback"],
"properties": {
"agent": { "enum": ["claude", "codex", "cursor", "factory", "gemini", "kiro", "opencode", "windsurf"] },
"domain": { "enum": ["ai-assistant", "booking", "content", "developer-tools", "ecommerce", "education", "fintech", "health", "marketplace", "none", "productivity", "saas", "social"] },
"adapter": { "enum": ["document-report", "product-ui", "single-frame", "slide-deck", "social-carousel"] },
"recipe": { "enum": ["calm-consumer", "commerce-operator", "creative-professional", "developer-platform", "editorial-authority", "enterprise-workbench", "expressive-brand", "native-mobile", "public-service"] },
"palette": { "enum": ["civic-blue", "cobalt-instrument", "deep-lime-studio", "editorial-ink", "nocturne-violet", "quiet-mineral", "signal-coral", "warm-clay-commerce"] },
"profile": { "enum": ["brutalist-lite", "editorial", "minimal-mono", "none", "swiss", "technical", "warm-dtc"] },
"fallback": { "enum": [null, "commerce-conversion", "consumer-service", "editorial-reading", "expressive-marketing", "institutional-service", "operations-console", "sequential-story", "technical-instrument"] }
}
},
"brand": {
"type": "object", "additionalProperties": false,
"required": ["keyColor", "paletteCharacter", "paletteMode", "paletteHarmony", "surfaceTemperature", "fontFamilies", "radius", "elevation", "density", "motion", "imageryRole"],
"properties": {
"keyColor": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" },
"paletteCharacter": { "enum": ["calm", "balanced", "vivid", "deep"] },
"paletteMode": { "enum": ["light", "dark"] },
"paletteHarmony": { "enum": ["auto", "tonal", "adjacent", "contrast"] },
"surfaceTemperature": { "enum": ["neutral", "warm", "cool"] },
"fontFamilies": { "type": "array", "minItems": 1, "maxItems": 8, "items": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[\\p{L}\\p{N} .,'-]+$" } },
"radius": { "enum": ["sharp", "restrained", "balanced", "soft", "pill"] },
"elevation": { "enum": ["flat", "tonal", "restrained-shadow", "layered"] },
"density": { "enum": ["compact", "comfortable", "spacious"] },
"motion": {
"type": "object", "additionalProperties": false, "required": ["seed", "intensity"],
"properties": { "seed": { "enum": ["spring", "silk", "snap", "float", "pulse"] }, "intensity": { "enum": ["restrained", "standard", "lively"] } }
},
"imageryRole": { "enum": ["data-first", "product-proof-first", "editorial-media", "people-context", "generated-atmosphere", "none"] }
}
}
},
"$defs": { "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,63}$" } }
}
scripts/compiler.mjs
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { sha256, safeProjectPath } from "./runtime-contract.mjs";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const skillDir = resolve(scriptDir, "..");
const paletteGeneratorPath = [
resolve(scriptDir, "palette-generator.mjs"),
resolve(scriptDir, "../../../../color/generator.mjs"),
resolve(scriptDir, "../../../engine/color/generator.mjs"),
].find((path) => existsSync(path));
if (!paletteGeneratorPath) throw new Error("StyleSeed palette generator is missing from the installed distribution");
const { generatePalette } = await import(pathToFileURL(paletteGeneratorPath).href);
const defaultCatalog = JSON.parse(readFileSync(resolve(skillDir, "references/catalog.json"), "utf8"));
const AUTO_RECIPE_BY_GRAMMAR = {
"consumer-service": "calm-consumer",
"operations-console": "enterprise-workbench",
"technical-instrument": "developer-platform",
"editorial-reading": "editorial-authority",
"commerce-conversion": "commerce-operator",
"institutional-service": "public-service",
"expressive-marketing": "expressive-brand",
"sequential-story": "creative-professional",
};
const AUTO_PALETTE_BY_RECIPE = {
"calm-consumer": "quiet-mineral",
"native-mobile": "quiet-mineral",
"enterprise-workbench": "cobalt-instrument",
"developer-platform": "cobalt-instrument",
"commerce-operator": "warm-clay-commerce",
"public-service": "civic-blue",
"creative-professional": "deep-lime-studio",
"editorial-authority": "editorial-ink",
"expressive-brand": "signal-coral",
};
const PALETTE_DEFAULTS_BY_RECIPE = {
"calm-consumer": { character: "calm", temperature: "neutral" },
"native-mobile": { character: "balanced", temperature: "neutral" },
"enterprise-workbench": { character: "balanced", temperature: "cool" },
"developer-platform": { character: "deep", temperature: "cool" },
"commerce-operator": { character: "balanced", temperature: "warm" },
"public-service": { character: "calm", temperature: "neutral" },
"creative-professional": { character: "vivid", temperature: "neutral" },
"editorial-authority": { character: "deep", temperature: "warm" },
"expressive-brand": { character: "vivid", temperature: "warm" },
};
function digest(value) {
return createHash("sha256").update(value).digest("hex");
}
function stableStringify(value) {
return JSON.stringify(value, null, 2);
}
function sourceEntry(id, path, content) {
return { id, path, sha256: sha256(content), bytes: Buffer.byteLength(content) };
}
function outputEntry(kind, path, content) {
return { kind, path, sha256: sha256(content), bytes: Buffer.byteLength(content) };
}
function requireCatalogContent(catalog, group, id, label) {
if (id === null || id === "none") return "";
const value = catalog[group]?.[id];
if (!value) throw new Error(`Unknown ${label} "${id}". Supported: ${Object.keys(catalog[group] ?? {}).join(", ")}`);
return value;
}
function buildLegacyLockSection(legacyLock) {
const lines = [];
const mapping = [
["domain", "App domain"],
["adapter", "Surface adapter"],
["page", "Page type"],
["grammar", "Output grammar"],
["fallback", "Grammar fallback"],
["recipeSelection", "Brand recipe"],
["paletteSelection", "Palette recipe"],
["keyColor", "Key color"],
["paletteCharacter", "Palette character"],
["paletteMode", "Palette mode"],
["paletteHarmony", "Palette harmony"],
["surfaceTemperature", "Surface temperature"],
["profile", "Aesthetic profile"],
];
for (const [key, label] of mapping) {
const value = legacyLock?.normalized?.[key];
if (value === null || value === undefined || value === "") continue;
lines.push(`- ${label}: ${value}`);
}
if (!lines.length) return "";
return `## Project design lock\n\n${lines.join("\n")}`;
}
function resolveRecipeAndPalette(selection, projectBrand, catalog) {
const recipeGrammar = selection.grammar.startsWith("reference:") ? selection.fallback : selection.grammar;
const recipe = selection.recipeSelection === "auto"
? AUTO_RECIPE_BY_GRAMMAR[recipeGrammar]
: selection.recipeSelection;
if (!recipe) throw new Error(`No automatic brand recipe for grammar "${recipeGrammar}". Pass an explicit recipe.`);
if (!catalog.recipes?.[recipe]) throw new Error(`Unknown brand recipe "${recipe}"`);
const palette = selection.paletteSelection === "auto"
? AUTO_PALETTE_BY_RECIPE[recipe]
: selection.paletteSelection;
if (!palette) throw new Error(`No automatic palette recipe for brand recipe "${recipe}". Pass an explicit palette.`);
if (!catalog.palettes?.[palette]) throw new Error(`Unknown palette recipe "${palette}"`);
const defaults = PALETTE_DEFAULTS_BY_RECIPE[recipe] ?? { character: "balanced", temperature: "neutral" };
const paletteGeneration = projectBrand.keyColor
? generatePalette({
keyColor: projectBrand.keyColor,
mode: projectBrand.paletteMode,
character: projectBrand.paletteCharacter ?? defaults.character,
harmony: projectBrand.paletteHarmony ?? "auto",
temperature: projectBrand.surfaceTemperature ?? defaults.temperature,
})
: null;
return { recipe, palette, paletteGeneration };
}
function resolveReferenceGrammar({ projectRoot, grammar, fallback, registryMode }) {
if (!grammar.startsWith("reference:")) return null;
const slug = grammar.slice("reference:".length);
if (!/^[a-z0-9][a-z0-9-]*$/u.test(slug)) throw new Error(`Invalid reference grammar slug: ${slug}`);
const baseDir = safeProjectPath(projectRoot, `.styleseed/rulesets/${slug}`);
const requiredFiles = registryMode
? ["RULESET.md", "tokens.json", "evidence.json", "checks.md", "reference-board.html", "adapter.json"]
: ["RULESET.md"];
const optionalFiles = registryMode ? [] : ["checks.md"];
const files = [];
for (const name of [...requiredFiles, ...optionalFiles]) {
const filePath = resolve(baseDir, name);
if (!existsSync(filePath)) {
if (requiredFiles.includes(name)) {
throw new Error(`Missing reference grammar contract file: .styleseed/rulesets/${slug}/${name}`);
}
continue;
}
files.push({
name,
path: `.styleseed/rulesets/${slug}/${name}`,
content: readFileSync(filePath, "utf8").trim(),
});
}
const fallbackContent = requireCatalogContent(defaultCatalog, "grammars", fallback, "fallback grammar");
const ruleSet = files.find((file) => file.name === "RULESET.md");
const checks = files.find((file) => file.name === "checks.md");
const composite = [
fallbackContent,
"",
"## Project-local reference grammar override",
"",
ruleSet.content,
...(checks ? ["", "## Reference grammar checks", "", checks.content] : []),
].join("\n");
return {
slug,
grammarSource: `project:.styleseed/rulesets/${slug}/RULESET.md`,
grammarContent: composite,
sourceFiles: files,
contract: registryMode ? "reference-contract-v2" : "reference-contract-legacy",
};
}
function buildBundle({ catalog, selected, sections }) {
return [
"# StyleSeed Effective Rule Bundle",
"",
"<!-- Generated by ss-resolve. Do not hand-edit; update project-owned inputs or resolver inputs. -->",
"",
`- Engine: ${catalog.engineVersion}`,
`- Engine revision: ${catalog.engineRevision ?? "legacy-unknown"}`,
`- Agent: ${selected.agent}`,
`- Output grammar: ${selected.grammar}`,
`- Surface adapter: ${selected.adapter}`,
`- Domain: ${selected.domain}`,
`- Page type: ${selected.page}`,
`- Brand recipe: ${selected.recipe}${selected.recipeSelection === "auto" ? " (auto)" : ""}`,
`- Palette recipe: ${selected.palette}${selected.paletteSelection === "auto" ? " (auto)" : ""}`,
...(selected.paletteGeneration ? [`- Generated palette: ${selected.paletteGeneration.keyColor} · ${selected.paletteGeneration.mode} · ${selected.paletteGeneration.character} · ${selected.paletteGeneration.harmony}`] : []),
`- Aesthetic profile: ${selected.profile}`,
"",
"Read this bundle before implementation. The agent execution contract is operational; method authority then runs core → grammar → adapter → domain/page → brand recipe → palette recipe → profile → lock → compact craft baseline, with the earlier method layer winning conflicts. Run the code gate and rendered visual gate.",
"",
...sections.flatMap((section) => [section.content, "", "---", ""]),
].join("\n").replace(/\n---\n\s*$/, "\n");
}
function toNamedInput(id, path, content) {
return { entry: sourceEntry(id, path, content), content };
}
export function compileContext({
catalog = defaultCatalog,
projectRoot,
agent,
normalizedProject,
normalizedArtifact,
inputFiles = null,
legacyLock = null,
mode = "legacy",
}) {
const registryMode = mode === "registry";
const selection = {
grammar: normalizedArtifact.selection.grammar,
adapter: normalizedArtifact.selection.adapter ?? normalizedProject.defaults.adapter,
domain: normalizedArtifact.selection.domain ?? normalizedProject.defaults.domain,
page: normalizedArtifact.selection.page,
recipeSelection: normalizedArtifact.selection.recipe ?? normalizedProject.defaults.recipe,
paletteSelection: normalizedArtifact.selection.palette ?? normalizedProject.defaults.palette,
profile: normalizedArtifact.selection.profile ?? normalizedProject.defaults.profile,
fallback: normalizedArtifact.selection.fallback ?? normalizedProject.defaults.fallback,
};
const { recipe, palette, paletteGeneration } = resolveRecipeAndPalette(selection, normalizedProject.brand, catalog);
const reference = resolveReferenceGrammar({
projectRoot,
grammar: selection.grammar,
fallback: selection.fallback,
registryMode,
});
const grammarContent = reference
? reference.grammarContent
: requireCatalogContent(catalog, "grammars", selection.grammar, "grammar");
const grammarSource = reference ? reference.grammarSource : `built-in:${selection.grammar}`;
const adapterContent = requireCatalogContent(catalog, "adapters", selection.adapter, "adapter");
const domainContent = requireCatalogContent(catalog, "domains", selection.domain, "domain");
const pageContent = requireCatalogContent(catalog, "pages", selection.page, "page");
const recipeContent = requireCatalogContent(catalog, "recipes", recipe, "brand recipe");
let paletteContent = requireCatalogContent(catalog, "palettes", palette, "palette recipe");
const profileContent = selection.profile === "none" ? "" : requireCatalogContent(catalog, "profiles", selection.profile, "profile");
if (paletteGeneration) {
paletteContent = [
paletteContent,
"",
"## Generated semantic palette override",
"",
`- Key color: ${paletteGeneration.input.keyColor}`,
`- Mode: ${paletteGeneration.input.mode}`,
`- Character: ${paletteGeneration.input.character}`,
`- Harmony: ${paletteGeneration.input.harmony}`,
`- Surface temperature: ${paletteGeneration.input.temperature}`,
`- Semantic roles: ${Object.entries(paletteGeneration.roles).map(([role, value]) => `${role}=${value}`).join(" · ")}`,
`- Contrast gates: ${paletteGeneration.contrast.map((item) => `${item.foreground}/${item.background}=${item.ratio}:1`).join(" · ")}`,
"- Allocation: 60% canvas/surfaces · 30% chrome/type/structure · at most 10% primary and accent emphasis combined.",
"- This override replaces the recipe hex values while preserving its product posture and semantic restrictions.",
"- Components consume semantic roles. Raw ramp values remain reference tokens.",
].join("\n");
}
const selected = {
agent,
grammar: selection.grammar,
grammarSource,
referenceContract: reference?.contract ?? null,
fallback: selection.fallback ?? null,
adapter: selection.adapter,
domain: selection.domain,
page: selection.page,
recipe,
recipeSelection: selection.recipeSelection,
palette,
paletteSelection: selection.paletteSelection,
paletteGeneration: paletteGeneration ? paletteGeneration.input : null,
profile: selection.profile,
};
const sections = [
{ id: "agent-execution", path: "catalog:agents", content: catalog.agents[agent] },
{ id: "core", path: "catalog:core", content: catalog.core },
{ id: "grammar", path: grammarSource, content: grammarContent },
{ id: "adapter", path: `catalog:adapters/${selection.adapter}`, content: adapterContent },
...(domainContent ? [{ id: "domain", path: `catalog:domains/${selection.domain}`, content: domainContent }] : []),
...(pageContent ? [{ id: "page", path: `catalog:pages/${selection.page}`, content: pageContent }] : []),
{ id: "recipe", path: `catalog:recipes/${recipe}`, content: recipeContent },
{ id: "palette", path: `catalog:palettes/${palette}`, content: paletteContent },
...(profileContent ? [{ id: "profile", path: `catalog:profiles/${selection.profile}`, content: profileContent }] : []),
...(legacyLock ? [{ id: "lock", path: legacyLock.path ?? "legacy:STYLESEED.md", content: buildLegacyLockSection(legacyLock) }] : []),
{ id: "craft", path: "catalog:craft", content: catalog.craft },
].filter((section) => section.content && section.content.trim().length > 0);
const bundle = buildBundle({ catalog, selected, sections });
const bundlePath = registryMode
? `.styleseed/bundles/${normalizedArtifact.id}.md`
: ".styleseed/effective-rules.md";
const paletteJsonPath = registryMode
? `.styleseed/palettes/${normalizedArtifact.id}.json`
: ".styleseed/palette.json";
const paletteCssPath = registryMode
? `.styleseed/palettes/${normalizedArtifact.id}.css`
: ".styleseed/palette.css";
const manifestPath = registryMode
? `.styleseed/manifests/${normalizedArtifact.id}.json`
: ".styleseed/manifest.json";
const paletteJson = paletteGeneration ? `${stableStringify(paletteGeneration)}\n` : null;
const paletteCss = paletteGeneration ? `${paletteGeneration.css}\n` : null;
const bundleEntry = outputEntry("bundle", bundlePath, bundle);
const outputs = [
bundleEntry,
...(paletteJson ? [outputEntry("palette-json", paletteJsonPath, paletteJson)] : []),
...(paletteCss ? [outputEntry("palette-css", paletteCssPath, paletteCss)] : []),
];
const sources = [
...sections.map((section) => sourceEntry(section.id, section.path, section.content)),
...(reference?.sourceFiles ?? []).map((file) => sourceEntry(`reference:${file.name}`, file.path, file.content)),
];
if (!registryMode) {
const legacyOutputs = outputs.map((entry) => ({ ...entry, sha256: entry.sha256.replace(/^sha256:/u, "") }));
const legacyBundle = { ...bundleEntry, sha256: bundleEntry.sha256.replace(/^sha256:/u, "") };
const legacyPaletteJson = legacyOutputs.find((entry) => entry.kind === "palette-json") ?? null;
const legacyPaletteCss = legacyOutputs.find((entry) => entry.kind === "palette-css") ?? null;
const manifest = {
schemaVersion: 1,
engineVersion: catalog.engineVersion,
engineRevision: catalog.engineRevision ?? null,
generatedAt: new Date().toISOString(),
selection: selected,
bundle: legacyBundle,
outputs: legacyOutputs,
palette: paletteGeneration
? {
json: legacyPaletteJson,
css: legacyPaletteCss,
}
: null,
sources: sources.map(({ id, sha256: fileSha, bytes }) => ({ id, sha256: fileSha.replace(/^sha256:/u, ""), bytes })),
};
return { bundle, manifest, paletteJson, paletteCss, manifestPath, outputs, sources };
}
if (!inputFiles?.project?.path || typeof inputFiles.project.content !== "string" || !inputFiles?.artifact?.path || typeof inputFiles.artifact.content !== "string") {
throw new Error("Registry compilation requires exact project and artifact input bytes");
}
const projectInput = toNamedInput("project", inputFiles.project.path, inputFiles.project.content);
const artifactInput = toNamedInput("artifact", inputFiles.artifact.path, inputFiles.artifact.content);
const methodHash = sha256({
project: normalizedProject.defaults,
brand: normalizedProject.brand,
selection: {
grammar: selected.grammar,
adapter: selected.adapter,
domain: selected.domain,
page: selected.page,
recipe: selected.recipe,
palette: selected.palette,
profile: selected.profile,
fallback: selected.fallback,
},
decisions: normalizedArtifact.decisions,
sources: sources.map((entry) => ({ id: entry.id, sha256: entry.sha256 })),
});
const validationHash = sha256({
target: normalizedArtifact.target,
implementation: normalizedArtifact.implementation,
validation: normalizedArtifact.validation,
});
const validationSlices = {
target: sha256(normalizedArtifact.target),
implementation: sha256(normalizedArtifact.implementation),
scoreFloor: sha256(normalizedArtifact.validation.scoreFloor),
requiredRenders: sha256(normalizedArtifact.validation.requiredRenders),
temporal: sha256(normalizedArtifact.validation.temporal),
humanAcceptance: sha256(normalizedArtifact.validation.humanAcceptance),
};
const manifest = {
schemaVersion: 2,
artifactId: normalizedArtifact.id,
engineVersion: catalog.engineVersion,
engineRevision: catalog.engineRevision,
distributionIntegrity: "verified",
selection: selected,
inputs: [projectInput.entry, artifactInput.entry],
sources,
methodHash,
validationHash,
validationSlices,
bundle: bundleEntry,
outputs,
};
return { bundle, manifest, paletteJson, paletteCss, manifestPath, outputs, sources };
}
export function materializeManifestOutputs(existingManifest) {
if (Array.isArray(existingManifest?.outputs)) return existingManifest.outputs;
const outputs = [];
if (existingManifest?.bundle) outputs.push({ kind: "bundle", ...existingManifest.bundle });
if (existingManifest?.palette?.json) outputs.push({ kind: "palette-json", ...existingManifest.palette.json });
if (existingManifest?.palette?.css) outputs.push({ kind: "palette-css", ...existingManifest.palette.css });
return outputs;
}
export function compareEntries(expected, actual, label, keyFields) {
if (!Array.isArray(actual)) return `${label} declarations are missing or invalid.`;
if (expected.length !== actual.length) return `${label} count mismatch: expected ${expected.length}, found ${actual.length}.`;
for (let index = 0; index < expected.length; index += 1) {
const left = expected[index];
const right = actual[index];
for (const field of keyFields) {
if (left[field] !== right?.[field]) {
return `${label} mismatch for ${left.path ?? left.id ?? `entry ${index}`}: expected ${field}=${left[field]}, found ${right?.[field] ?? "missing"}.`;
}
}
}
return null;
}
export { defaultCatalog };
scripts/distribution-integrity.mjs
import { createHash } from "node:crypto";
import { existsSync, lstatSync, readFileSync } from "node:fs";
import { dirname, resolve, sep } from "node:path";
function sha256(value) {
return createHash("sha256").update(value).digest("hex");
}
function normalizePath(value) {
return value.split(sep).join("/");
}
function isSafeDistributionPath(value) {
if (typeof value !== "string" || value.length === 0) return false;
if (value.includes("\0") || value.includes("\\")) return false;
if (value.startsWith("/") || value.startsWith("../") || value === "..") return false;
const parts = value.split("/");
return parts.every((part) => part.length > 0 && part !== "." && part !== "..");
}
function resolveDistributionLayout(scriptPath) {
const absoluteScript = normalizePath(resolve(scriptPath));
for (const [marker, skillRoot, distribution] of [
["/engine/.claude/skills/", "engine/.claude/skills", "core"],
["/.claude/skills/", ".claude/skills", "skills"],
["/.agents/skills/", ".agents/skills", "skills"],
["/skills/", "skills", "skills"],
]) {
const index = absoluteScript.lastIndexOf(marker);
if (index > 0) return { root: absoluteScript.slice(0, index), skillRoot, distribution };
}
throw new Error(`Cannot locate the StyleSeed distribution root from: ${scriptPath}`);
}
function physicalDistributionPath(relativePath, skillRoot) {
const canonicalPrefix = "engine/.claude/skills/";
return relativePath.startsWith(canonicalPrefix) && skillRoot !== "engine/.claude/skills"
? `${skillRoot}/${relativePath.slice(canonicalPrefix.length)}`
: relativePath;
}
function resolveContainedPath(root, relativePath, skillRoot) {
if (!isSafeDistributionPath(relativePath)) {
throw new Error(`Invalid distribution path: ${relativePath}`);
}
const physicalPath = physicalDistributionPath(relativePath, skillRoot);
if (!isSafeDistributionPath(physicalPath)) {
throw new Error(`Invalid physical distribution path: ${physicalPath}`);
}
const absolutePath = resolve(root, physicalPath);
const normalizedRoot = normalizePath(root);
const normalizedAbsolute = normalizePath(absolutePath);
if (normalizedAbsolute !== normalizedRoot && !normalizedAbsolute.startsWith(`${normalizedRoot}/`)) {
throw new Error(`Distribution path escapes root: ${relativePath}`);
}
let current = root;
for (const part of physicalPath.split("/")) {
current = resolve(current, part);
if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
throw new Error(`Distribution path traverses a symlink: ${relativePath}`);
}
}
return absolutePath;
}
function compareByPath(left, right) {
if (left.path < right.path) return -1;
if (left.path > right.path) return 1;
return 0;
}
export function getDistribution(catalog, name = "core") {
const distribution = catalog?.distributions?.[name];
if (
!distribution
|| typeof distribution.revision !== "string"
|| !/^sha256:[0-9a-f]{64}$/u.test(distribution.revision)
|| !Array.isArray(distribution.files)
) {
throw new Error(`Installed catalog does not expose a verifiable distributions.${name} inventory`);
}
return distribution;
}
export function getCoreDistribution(catalog) {
return getDistribution(catalog, "core");
}
export function verifyDistribution({ catalog, scriptPath }) {
if (typeof scriptPath !== "string" || scriptPath.length === 0) {
throw new Error("verifyDistribution requires scriptPath");
}
const { root, skillRoot, distribution: distributionName } = resolveDistributionLayout(scriptPath);
const distribution = getDistribution(catalog, distributionName);
const files = distribution.files.map((entry, index) => {
if (
!entry
|| typeof entry.path !== "string"
|| !/^([0-9a-f]{64})$/u.test(entry.sha256 ?? "")
|| !Number.isInteger(entry.bytes)
|| entry.bytes < 0
) {
throw new Error(`Invalid distribution file entry at index ${index}`);
}
return {
path: entry.path,
sha256: entry.sha256,
bytes: entry.bytes,
};
});
const sorted = [...files].sort(compareByPath);
for (let index = 0; index < files.length; index += 1) {
if (files[index].path !== sorted[index].path) {
throw new Error(`Distribution file inventory is not sorted by path: ${files[index].path}`);
}
if (index > 0 && files[index - 1].path === files[index].path) {
throw new Error(`Distribution file inventory contains a duplicate path: ${files[index].path}`);
}
}
const actualDigests = [];
const mismatches = [];
for (const file of files) {
const absolutePath = resolveContainedPath(root, file.path, skillRoot);
if (!existsSync(absolutePath)) {
mismatches.push({ path: file.path, reason: "missing" });
continue;
}
const stats = lstatSync(absolutePath);
if (!stats.isFile()) {
mismatches.push({ path: file.path, reason: "not-a-regular-file" });
continue;
}
if (stats.nlink !== 1) {
mismatches.push({ path: file.path, reason: "link-count-mismatch", actualLinks: stats.nlink });
continue;
}
const content = readFileSync(absolutePath);
const actualBytes = stats.size;
const actualSha256 = sha256(content);
actualDigests.push({ path: file.path, sha256: actualSha256 });
if (actualBytes !== file.bytes || actualSha256 !== file.sha256) {
mismatches.push({
path: file.path,
reason: actualBytes !== file.bytes ? "bytes-mismatch" : "sha256-mismatch",
expectedBytes: file.bytes,
actualBytes,
expectedSha256: file.sha256,
actualSha256,
});
}
}
const computedRevision = `sha256:${sha256(
actualDigests
.sort(compareByPath)
.map((entry) => `${entry.path}\0${entry.sha256}\n`)
.join(""),
)}`;
if (mismatches.some((entry) => entry.reason === "missing")) {
return { status: "incomplete", distribution: distributionName, computedRevision, mismatches };
}
if (computedRevision !== distribution.revision) {
mismatches.push({
path: "__distribution_revision__",
reason: "revision-mismatch",
expectedRevision: distribution.revision,
actualRevision: computedRevision,
});
}
if (mismatches.length > 0) {
return { status: "tampered", distribution: distributionName, computedRevision, mismatches };
}
return { status: "verified", distribution: distributionName, computedRevision, mismatches: [] };
}
scripts/migrate-project.mjs
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, readFileSync } from "node:fs";
import { basename, dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { canonicalJson, normalizeArtifact, normalizeIndex, normalizeProject, safeProjectPath } from "./runtime-contract.mjs";
import { getRegistryPaths } from "./project-registry.mjs";
const AUTO_RECIPE_BY_GRAMMAR = {
"consumer-service": "calm-consumer",
"operations-console": "enterprise-workbench",
"technical-instrument": "developer-platform",
"editorial-reading": "editorial-authority",
"commerce-conversion": "commerce-operator",
"institutional-service": "public-service",
"expressive-marketing": "expressive-brand",
"sequential-story": "creative-professional",
};
const AUTO_PALETTE_BY_RECIPE = {
"calm-consumer": "quiet-mineral",
"native-mobile": "quiet-mineral",
"enterprise-workbench": "cobalt-instrument",
"developer-platform": "cobalt-instrument",
"commerce-operator": "warm-clay-commerce",
"public-service": "civic-blue",
"creative-professional": "deep-lime-studio",
"editorial-authority": "editorial-ink",
"expressive-brand": "signal-coral",
};
const PROJECT_DEFAULTS = Object.freeze({
agent: "codex",
domain: "developer-tools",
adapter: "product-ui",
recipe: "expressive-brand",
palette: "signal-coral",
profile: "none",
fallback: null,
});
const BRAND_DEFAULTS = Object.freeze({
keyColor: "#6C5CE7",
paletteCharacter: "vivid",
paletteMode: "light",
paletteHarmony: "auto",
surfaceTemperature: "neutral",
fontFamilies: ["Inter"],
radius: "soft",
elevation: "restrained-shadow",
density: "comfortable",
motion: { seed: "spring", intensity: "restrained" },
imageryRole: "product-proof-first",
});
const LEGACY_FIELDS = Object.freeze({
"App domain": { kind: "selection", target: "domain" },
"Surface adapter": { kind: "selection", target: "adapter" },
"Page type": { kind: "selection", target: "page" },
"Output grammar": { kind: "selection", target: "grammar" },
"Grammar fallback": { kind: "selection", target: "fallback" },
"Brand recipe": { kind: "selection", target: "recipe" },
"Palette recipe": { kind: "selection", target: "palette" },
"Aesthetic profile": { kind: "brand", target: "profile" },
"Key color": { kind: "brand", target: "keyColor" },
"Primary action": { kind: "brand", target: "keyColor", source: "primary-action-color" },
"Palette character": { kind: "brand", target: "paletteCharacter" },
"Palette mode": { kind: "brand", target: "paletteMode" },
"Palette harmony": { kind: "brand", target: "paletteHarmony" },
"Surface temperature": { kind: "brand", target: "surfaceTemperature" },
Font: { kind: "brand", target: "fontFamilies" },
Radius: { kind: "brand", target: "radius" },
Elevation: { kind: "brand", target: "elevation" },
Density: { kind: "brand", target: "density" },
Motion: { kind: "brand", target: "motion" },
"Imagery/data role": { kind: "brand", target: "imageryRole" },
});
const skillDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const catalog = JSON.parse(
readFileSync(resolve(skillDir, "references/catalog.json"), "utf8"),
);
function parseArgs(argv) {
const out = { dryRun: true, fromLock: "STYLESEED.md", artifact: "default" };
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index];
if (!value.startsWith("--")) throw new Error(`Unexpected argument: ${value}`);
const key = value.slice(2);
if (key === "dry-run") {
out.dryRun = true;
continue;
}
if (key === "write") {
out.dryRun = false;
continue;
}
if (key === "help") {
out.help = true;
continue;
}
const next = argv[index + 1];
if (!next || next.startsWith("--")) throw new Error(`Missing value for --${key}`);
out[key] = next;
index += 1;
}
return out;
}
function help() {
return `StyleSeed project migration
Usage:
node migrate-project.mjs --project-root . --from-lock STYLESEED.md --artifact default --dry-run
node migrate-project.mjs --project-root . --from-lock STYLESEED.md --artifact default --write
`;
}
function sha256(value) {
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
}
function toJsonText(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
function safeProjectId(projectRoot) {
const slug = basename(projectRoot).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
if (!slug || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(slug)) return "styleseed-project";
return slug;
}
function parseLegacyLines(text) {
const recognized = new Map();
const unmigratedFields = [];
for (const line of text.split(/\r?\n/u)) {
const match = line.match(/^\s*-\s+([^:]+):\s*(.+?)\s*$/u);
if (!match) continue;
const label = match[1].trim();
const value = match[2].replace(/\s+#.*$/u, "").trim();
const metadata = LEGACY_FIELDS[label];
if (!metadata) {
unmigratedFields.push({ field: label, values: [value], reason: "unknown" });
continue;
}
const entry = recognized.get(label) ?? { ...metadata, field: label, values: [] };
entry.values.push(value);
recognized.set(label, entry);
}
return { recognized, unmigratedFields };
}
function addUnmigrated(unmigratedFields, field, values, reason) {
unmigratedFields.push({ field, values: [...values], reason });
}
function chooseCatalogId(group, value, fallback, unmigratedFields, field) {
if (!value) return fallback;
if (value === "none" && group === "profiles") return "none";
if (value === "none" && group === "domains") return "none";
if (value === "none" && group === "pages") return "none";
if (value === "none" && group === "grammars") return null;
if (Object.hasOwn(catalog[group], value)) return value;
addUnmigrated(unmigratedFields, field, [value], "unsupported");
return fallback;
}
function chooseSingle(recognized, label, unmigratedFields) {
const entry = recognized.get(label);
if (!entry) return null;
if (entry.values.length !== 1) {
addUnmigrated(unmigratedFields, label, entry.values, "duplicate");
return null;
}
return entry.values[0];
}
function chooseColor(recognized, unmigratedFields) {
const candidates = [];
for (const label of ["Key color", "Primary action"]) {
const entry = recognized.get(label);
if (!entry) continue;
if (entry.values.length !== 1) {
addUnmigrated(unmigratedFields, label, entry.values, "duplicate");
continue;
}
candidates.push({ field: label, value: entry.values[0] });
}
if (!candidates.length) return BRAND_DEFAULTS.keyColor;
const unique = [...new Set(candidates.map((item) => item.value.toUpperCase()))];
if (unique.length !== 1) {
addUnmigrated(
unmigratedFields,
"brand.keyColor",
candidates.map((item) => `${item.field}=${item.value}`),
"conflict",
);
return BRAND_DEFAULTS.keyColor;
}
if (!/^#[0-9A-Fa-f]{6}$/u.test(unique[0])) {
addUnmigrated(unmigratedFields, "brand.keyColor", [unique[0]], "unsupported");
return BRAND_DEFAULTS.keyColor;
}
return unique[0];
}
function chooseFontFamilies(recognized, unmigratedFields) {
const value = chooseSingle(recognized, "Font", unmigratedFields);
if (!value) return BRAND_DEFAULTS.fontFamilies;
if (value.length > 80 || !/^[\p{L}\p{N} .,'-]+$/u.test(value)) {
addUnmigrated(unmigratedFields, "Font", [value], "unsupported");
return BRAND_DEFAULTS.fontFamilies;
}
return [value];
}
function chooseMotion(recognized, unmigratedFields) {
const value = chooseSingle(recognized, "Motion", unmigratedFields);
if (!value) return BRAND_DEFAULTS.motion;
const match = value.trim().match(/^(spring|silk|snap|float|pulse)\s+(restrained|standard|lively)$/iu);
if (!match) {
addUnmigrated(unmigratedFields, "Motion", [value], "unsupported");
return BRAND_DEFAULTS.motion;
}
return { seed: match[1].toLowerCase(), intensity: match[2].toLowerCase() };
}
function chooseRecipe(grammar, recognized, unmigratedFields) {
const requested = chooseSingle(recognized, "Brand recipe", unmigratedFields);
if (!requested) return PROJECT_DEFAULTS.recipe;
if (requested === "auto") {
const recipe = AUTO_RECIPE_BY_GRAMMAR[grammar];
if (!recipe) {
addUnmigrated(unmigratedFields, "Brand recipe", [requested], "unsupported");
return PROJECT_DEFAULTS.recipe;
}
return recipe;
}
return chooseCatalogId("recipes", requested, PROJECT_DEFAULTS.recipe, unmigratedFields, "Brand recipe");
}
function choosePalette(recipe, recognized, unmigratedFields) {
const requested = chooseSingle(recognized, "Palette recipe", unmigratedFields);
if (!requested) return PROJECT_DEFAULTS.palette;
if (requested === "auto") {
const palette = AUTO_PALETTE_BY_RECIPE[recipe];
if (!palette) {
addUnmigrated(unmigratedFields, "Palette recipe", [requested], "unsupported");
return PROJECT_DEFAULTS.palette;
}
return palette;
}
return chooseCatalogId("palettes", requested, PROJECT_DEFAULTS.palette, unmigratedFields, "Palette recipe");
}
function chooseImageryRole(recognized, unmigratedFields) {
const value = chooseSingle(recognized, "Imagery/data role", unmigratedFields);
if (!value) return BRAND_DEFAULTS.imageryRole;
if (!BRAND_DEFAULTS.imageryRole || !["data-first", "product-proof-first", "editorial-media", "people-context", "generated-atmosphere", "none"].includes(value)) {
addUnmigrated(unmigratedFields, "Imagery/data role", [value], "unsupported");
return BRAND_DEFAULTS.imageryRole;
}
return value;
}
function buildMigration(projectRoot, lockText, artifactId) {
const { recognized, unmigratedFields } = parseLegacyLines(lockText);
const grammar = chooseCatalogId(
"grammars",
chooseSingle(recognized, "Output grammar", unmigratedFields),
"consumer-service",
unmigratedFields,
"Output grammar",
);
const recipe = chooseRecipe(grammar, recognized, unmigratedFields);
const palette = choosePalette(recipe, recognized, unmigratedFields);
const project = normalizeProject({
schemaVersion: 1,
projectId: safeProjectId(projectRoot),
defaults: {
agent: PROJECT_DEFAULTS.agent,
domain: chooseCatalogId("domains", chooseSingle(recognized, "App domain", unmigratedFields), PROJECT_DEFAULTS.domain, unmigratedFields, "App domain"),
adapter: chooseCatalogId("adapters", chooseSingle(recognized, "Surface adapter", unmigratedFields), PROJECT_DEFAULTS.adapter, unmigratedFields, "Surface adapter"),
recipe,
palette,
profile: chooseCatalogId("profiles", chooseSingle(recognized, "Aesthetic profile", unmigratedFields), PROJECT_DEFAULTS.profile, unmigratedFields, "Aesthetic profile"),
fallback: chooseCatalogId("grammars", chooseSingle(recognized, "Grammar fallback", unmigratedFields), PROJECT_DEFAULTS.fallback, unmigratedFields, "Grammar fallback"),
},
brand: {
keyColor: chooseColor(recognized, unmigratedFields),
paletteCharacter: chooseCatalogEnum("paletteCharacter", chooseSingle(recognized, "Palette character", unmigratedFields), BRAND_DEFAULTS.paletteCharacter, unmigratedFields, "Palette character"),
paletteMode: chooseCatalogEnum("paletteMode", chooseSingle(recognized, "Palette mode", unmigratedFields), BRAND_DEFAULTS.paletteMode, unmigratedFields, "Palette mode"),
paletteHarmony: chooseCatalogEnum("paletteHarmony", chooseSingle(recognized, "Palette harmony", unmigratedFields), BRAND_DEFAULTS.paletteHarmony, unmigratedFields, "Palette harmony"),
surfaceTemperature: chooseCatalogEnum("surfaceTemperature", chooseSingle(recognized, "Surface temperature", unmigratedFields), BRAND_DEFAULTS.surfaceTemperature, unmigratedFields, "Surface temperature"),
fontFamilies: chooseFontFamilies(recognized, unmigratedFields),
radius: chooseCatalogEnum("radius", chooseSingle(recognized, "Radius", unmigratedFields), BRAND_DEFAULTS.radius, unmigratedFields, "Radius"),
elevation: chooseCatalogEnum("elevation", chooseSingle(recognized, "Elevation", unmigratedFields), BRAND_DEFAULTS.elevation, unmigratedFields, "Elevation"),
density: chooseCatalogEnum("density", chooseSingle(recognized, "Density", unmigratedFields), BRAND_DEFAULTS.density, unmigratedFields, "Density"),
motion: chooseMotion(recognized, unmigratedFields),
imageryRole: chooseImageryRole(recognized, unmigratedFields),
},
}, catalog);
const artifact = normalizeArtifact({
schemaVersion: 1,
id: artifactId,
target: { kind: "route", locator: "/" },
selection: {
grammar,
adapter: null,
domain: null,
page: chooseCatalogId("pages", chooseSingle(recognized, "Page type", unmigratedFields), "none", unmigratedFields, "Page type"),
recipe: null,
palette: null,
profile: null,
fallback: null,
},
decisions: {
primaryDecision: "Primary decision pending artifact-specific migration.",
primaryAction: "Primary action pending artifact-specific migration.",
signatureMove: "Signature move pending artifact-specific migration.",
},
implementation: {
sourceRoots: ["src"],
tokenFiles: [],
},
validation: {
scoreFloor: 80,
requiredRenders: [{ id: "desktop-loaded", state: "loaded", viewport: { width: 1440, height: 1000 } }],
temporal: { required: false, scenarios: [] },
humanAcceptance: false,
},
}, project, catalog);
const index = normalizeIndex({
schemaVersion: 1,
artifacts: [{ id: artifactId, config: `${artifactId}.json` }],
});
return {
project,
index,
artifact,
unmigratedFields: unmigratedFields.sort((left, right) => left.field.localeCompare(right.field) || left.reason.localeCompare(right.reason)),
};
}
function chooseCatalogEnum(group, value, fallback, unmigratedFields, field) {
if (!value) return fallback;
const allowed = {
paletteCharacter: ["calm", "balanced", "vivid", "deep"],
paletteMode: ["light", "dark"],
paletteHarmony: ["auto", "tonal", "adjacent", "contrast"],
surfaceTemperature: ["neutral", "warm", "cool"],
radius: ["sharp", "restrained", "balanced", "soft", "pill"],
elevation: ["flat", "tonal", "restrained-shadow", "layered"],
density: ["compact", "comfortable", "spacious"],
}[group];
if (allowed.includes(value)) return value;
addUnmigrated(unmigratedFields, field, [value], "unsupported");
return fallback;
}
function assertNoOverwrite(targets) {
const existing = targets.filter((target) => existsSync(target.absolutePath));
if (existing.length) {
throw new Error(`Refusing to overwrite existing migration targets: ${existing.map((item) => item.relativePath).join(", ")}`);
}
}
function atomicWrite(path, text) {
mkdirSync(dirname(path), { recursive: true });
const tempPath = resolve(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
try {
writeFileSync(tempPath, text, { flag: "wx" });
renameSync(tempPath, path);
} catch (error) {
rmSync(tempPath, { force: true });
throw error;
}
}
export function migrateProject({ projectRoot, fromLock = "STYLESEED.md", artifact = "default", dryRun = true }) {
if (!/^[a-z0-9][a-z0-9-]{0,63}$/u.test(artifact)) throw new Error(`Artifact ID must be a safe ID: ${artifact}`);
const root = resolve(projectRoot);
const lockPath = safeProjectPath(root, fromLock);
const lockText = readFileSync(lockPath, "utf8");
const migration = buildMigration(root, lockText, artifact);
const paths = getRegistryPaths(root);
const targets = [
{ relativePath: ".styleseed/project.json", absolutePath: paths.projectFile, text: toJsonText(migration.project) },
{ relativePath: ".styleseed/artifacts/index.json", absolutePath: paths.indexFile, text: toJsonText(migration.index) },
{ relativePath: `.styleseed/artifacts/${artifact}.json`, absolutePath: resolve(paths.artifactsDir, `${artifact}.json`), text: toJsonText(migration.artifact) },
];
assertNoOverwrite(targets);
if (!dryRun) {
for (const target of targets) atomicWrite(target.absolutePath, target.text);
}
return {
dryRun,
artifact,
targets: targets.map((target) => ({
path: target.relativePath,
bytes: Buffer.byteLength(target.text),
sha256: sha256(target.text),
content: JSON.parse(target.text),
})),
unmigratedFields: migration.unmigratedFields,
legacyLockSha256: sha256(lockText),
};
}
const args = parseArgs(process.argv.slice(2));
if (args.help) {
console.log(help());
process.exit(0);
}
try {
const result = migrateProject({
projectRoot: args["project-root"] ?? process.cwd(),
fromLock: args["from-lock"],
artifact: args.artifact,
dryRun: args.dryRun,
});
process.stdout.write(toJsonText(result));
} catch (error) {
process.stderr.write(`${error.message}\n`);
process.exit(1);
}
scripts/palette-generator.mjs
const VERSION = "1.0.0";
const CHARACTER = {
calm: { label: "Calm", chroma: 0.72, accent: 0.78, surface: 0.010, primaryLight: 0.48, primaryDark: 0.76 },
balanced: { label: "Balanced", chroma: 0.94, accent: 0.96, surface: 0.014, primaryLight: 0.50, primaryDark: 0.75 },
vivid: { label: "Vivid", chroma: 1.14, accent: 1.15, surface: 0.018, primaryLight: 0.53, primaryDark: 0.78 },
deep: { label: "Deep", chroma: 0.90, accent: 0.92, surface: 0.014, primaryLight: 0.42, primaryDark: 0.70 },
};
const TEMPERATURE_HUE = { neutral: null, warm: 65, cool: 245 };
const STATUS_HUES = { success: 148, warning: 78, danger: 27 };
const RAMP_LIGHTNESS = [0.98, 0.94, 0.88, 0.80, 0.70, 0.61, 0.53, 0.45, 0.36, 0.27, 0.18];
const RAMP_KEYS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
function option(value, fallback, supported, label) {
if (value === undefined || value === null || value === "") return fallback;
if (!supported.includes(value)) {
throw new Error(`Invalid ${label}: ${value}. Supported: ${supported.join(", ")}`);
}
return value;
}
function clamp(value, min = 0, max = 1) {
return Math.min(max, Math.max(min, value));
}
function wrapHue(value) {
return ((value % 360) + 360) % 360;
}
function hueDistance(a, b) {
const direct = Math.abs(wrapHue(a) - wrapHue(b));
return Math.min(direct, 360 - direct);
}
function normalizeHex(input) {
const value = String(input || "").trim();
const expanded = /^#[0-9a-f]{3}$/i.test(value)
? `#${value.slice(1).split("").map((part) => part + part).join("")}`
: value;
if (!/^#[0-9a-f]{6}$/i.test(expanded)) throw new Error(`Invalid key color: ${input}`);
return expanded.toUpperCase();
}
function hexToRgb(hex) {
const value = normalizeHex(hex);
return [1, 3, 5].map((index) => Number.parseInt(value.slice(index, index + 2), 16) / 255);
}
function linearize(value) {
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
}
function delinearize(value) {
return value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055;
}
function rgbToOklab(rgb) {
const [r, g, b] = rgb.map(linearize);
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
return {
l: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
};
}
function oklabToLinearRgb({ l, a, b }) {
const ll = (l + 0.3963377774 * a + 0.2158037573 * b) ** 3;
const mm = (l - 0.1055613458 * a - 0.0638541728 * b) ** 3;
const ss = (l - 0.0894841775 * a - 1.291485548 * b) ** 3;
return [
4.0767416621 * ll - 3.3077115913 * mm + 0.2309699292 * ss,
-1.2684380046 * ll + 2.6097574011 * mm - 0.3413193965 * ss,
-0.0041960863 * ll - 0.7034186147 * mm + 1.707614701 * ss,
];
}
function oklchToOklab({ l, c, h }) {
const radians = wrapHue(h) * Math.PI / 180;
return { l, a: c * Math.cos(radians), b: c * Math.sin(radians) };
}
function hexToOklch(hex) {
const lab = rgbToOklab(hexToRgb(hex));
const c = Math.sqrt(lab.a ** 2 + lab.b ** 2);
return { l: lab.l, c, h: c < 0.0001 ? 0 : wrapHue(Math.atan2(lab.b, lab.a) * 180 / Math.PI) };
}
function inSrgb(linearRgb) {
return linearRgb.every((value) => value >= -0.000001 && value <= 1.000001);
}
function fitOklch(color) {
const target = { l: clamp(color.l), c: Math.max(0, color.c), h: wrapHue(color.h) };
if (inSrgb(oklabToLinearRgb(oklchToOklab(target)))) return target;
let low = 0;
let high = target.c;
for (let index = 0; index < 24; index += 1) {
const c = (low + high) / 2;
if (inSrgb(oklabToLinearRgb(oklchToOklab({ ...target, c })))) low = c;
else high = c;
}
return { ...target, c: low };
}
function oklchToHex(input) {
const color = fitOklch(input);
const rgb = oklabToLinearRgb(oklchToOklab(color)).map((value) => clamp(delinearize(value)));
return `#${rgb.map((value) => Math.round(value * 255).toString(16).padStart(2, "0")).join("")}`.toUpperCase();
}
function luminance(hex) {
const [r, g, b] = hexToRgb(hex).map(linearize);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contrast(a, b) {
const values = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (values[0] + 0.05) / (values[1] + 0.05);
}
function readableForeground(background, dark, light, target = 4.5) {
const candidates = [dark, light].map((hex) => ({ hex, ratio: contrast(hex, background) }));
candidates.sort((a, b) => b.ratio - a.ratio);
return { ...candidates[0], pass: candidates[0].ratio >= target };
}
function accessibleFill(color, dark, light, target = 4.5) {
const firstHex = oklchToHex(color);
const firstForeground = readableForeground(firstHex, dark, light, target);
if (firstForeground.pass) return { color: fitOklch(color), hex: firstHex, foreground: firstForeground.hex, ratio: firstForeground.ratio, adjusted: false };
let winner = null;
for (let step = 3; step <= 97; step += 1) {
const candidate = fitOklch({ ...color, l: step / 100 });
const hex = oklchToHex(candidate);
const foreground = readableForeground(hex, dark, light, target);
if (!foreground.pass) continue;
const distance = Math.abs(candidate.l - color.l) + Math.abs(candidate.c - color.c) * 0.25;
if (!winner || distance < winner.distance) winner = { color: candidate, hex, foreground: foreground.hex, ratio: foreground.ratio, distance };
}
if (!winner) throw new Error("Unable to derive an accessible fill");
return { ...winner, adjusted: true };
}
function rolePair(roles, foreground, background, minimum) {
const ratio = contrast(roles[foreground], roles[background]);
return { foreground, background, ratio: Number(ratio.toFixed(2)), minimum, pass: ratio >= minimum };
}
function chooseAccentHue(keyHue, harmony, character) {
const offsets = {
tonal: [0, 24, -24],
adjacent: [42, -42, 68, -68],
contrast: [145, -145, 180, 118, -118],
auto: character === "calm" ? [42, -42, 68, -68] : character === "deep" ? [-72, 72, 132, -132] : [118, -118, 148, -148, 42],
}[harmony];
const candidates = offsets.map((offset, index) => {
const hue = wrapHue(keyHue + offset);
const semanticDistance = Math.min(...Object.values(STATUS_HUES).map((statusHue) => hueDistance(hue, statusHue)));
const keyDistance = hueDistance(hue, keyHue);
const separationTarget = harmony === "tonal" ? 12 : harmony === "adjacent" ? 46 : 130;
const score = semanticDistance * 0.75 - Math.abs(keyDistance - separationTarget) * 0.3 - index * 0.5;
return { hue, offset, score, semanticDistance };
});
candidates.sort((a, b) => b.score - a.score);
return candidates[0];
}
function surfaceColor(lightness, hue, chroma) {
return oklchToHex({ l: lightness, c: chroma, h: hue });
}
function makeRamp(hue, chroma) {
return Object.fromEntries(RAMP_LIGHTNESS.map((lightness, index) => {
const centerWeight = 0.62 + 0.38 * (1 - Math.abs(lightness - 0.56) / 0.5);
return [RAMP_KEYS[index], oklchToHex({ l: lightness, c: chroma * clamp(centerWeight, 0.5, 1), h: hue })];
}));
}
function tokenCss(result) {
const declarations = Object.entries(result.roles)
.map(([role, value]) => ` --ss-${role.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)}: ${value};`)
.join("\n");
const ramps = Object.entries(result.ramps)
.flatMap(([name, ramp]) => Object.entries(ramp)
.map(([step, value]) => ` --ss-${name}-${step}: ${value};`))
.join("\n");
return `:root {\n${declarations}\n${ramps}\n}`;
}
export function generatePalette(options = {}) {
const keyColor = normalizeHex(options.keyColor || "#276B5E");
const mode = option(options.mode, "light", ["light", "dark"], "mode");
const character = option(options.character, "balanced", Object.keys(CHARACTER), "character");
const harmony = option(options.harmony, "auto", ["auto", "tonal", "adjacent", "contrast"], "harmony");
const temperature = option(options.temperature, "neutral", Object.keys(TEMPERATURE_HUE), "temperature");
const config = CHARACTER[character];
const key = hexToOklch(keyColor);
const keyHue = key.c < 0.025 ? 250 : key.h;
const neutralHue = TEMPERATURE_HUE[temperature] ?? keyHue;
const neutralChroma = temperature === "neutral" ? Math.min(config.surface, Math.max(0.004, key.c * 0.08)) : config.surface;
const darkInk = surfaceColor(mode === "dark" ? 0.10 : 0.15, neutralHue, neutralChroma * 0.55);
const lightPaper = surfaceColor(mode === "dark" ? 0.96 : 0.99, neutralHue, neutralChroma * 0.42);
const primaryChroma = clamp(Math.max(key.c, 0.095) * config.chroma, 0.075, character === "vivid" ? 0.25 : 0.19);
const primary = accessibleFill({ l: mode === "dark" ? config.primaryDark : config.primaryLight, c: primaryChroma, h: keyHue }, darkInk, lightPaper);
const accentChoice = chooseAccentHue(keyHue, harmony, character);
const accentChroma = clamp(Math.max(primary.color.c * config.accent, 0.085), 0.075, character === "vivid" ? 0.22 : 0.17);
const accent = accessibleFill({ l: mode === "dark" ? 0.79 : 0.48, c: accentChroma, h: accentChoice.hue }, darkInk, lightPaper);
const levels = mode === "dark"
? { background: 0.105, surface: 0.155, chrome: 0.215, muted: 0.72, border: 0.34 }
: { background: 0.965, surface: 0.992, chrome: 0.91, muted: 0.43, border: 0.79 };
const background = surfaceColor(levels.background, neutralHue, neutralChroma);
const surface = surfaceColor(levels.surface, neutralHue, neutralChroma * 0.55);
const chrome = surfaceColor(levels.chrome, neutralHue, neutralChroma * 1.25);
const foreground = mode === "dark" ? lightPaper : darkInk;
const mutedForegroundCandidate = surfaceColor(levels.muted, neutralHue, neutralChroma * 0.7);
const mutedForeground = contrast(mutedForegroundCandidate, background) >= 4.5
? mutedForegroundCandidate
: readableForeground(background, darkInk, lightPaper, 4.5).hex;
const chromeForeground = readableForeground(chrome, darkInk, lightPaper, 4.5).hex;
const status = Object.fromEntries(Object.entries(STATUS_HUES).map(([role, hue]) => {
const chroma = role === "warning" ? 0.12 : 0.14;
return [role, accessibleFill({ l: mode === "dark" ? 0.78 : 0.44, c: chroma, h: hue }, darkInk, lightPaper)];
}));
const focusCandidates = [accent.hex, primary.hex, surfaceColor(mode === "dark" ? 0.82 : 0.48, 250, 0.16)];
const focus = focusCandidates
.map((hex) => ({ hex, score: Math.min(contrast(hex, background), contrast(hex, surface)) }))
.sort((a, b) => b.score - a.score)[0].hex;
const roles = {
background,
surface,
chrome,
foreground,
mutedForeground,
chromeForeground,
border: surfaceColor(levels.border, neutralHue, neutralChroma * 1.4),
primary: primary.hex,
primaryForeground: primary.foreground,
accent: accent.hex,
accentForeground: accent.foreground,
focus,
success: status.success.hex,
successForeground: status.success.foreground,
warning: status.warning.hex,
warningForeground: status.warning.foreground,
danger: status.danger.hex,
dangerForeground: status.danger.foreground,
};
const contrastChecks = [
rolePair(roles, "foreground", "background", 4.5),
rolePair(roles, "mutedForeground", "background", 4.5),
rolePair(roles, "foreground", "surface", 4.5),
rolePair(roles, "chromeForeground", "chrome", 4.5),
rolePair(roles, "primaryForeground", "primary", 4.5),
rolePair(roles, "accentForeground", "accent", 4.5),
rolePair(roles, "successForeground", "success", 4.5),
rolePair(roles, "warningForeground", "warning", 4.5),
rolePair(roles, "dangerForeground", "danger", 4.5),
rolePair(roles, "focus", "background", 3),
rolePair(roles, "focus", "surface", 3),
];
const result = {
schemaVersion: 1,
generator: { name: "StyleSeed Palette Engine", version: VERSION, colorSpace: "OKLCH", targetGamut: "sRGB" },
input: { keyColor, mode, character, harmony, temperature },
normalizedKey: {
hex: keyColor,
oklch: { l: Number(key.l.toFixed(4)), c: Number(key.c.toFixed(4)), h: Number(keyHue.toFixed(2)) },
},
decisions: {
character: `${config.label} character controls lightness/chroma before hue harmony.`,
accent: { hue: Number(accentChoice.hue.toFixed(2)), offset: accentChoice.offset, method: harmony, avoidsStatusCollisionBy: Number(accentChoice.semanticDistance.toFixed(2)) },
primaryAdjustedForContrast: primary.adjusted,
accentAdjustedForContrast: accent.adjusted,
allocation: { dominant: "60% canvas and surfaces", secondary: "30% chrome, type, and supporting structure", accent: "10% primary/accent emphasis combined" },
},
ramps: { primary: makeRamp(keyHue, primaryChroma), accent: makeRamp(accentChoice.hue, accentChroma) },
roles,
contrast: contrastChecks,
valid: contrastChecks.every((item) => item.pass),
assetBrief: {
anchors: [background, primary.hex, accent.hex],
hierarchy: "background dominant; primary identifies the product/action; accent is rare supporting emphasis",
avoid: ["equal-area competing accents", "rainbow category decoration", "text baked into generated imagery"],
},
reasoning: [
"Character is chosen before harmony: lightness and chroma drive perceived calm or intensity more reliably than hue geometry alone.",
"The key hue is preserved while chroma is reduced to the sRGB gamut boundary instead of clipping RGB channels.",
`The ${harmony} accent candidate is scored for separation from the key and reserved success, warning, and danger hues.`,
"Reference colors are mapped through semantic roles; components should never consume raw ramp values directly.",
"Every text, action, status, and focus pair is checked after generation; failing fills are moved in lightness while preserving hue identity.",
],
};
return { ...result, css: tokenCss(result) };
}
export { contrast, hexToOklch, normalizeHex, oklchToHex, tokenCss as generatePaletteCss };
scripts/project-registry.mjs
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
SAFE_ID,
normalizeArtifact,
normalizeIndex,
normalizeProject,
parseStrictJson,
safeProjectPath,
} from "./runtime-contract.mjs";
const skillDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const defaultCatalog = JSON.parse(
readFileSync(resolve(skillDir, "references/catalog.json"), "utf8"),
);
export function getRegistryPaths(projectRoot) {
const root = resolve(projectRoot);
const styleseedDir = resolve(root, ".styleseed");
const artifactsDir = resolve(styleseedDir, "artifacts");
return {
projectRoot: root,
styleseedDir,
artifactsDir,
projectFile: resolve(styleseedDir, "project.json"),
indexFile: resolve(artifactsDir, "index.json"),
};
}
function readStrictJson(path, maxBytes) {
return parseStrictJson(readFileSync(path, "utf8"), { maxBytes });
}
function normalizeRegistryArtifact(input, project, catalog) {
const cloned = JSON.parse(JSON.stringify(input));
const grammar = cloned?.selection?.grammar;
if (typeof grammar === "string" && /^reference:[a-z0-9][a-z0-9-]{0,63}$/u.test(grammar)) {
const fallback = cloned.selection.fallback ?? project.defaults.fallback;
const normalizedFallback = typeof fallback === "string" ? fallback : null;
if (!normalizedFallback) throw new Error(`Reference grammar ${grammar} requires a built-in fallback`);
cloned.selection.grammar = normalizedFallback;
const normalized = normalizeArtifact(cloned, project, catalog);
normalized.selection.grammar = grammar;
normalized.selection.fallback = normalizedFallback;
return normalized;
}
return normalizeArtifact(cloned, project, catalog);
}
export function loadProjectRegistry(projectRoot, { catalog = defaultCatalog, maxBytes = 1024 * 1024 } = {}) {
const paths = getRegistryPaths(projectRoot);
const hasProject = existsSync(paths.projectFile);
const hasIndex = existsSync(paths.indexFile);
if (!hasProject && !hasIndex) return null;
if (!hasProject || !hasIndex) {
throw new Error("Incomplete .styleseed registry: project.json and artifacts/index.json must both exist");
}
const project = normalizeProject(readStrictJson(paths.projectFile, maxBytes), catalog);
const index = normalizeIndex(readStrictJson(paths.indexFile, maxBytes));
const artifacts = index.artifacts.map((entry) => {
const configRelative = `artifacts/${entry.config}`;
const configPath = safeProjectPath(paths.styleseedDir, configRelative);
if (!existsSync(configPath)) throw new Error(`Missing artifact config: ${configRelative}`);
const artifact = normalizeRegistryArtifact(readStrictJson(configPath, maxBytes), project, catalog);
if (artifact.id !== entry.id) {
throw new Error(`Artifact ID mismatch: index=${entry.id}, config=${artifact.id}`);
}
return {
id: artifact.id,
config: entry.config,
path: configPath,
relativePath: `.styleseed/${configRelative}`,
artifact,
};
});
return {
paths,
project,
index,
artifacts,
artifactMap: new Map(artifacts.map((entry) => [entry.id, entry])),
};
}
export function loadArtifactConfig(projectRoot, artifactId, options) {
const registry = loadProjectRegistry(projectRoot, options);
if (!registry) return null;
return registry.artifactMap.get(artifactId) ?? null;
}
scripts/resolve-context.mjs
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { canonicalJson, normalizeArtifact, normalizeProject, safeProjectPath } from "./runtime-contract.mjs";
import { compileContext, compareEntries, defaultCatalog, materializeManifestOutputs } from "./compiler.mjs";
import { loadProjectRegistry } from "./project-registry.mjs";
const skillDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const catalog = defaultCatalog;
const AUTO_RECIPE_BY_GRAMMAR = {
"consumer-service": "calm-consumer",
"operations-console": "enterprise-workbench",
"technical-instrument": "developer-platform",
"editorial-reading": "editorial-authority",
"commerce-conversion": "commerce-operator",
"institutional-service": "public-service",
"expressive-marketing": "expressive-brand",
"sequential-story": "creative-professional",
};
const AUTO_PALETTE_BY_RECIPE = {
"calm-consumer": "quiet-mineral",
"native-mobile": "quiet-mineral",
"enterprise-workbench": "cobalt-instrument",
"developer-platform": "cobalt-instrument",
"commerce-operator": "warm-clay-commerce",
"public-service": "civic-blue",
"creative-professional": "deep-lime-studio",
"editorial-authority": "editorial-ink",
"expressive-brand": "signal-coral",
};
function parseArgs(argv) {
const out = {};
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index];
if (!value.startsWith("--")) throw new Error(`Unexpected argument: ${value}`);
const key = value.slice(2);
if (["list", "stdout", "check", "help", "all"].includes(key)) {
out[key] = true;
continue;
}
const next = argv[index + 1];
if (!next || next.startsWith("--")) throw new Error(`Missing value for --${key}`);
out[key] = next;
index += 1;
}
return out;
}
function help() {
return `StyleSeed Context Compiler
Usage:
resolve-context.mjs --from-lock STYLESEED.md --agent codex
resolve-context.mjs --grammar <id> --adapter <id> [options]
resolve-context.mjs --project-root . --artifact app-dashboard --agent codex
resolve-context.mjs --project-root . --all --agent codex
Options:
--agent claude|codex|cursor|factory|gemini|kiro|opencode|windsurf
--artifact <id>
--all
--grammar <built-in id|reference:slug>
--adapter <id>
--domain <id|none>
--page <id|none>
--recipe <id|auto>
--palette <id|auto>
--key-color <hex>
--palette-character calm|balanced|vivid|deep
--palette-mode light|dark
--palette-harmony auto|tonal|adjacent|contrast
--surface-temperature neutral|warm|cool
--profile <id|none>
--fallback <built-in grammar id>
--from-lock <path>
--project-root <path>
--stdout
--check
--list
`;
}
function parseLegacyLock(path) {
if (!path || !existsSync(path)) return { path: "legacy:STYLESEED.md", normalized: {}, duplicates: [], unknown: [], text: "" };
const text = readFileSync(path, "utf8");
const normalized = {};
const seen = new Map();
const unknown = [];
const duplicates = [];
const mapping = {
"App domain": "domain",
"Surface adapter": "adapter",
"Page type": "page",
"Output grammar": "grammar",
"Grammar fallback": "fallback",
"Brand recipe": "recipeSelection",
"Palette recipe": "paletteSelection",
"Key color": "keyColor",
"Primary action": "keyColor",
"Palette character": "paletteCharacter",
"Palette mode": "paletteMode",
"Palette harmony": "paletteHarmony",
"Surface temperature": "surfaceTemperature",
"Aesthetic profile": "profile",
};
for (const line of text.split(/\r?\n/u)) {
const match = line.match(/^\s*-\s+([^:]+):\s*(.+?)\s*$/u);
if (!match) continue;
const label = match[1].trim();
const value = match[2].replace(/\s+#.*$/u, "").trim();
const key = mapping[label];
if (!key) {
unknown.push(label);
continue;
}
if (seen.has(label)) {
duplicates.push(label);
continue;
}
seen.set(label, value);
normalized[key] = value;
}
if (duplicates.length) throw new Error(`Duplicate legacy lock fields are not allowed: ${[...new Set(duplicates)].join(", ")}`);
const motionLine = text.split(/\r?\n/u).find((line) => /^\s*-\s+Motion:/u.test(line));
if (motionLine) {
const motionValue = motionLine.replace(/^\s*-\s+Motion:\s*/u, "").trim();
if (!/^(spring|silk|snap|float|pulse)\s+(restrained|standard|lively)$/iu.test(motionValue)) {
throw new Error(`Unsupported legacy Motion value: ${motionValue}`);
}
}
return { path: "legacy:STYLESEED.md", normalized, duplicates, unknown, text };
}
function legacyArgsToNormalized(args, lock) {
const pick = (name, fallback = null) => args[name] ?? lock.normalized[name] ?? fallback;
const grammar = pick("grammar");
const adapter = pick("adapter");
if (!grammar) throw new Error("Missing output grammar. Pass --grammar or use --from-lock.");
if (!adapter) throw new Error("Missing surface adapter. Pass --adapter or use --from-lock.");
const recipeGrammar = typeof grammar === "string" && grammar.startsWith("reference:")
? pick("fallback", null)
: grammar;
const recipeSelection = pick("recipe", "auto");
const resolvedRecipe = recipeSelection === "auto" ? AUTO_RECIPE_BY_GRAMMAR[recipeGrammar] : recipeSelection;
if (!resolvedRecipe) throw new Error(`No automatic brand recipe for grammar "${grammar}".`);
const paletteSelection = pick("palette", "auto");
const resolvedPalette = paletteSelection === "auto" ? AUTO_PALETTE_BY_RECIPE[resolvedRecipe] : paletteSelection;
if (!resolvedPalette) throw new Error(`No automatic palette recipe for brand recipe "${resolvedRecipe}".`);
const project = normalizeProject({
schemaVersion: 1,
projectId: "legacy-project",
defaults: {
agent: args.agent ?? "codex",
domain: pick("domain", "none"),
adapter,
recipe: resolvedRecipe,
palette: resolvedPalette,
profile: pick("profile", "none"),
fallback: pick("fallback", null),
},
brand: {
keyColor: args["key-color"] ?? lock.normalized.keyColor ?? "#6C5CE7",
paletteCharacter: args["palette-character"] ?? lock.normalized.paletteCharacter ?? "vivid",
paletteMode: args["palette-mode"] ?? lock.normalized.paletteMode ?? "light",
paletteHarmony: args["palette-harmony"] ?? lock.normalized.paletteHarmony ?? "auto",
surfaceTemperature: args["surface-temperature"] ?? lock.normalized.surfaceTemperature ?? "neutral",
fontFamilies: ["Inter"],
radius: "soft",
elevation: "restrained-shadow",
density: "comfortable",
motion: { seed: "spring", intensity: "restrained" },
imageryRole: "product-proof-first",
},
}, catalog);
const normalizedGrammar = grammar.startsWith("reference:") ? pick("fallback") : grammar;
if (!normalizedGrammar) throw new Error("A project reference grammar requires a built-in fallback grammar.");
const artifact = normalizeArtifact({
schemaVersion: 1,
id: "default",
target: { kind: "route", locator: "/" },
selection: {
grammar: normalizedGrammar,
adapter,
domain: pick("domain", "none"),
page: pick("page", "none"),
recipe: recipeSelection === "auto" ? null : recipeSelection,
palette: paletteSelection === "auto" ? null : paletteSelection,
profile: pick("profile", "none"),
fallback: pick("fallback", null),
},
decisions: {
primaryDecision: "Legacy single-artifact resolver.",
primaryAction: "Apply the compiled design method.",
signatureMove: "Preserve the legacy output paths while using normalized inputs only.",
},
implementation: { sourceRoots: ["src"], tokenFiles: [] },
validation: {
scoreFloor: 80,
requiredRenders: [{ id: "desktop-loaded", state: "loaded", viewport: { width: 1440, height: 1000 } }],
temporal: { required: false, scenarios: [] },
humanAcceptance: false,
},
}, project, catalog);
artifact.selection.grammar = grammar;
return { project, artifact };
}
function ensureNoRegistryOverrides(args) {
const forbidden = ["grammar", "adapter", "domain", "page", "recipe", "palette", "key-color", "palette-character", "palette-mode", "palette-harmony", "surface-temperature", "profile", "fallback", "from-lock"];
const used = forbidden.filter((key) => Object.hasOwn(args, key));
if (used.length) throw new Error(`Registry mode forbids selection overrides: ${used.map((key) => `--${key}`).join(", ")}`);
}
function writeOutputs(projectRoot, compiled) {
const writes = [
{ path: compiled.manifestPath, content: `${JSON.stringify(compiled.manifest, null, 2)}\n` },
{ path: compiled.manifest.bundle.path, content: compiled.bundle },
];
if (compiled.paletteJson) writes.push({ path: compiled.manifest.outputs.find((entry) => entry.kind === "palette-json").path, content: compiled.paletteJson });
if (compiled.paletteCss) writes.push({ path: compiled.manifest.outputs.find((entry) => entry.kind === "palette-css").path, content: compiled.paletteCss });
for (const write of writes) {
const target = resolve(projectRoot, write.path);
mkdirSync(dirname(target), { recursive: true });
writeFileSync(target, write.content);
}
if (!compiled.paletteJson) rmSync(resolve(projectRoot, compiled.manifest.bundle.path.replace(/bundles\/.*$/u, "palette.json")), { force: true });
}
function checkCompiled(projectRoot, compiled) {
const manifestPath = resolve(projectRoot, compiled.manifestPath);
if (!existsSync(manifestPath)) {
console.error(`StyleSeed context drift: manifest is missing for ${compiled.manifestPath}.`);
return 2;
}
const previous = JSON.parse(readFileSync(manifestPath, "utf8"));
if (previous.schemaVersion === 2) {
for (const field of ["artifactId", "engineVersion", "engineRevision", "distributionIntegrity", "methodHash", "validationHash"]) {
if (previous[field] !== compiled.manifest[field]) {
console.error(`StyleSeed context drift: manifest ${field} mismatch (expected ${compiled.manifest[field]}, found ${previous[field] ?? "missing"}).`);
return 2;
}
}
if (canonicalJson(previous.selection) !== canonicalJson(compiled.manifest.selection)) {
console.error("StyleSeed context drift: manifest selection mismatch.");
return 2;
}
if (canonicalJson(previous.validationSlices) !== canonicalJson(compiled.manifest.validationSlices)) {
console.error("StyleSeed context drift: manifest validation slice mismatch.");
return 2;
}
const inputsMismatch = compareEntries(compiled.manifest.inputs, previous.inputs, "manifest inputs", ["id", "path", "sha256", "bytes"]);
if (inputsMismatch) {
console.error(`StyleSeed context drift: ${inputsMismatch}`);
return 2;
}
}
const expectedOutputs = previous.schemaVersion === 1
? compiled.outputs.map((entry) => ({ ...entry, sha256: entry.sha256.replace(/^sha256:/u, "") }))
: compiled.outputs;
const outputsMismatch = compareEntries(expectedOutputs, materializeManifestOutputs(previous), "manifest outputs", ["kind", "path", "sha256", "bytes"]);
if (outputsMismatch) {
console.error(`StyleSeed context drift: ${outputsMismatch}`);
return 2;
}
const sourceFields = previous.schemaVersion === 1 ? ["id", "sha256", "bytes"] : ["id", "path", "sha256", "bytes"];
const expectedSources = previous.schemaVersion === 1
? compiled.sources.map(({ id, sha256, bytes }) => ({ id, sha256: sha256.replace(/^sha256:/u, ""), bytes }))
: compiled.sources;
const sourcesMismatch = compareEntries(expectedSources, previous.sources, "manifest sources", sourceFields);
if (sourcesMismatch) {
console.error(`StyleSeed context drift: ${sourcesMismatch}`);
return 2;
}
for (const entry of materializeManifestOutputs(previous)) {
const actualPath = resolve(projectRoot, entry.path);
if (!existsSync(actualPath)) {
console.error(`StyleSeed context drift: missing output file ${entry.path}.`);
return 2;
}
const actualContent = readFileSync(actualPath);
const actualHash = `sha256:${createHash("sha256").update(actualContent).digest("hex")}`;
const expectedHash = typeof entry.sha256 === "string" && entry.sha256.startsWith("sha256:")
? entry.sha256
: `sha256:${entry.sha256}`;
if (actualHash !== expectedHash || actualContent.byteLength !== entry.bytes) {
console.error(`StyleSeed context drift: output mismatch for ${entry.path} (expected ${entry.bytes} bytes ${expectedHash}, found ${actualContent.byteLength} bytes ${actualHash}).`);
return 2;
}
}
return 0;
}
function resolveTargets(registry, args) {
if (!registry) return [];
const artifacts = [...registry.artifacts].sort((left, right) => left.id.localeCompare(right.id));
if (artifacts.length > 1 && !args.all && !args.artifact) {
throw new Error("Multiple registry artifacts exist; pass --artifact <id> or --all.");
}
if (args.all) return artifacts;
if (args.artifact) {
const artifact = registry.artifactMap.get(args.artifact);
if (!artifact) throw new Error(`Unknown artifact "${args.artifact}"`);
return [artifact];
}
return artifacts;
}
const args = parseArgs(process.argv.slice(2));
if (args.help) {
console.log(help());
process.exit(0);
}
if (args.list) {
console.log(JSON.stringify({
agents: Object.keys(catalog.agents),
grammars: Object.keys(catalog.grammars),
adapters: Object.keys(catalog.adapters),
domains: Object.keys(catalog.domains),
pages: Object.keys(catalog.pages),
recipes: ["auto", ...Object.keys(catalog.recipes)],
palettes: ["auto", ...Object.keys(catalog.palettes)],
profiles: ["none", ...Object.keys(catalog.profiles)],
}, null, 2));
process.exit(0);
}
const projectRoot = resolve(args["project-root"] ?? process.cwd());
const agent = args.agent ?? "codex";
if (!catalog.agents[agent]) throw new Error(`Unknown agent "${agent}"`);
const registry = loadProjectRegistry(projectRoot, { catalog });
if (!registry) {
const lockPath = safeProjectPath(projectRoot, args["from-lock"] ?? "STYLESEED.md");
const lock = parseLegacyLock(lockPath);
const normalized = legacyArgsToNormalized(args, lock);
const compiled = compileContext({
catalog,
projectRoot,
agent,
normalizedProject: normalized.project,
normalizedArtifact: normalized.artifact,
legacyLock: lock,
mode: "legacy",
});
if (args.check) process.exit(checkCompiled(projectRoot, compiled));
if (args.stdout) {
process.stdout.write(compiled.bundle);
process.exit(0);
}
writeOutputs(projectRoot, compiled);
console.log(`StyleSeed ${catalog.engineVersion}: ${compiled.manifest.selection.grammar} × ${compiled.manifest.selection.adapter} × ${compiled.manifest.selection.domain} × ${compiled.manifest.selection.page} × ${compiled.manifest.selection.recipe} × ${compiled.manifest.selection.palette} × ${compiled.manifest.selection.profile}`);
console.log(`wrote ${resolve(projectRoot, compiled.manifest.bundle.path)} (${Buffer.byteLength(compiled.bundle)} bytes, ${compiled.manifest.bundle.sha256})`);
console.log(`wrote ${resolve(projectRoot, compiled.manifestPath)}`);
if (compiled.paletteJson) console.log(`wrote ${resolve(projectRoot, ".styleseed/palette.json")} and ${resolve(projectRoot, ".styleseed/palette.css")}`);
process.exit(0);
}
ensureNoRegistryOverrides(args);
const targets = resolveTargets(registry, args);
const compiledTargets = targets.map((entry) => compileContext({
catalog,
projectRoot,
agent,
normalizedProject: registry.project,
normalizedArtifact: entry.artifact,
inputFiles: {
project: { path: ".styleseed/project.json", content: readFileSync(registry.paths.projectFile, "utf8") },
artifact: { path: entry.relativePath, content: readFileSync(entry.path, "utf8") },
},
mode: "registry",
})).sort((left, right) => left.manifest.artifactId.localeCompare(right.manifest.artifactId));
if (args.check) {
let status = 0;
for (const compiled of compiledTargets) status = Math.max(status, checkCompiled(projectRoot, compiled));
process.exit(status);
}
if (args.stdout) {
process.stdout.write(compiledTargets.map((compiled) => compiled.bundle).join("\n\n"));
process.exit(0);
}
for (const compiled of compiledTargets) writeOutputs(projectRoot, compiled);
for (const compiled of compiledTargets) {
console.log(`StyleSeed ${catalog.engineVersion}: wrote ${compiled.manifest.artifactId} → ${compiled.manifest.bundle.path}`);
}
scripts/runtime-contract.mjs
import { createHash } from "node:crypto";
import { existsSync, lstatSync, realpathSync } from "node:fs";
import { isAbsolute, relative, resolve, sep } from "node:path";
export const SAFE_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
const SAFE_FILE = /^[a-z0-9][a-z0-9-]{0,63}\.json$/;
const SHA256 = /^sha256:[0-9a-f]{64}$/;
const CONTROL = /[\u0000-\u001f\u007f]/u;
const enums = {
agents: ["claude", "codex", "cursor", "factory", "gemini", "kiro", "opencode", "windsurf"],
paletteCharacter: ["calm", "balanced", "vivid", "deep"],
paletteMode: ["light", "dark"],
paletteHarmony: ["auto", "tonal", "adjacent", "contrast"],
surfaceTemperature: ["neutral", "warm", "cool"],
radius: ["sharp", "restrained", "balanced", "soft", "pill"],
elevation: ["flat", "tonal", "restrained-shadow", "layered"],
density: ["compact", "comfortable", "spacious"],
motionSeed: ["spring", "silk", "snap", "float", "pulse"],
motionIntensity: ["restrained", "standard", "lively"],
imageryRole: ["data-first", "product-proof-first", "editorial-media", "people-context", "generated-atmosphere", "none"],
targetKind: ["route", "component", "document", "deck", "carousel", "single-frame"],
renderState: ["loaded", "loading", "empty", "error", "success", "disabled", "focused", "reduced-motion"],
};
function fail(message) { throw new Error(message); }
function exactObject(value, keys, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${label} must be an object`);
const extra = Object.keys(value).filter((key) => !keys.includes(key));
if (extra.length) fail(`${label} contains unknown keys: ${extra.join(", ")}`);
return value;
}
function required(value, label) { if (value === undefined) fail(`${label} is required`); return value; }
function oneOf(value, values, label) { if (!values.includes(value)) fail(`${label} is invalid`); return value; }
function id(value, label) { if (typeof value !== "string" || !SAFE_ID.test(value)) fail(`${label} must be a safe ID`); return value; }
function text(value, label, max = 600) {
if (typeof value !== "string" || value.length < 1 || value.length > max || CONTROL.test(value)) fail(`${label} must be bounded single-line text`);
return value;
}
function grammarId(catalog, value, label) {
if (typeof value !== "string") fail(`${label} is not in the current catalog`);
if (value.startsWith("reference:")) {
const slug = value.slice("reference:".length);
if (!SAFE_ID.test(slug)) fail(`${label} must use a safe reference grammar slug`);
return value;
}
return catalogId(catalog, "grammars", value, label);
}
function catalogId(catalog, group, value, label, { nullable = false, none = false } = {}) {
if (nullable && value === null) return null;
if (none && value === "none") return value;
if (typeof value !== "string" || !catalog?.[group] || !Object.hasOwn(catalog[group], value)) fail(`${label} is not in the current catalog`);
return value;
}
function projectPath(value, label) {
if (typeof value !== "string" || !value || value.length > 240 || CONTROL.test(value) || isAbsolute(value)) fail(`${label} must be a project-relative path`);
const parts = value.replaceAll("\\", "/").split("/");
if (parts.some((part) => !part || part === "." || part === "..")) fail(`${label} contains an unsafe segment`);
return parts.join("/");
}
export function canonicalJson(value) {
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
return JSON.stringify(value);
}
export function sha256(value) {
const bytes = Buffer.isBuffer(value) ? value : Buffer.from(typeof value === "string" ? value : canonicalJson(value));
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
}
export function parseStrictJson(textValue, { maxBytes = 1024 * 1024 } = {}) {
if (typeof textValue !== "string") fail("JSON input must be text");
if (Buffer.byteLength(textValue) > maxBytes) fail(`JSON input exceeds ${maxBytes} bytes`);
let cursor = 0;
let depth = 0;
const MAX_DEPTH = 128;
const ws = () => { while (/[ \t\r\n]/u.test(textValue[cursor] ?? "")) cursor += 1; };
const string = () => {
if (textValue[cursor] !== '"') fail(`Expected string at byte ${cursor}`);
const start = cursor++;
while (cursor < textValue.length) {
if (textValue[cursor] === "\\") { cursor += 2; continue; }
if (textValue[cursor++] === '"') return JSON.parse(textValue.slice(start, cursor));
}
fail("Unterminated JSON string");
};
const value = () => {
ws();
if (textValue[cursor] === '"') return string();
if (textValue[cursor] === "{") {
depth += 1; if (depth > MAX_DEPTH) fail(`JSON nesting exceeds ${MAX_DEPTH}`);
cursor += 1; ws(); const out = {}; const seen = new Set();
if (textValue[cursor] === "}") { cursor += 1; depth -= 1; return out; }
while (true) {
ws(); const key = string();
if (seen.has(key)) fail(`Duplicate JSON key: ${key}`);
seen.add(key); ws(); if (textValue[cursor++] !== ":") fail(`Expected colon at byte ${cursor - 1}`);
out[key] = value(); ws();
if (textValue[cursor] === "}") { cursor += 1; depth -= 1; return Object.fromEntries(Object.entries(out).sort(([left], [right]) => left.localeCompare(right))); }
if (textValue[cursor++] !== ",") fail(`Expected comma at byte ${cursor - 1}`);
}
}
if (textValue[cursor] === "[") {
depth += 1; if (depth > MAX_DEPTH) fail(`JSON nesting exceeds ${MAX_DEPTH}`);
cursor += 1; ws(); const out = [];
if (textValue[cursor] === "]") { cursor += 1; depth -= 1; return out; }
while (true) {
out.push(value()); ws();
if (textValue[cursor] === "]") { cursor += 1; depth -= 1; return out; }
if (textValue[cursor++] !== ",") fail(`Expected comma at byte ${cursor - 1}`);
}
}
const match = textValue.slice(cursor).match(/^(?:-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/u);
if (!match) fail(`Invalid JSON value at byte ${cursor}`);
cursor += match[0].length;
return JSON.parse(match[0]);
};
const parsed = value(); ws();
if (cursor !== textValue.length) fail(`Trailing JSON content at byte ${cursor}`);
return parsed;
}
export function safeProjectPath(projectRoot, relativePath) {
const normalized = projectPath(relativePath, "path");
const root = realpathSync(projectRoot);
const target = resolve(root, normalized);
const lexical = relative(root, target);
if (!lexical || lexical.startsWith(`..${sep}`) || lexical === ".." || isAbsolute(lexical)) fail("path escapes project root");
let current = root;
for (const part of normalized.split("/")) {
current = resolve(current, part);
if (existsSync(current) && lstatSync(current).isSymbolicLink()) fail(`path traverses a symlink: ${relativePath}`);
}
if (existsSync(target)) {
const stats = lstatSync(target);
if (!stats.isFile() && !stats.isDirectory()) fail("path target must be a regular file or directory");
const real = realpathSync(target);
const contained = relative(root, real);
if (contained.startsWith(`..${sep}`) || contained === ".." || isAbsolute(contained)) fail("path realpath escapes project root");
}
return target;
}
export function normalizeProject(input, catalog) {
exactObject(input, ["schemaVersion", "projectId", "defaults", "brand"], "project");
if (required(input.schemaVersion, "project.schemaVersion") !== 1) fail("project.schemaVersion must be 1");
const defaults = exactObject(required(input.defaults, "project.defaults"), ["agent", "domain", "adapter", "recipe", "palette", "profile", "fallback"], "project.defaults");
const brand = exactObject(required(input.brand, "project.brand"), ["keyColor", "paletteCharacter", "paletteMode", "paletteHarmony", "surfaceTemperature", "fontFamilies", "radius", "elevation", "density", "motion", "imageryRole"], "project.brand");
const motion = exactObject(required(brand.motion, "project.brand.motion"), ["seed", "intensity"], "project.brand.motion");
if (!Array.isArray(brand.fontFamilies) || brand.fontFamilies.length < 1 || brand.fontFamilies.length > 8) fail("fontFamilies must contain 1-8 values");
const fontFamilies = brand.fontFamilies.map((font) => {
if (typeof font !== "string" || font.length > 80 || !/^[\p{L}\p{N} .,'-]+$/u.test(font)) fail("fontFamilies contains an invalid font name");
return font;
});
if (typeof brand.keyColor !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(brand.keyColor)) fail("keyColor must be six-digit hex");
return {
schemaVersion: 1,
projectId: id(required(input.projectId, "project.projectId"), "project.projectId"),
defaults: {
agent: oneOf(required(defaults.agent, "defaults.agent"), enums.agents, "defaults.agent"),
domain: catalogId(catalog, "domains", required(defaults.domain, "defaults.domain"), "defaults.domain", { none: true }),
adapter: catalogId(catalog, "adapters", required(defaults.adapter, "defaults.adapter"), "defaults.adapter"),
recipe: catalogId(catalog, "recipes", required(defaults.recipe, "defaults.recipe"), "defaults.recipe"),
palette: catalogId(catalog, "palettes", required(defaults.palette, "defaults.palette"), "defaults.palette"),
profile: catalogId(catalog, "profiles", required(defaults.profile, "defaults.profile"), "defaults.profile", { none: true }),
fallback: catalogId(catalog, "grammars", required(defaults.fallback, "defaults.fallback"), "defaults.fallback", { nullable: true }),
},
brand: {
keyColor: brand.keyColor.toUpperCase(),
paletteCharacter: oneOf(required(brand.paletteCharacter, "brand.paletteCharacter"), enums.paletteCharacter, "brand.paletteCharacter"),
paletteMode: oneOf(required(brand.paletteMode, "brand.paletteMode"), enums.paletteMode, "brand.paletteMode"),
paletteHarmony: oneOf(required(brand.paletteHarmony, "brand.paletteHarmony"), enums.paletteHarmony, "brand.paletteHarmony"),
surfaceTemperature: oneOf(required(brand.surfaceTemperature, "brand.surfaceTemperature"), enums.surfaceTemperature, "brand.surfaceTemperature"),
fontFamilies,
radius: oneOf(required(brand.radius, "brand.radius"), enums.radius, "brand.radius"),
elevation: oneOf(required(brand.elevation, "brand.elevation"), enums.elevation, "brand.elevation"),
density: oneOf(required(brand.density, "brand.density"), enums.density, "brand.density"),
motion: { seed: oneOf(required(motion.seed, "motion.seed"), enums.motionSeed, "motion.seed"), intensity: oneOf(required(motion.intensity, "motion.intensity"), enums.motionIntensity, "motion.intensity") },
imageryRole: oneOf(required(brand.imageryRole, "brand.imageryRole"), enums.imageryRole, "brand.imageryRole"),
},
};
}
export function normalizeIndex(input) {
exactObject(input, ["schemaVersion", "artifacts"], "artifact index");
if (input.schemaVersion !== 1 || !Array.isArray(input.artifacts) || input.artifacts.length < 1) fail("artifact index is invalid");
const seen = new Set();
const artifacts = input.artifacts.map((entry, index) => {
exactObject(entry, ["id", "config"], `artifacts[${index}]`);
const artifactId = id(required(entry.id, `artifacts[${index}].id`), `artifacts[${index}].id`);
if (seen.has(artifactId)) fail(`duplicate artifact ID: ${artifactId}`);
seen.add(artifactId);
if (typeof entry.config !== "string" || !SAFE_FILE.test(entry.config) || entry.config !== `${artifactId}.json`) fail(`artifact config must be ${artifactId}.json`);
return { id: artifactId, config: entry.config };
}).sort((left, right) => left.id.localeCompare(right.id));
return { schemaVersion: 1, artifacts };
}
export function normalizeArtifact(input, project, catalog) {
exactObject(input, ["schemaVersion", "id", "target", "selection", "decisions", "implementation", "validation"], "artifact");
if (input.schemaVersion !== 1) fail("artifact.schemaVersion must be 1");
const target = exactObject(required(input.target, "artifact.target"), ["kind", "locator"], "artifact.target");
const selection = exactObject(required(input.selection, "artifact.selection"), ["grammar", "adapter", "domain", "page", "recipe", "palette", "profile", "fallback"], "artifact.selection");
const decisions = exactObject(required(input.decisions, "artifact.decisions"), ["primaryDecision", "primaryAction", "signatureMove"], "artifact.decisions");
const implementation = exactObject(required(input.implementation, "artifact.implementation"), ["sourceRoots", "tokenFiles"], "artifact.implementation");
const validation = exactObject(required(input.validation, "artifact.validation"), ["scoreFloor", "requiredRenders", "temporal", "humanAcceptance"], "artifact.validation");
const temporal = exactObject(required(validation.temporal, "validation.temporal"), ["required", "scenarios"], "validation.temporal");
const kind = oneOf(required(target.kind, "target.kind"), enums.targetKind, "target.kind");
let locator;
if (kind === "route") {
locator = target.locator;
if (typeof locator !== "string" || !locator.startsWith("/") || locator.length > 240 || /[?#]/u.test(locator) || CONTROL.test(locator)) fail("route locator is invalid");
} else locator = projectPath(target.locator, "target.locator");
if (!Array.isArray(implementation.sourceRoots) || implementation.sourceRoots.length < 1 || !Array.isArray(implementation.tokenFiles)) fail("artifact implementation paths are invalid");
if (!Array.isArray(validation.requiredRenders) || validation.requiredRenders.length < 1) fail("requiredRenders must not be empty");
const renderIds = new Set();
const requiredRenders = validation.requiredRenders.map((render, index) => {
exactObject(render, ["id", "state", "viewport"], `requiredRenders[${index}]`);
exactObject(render.viewport, ["width", "height"], `requiredRenders[${index}].viewport`);
const renderId = id(render.id, `requiredRenders[${index}].id`);
if (renderIds.has(renderId)) fail(`duplicate render ID: ${renderId}`);
renderIds.add(renderId);
for (const dimension of ["width", "height"]) if (!Number.isInteger(render.viewport[dimension]) || render.viewport[dimension] < 1 || render.viewport[dimension] > 10000) fail(`viewport ${dimension} is invalid`);
return { id: renderId, state: oneOf(render.state, enums.renderState, "render state"), viewport: { width: render.viewport.width, height: render.viewport.height } };
}).sort((left, right) => left.id.localeCompare(right.id));
if (typeof temporal.required !== "boolean" || !Array.isArray(temporal.scenarios)) fail("temporal contract is invalid");
const scenarios = temporal.scenarios.map((scenario) => id(scenario, "temporal scenario"));
if (new Set(scenarios).size !== scenarios.length) fail("temporal scenarios contain duplicate IDs");
if (!temporal.required && scenarios.length) fail("temporal scenarios require temporal.required=true");
if (!Number.isInteger(validation.scoreFloor) || validation.scoreFloor < 80 || validation.scoreFloor > 100) fail("scoreFloor must be 80-100");
if (typeof validation.humanAcceptance !== "boolean") fail("humanAcceptance must be boolean");
return {
schemaVersion: 1,
id: id(input.id, "artifact.id"),
target: { kind, locator },
selection: {
grammar: grammarId(catalog, selection.grammar, "selection.grammar"),
adapter: catalogId(catalog, "adapters", selection.adapter ?? project.defaults.adapter, "selection.adapter"),
domain: catalogId(catalog, "domains", selection.domain ?? project.defaults.domain, "selection.domain", { none: true }),
page: catalogId(catalog, "pages", selection.page, "selection.page", { none: true }),
recipe: catalogId(catalog, "recipes", selection.recipe ?? project.defaults.recipe, "selection.recipe"),
palette: catalogId(catalog, "palettes", selection.palette ?? project.defaults.palette, "selection.palette"),
profile: catalogId(catalog, "profiles", selection.profile ?? project.defaults.profile, "selection.profile", { none: true }),
fallback: catalogId(catalog, "grammars", selection.fallback ?? project.defaults.fallback, "selection.fallback", { nullable: true }),
},
decisions: { primaryDecision: text(decisions.primaryDecision, "primaryDecision"), primaryAction: text(decisions.primaryAction, "primaryAction"), signatureMove: text(decisions.signatureMove, "signatureMove") },
implementation: { sourceRoots: implementation.sourceRoots.map((path) => projectPath(path, "sourceRoot")).sort(), tokenFiles: implementation.tokenFiles.map((path) => projectPath(path, "tokenFile")).sort() },
validation: { scoreFloor: validation.scoreFloor, requiredRenders, temporal: { required: temporal.required, scenarios: [...new Set(scenarios)].sort() }, humanAcceptance: validation.humanAcceptance },
};
}
export const CONTRACT_ENUMS = Object.freeze(enums);
export const isSha256 = (value) => typeof value === "string" && SHA256.test(value);
scripts/styleseed-doctor.mjs
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptPath = fileURLToPath(import.meta.url);
const scriptDir = dirname(scriptPath);
const safeId = /^[a-z0-9][a-z0-9-]{0,63}$/u;
const limitations = [
"Local inventory consistency is not publisher authenticity, latest-release availability, or agent-host discovery.",
"Evidence is revalidated, not newly rendered or independently judged. No files, settings, or approvals are changed.",
];
function present(path) {
try { lstatSync(path); return true; } catch (error) {
if (error.code === "ENOENT") return false;
throw error;
}
}
// Reject even dangling links before the registry loader can consider legacy fallback.
function projectPath(root, relative) {
let current = root;
for (const part of relative.split("/")) {
current = resolve(current, part);
if (present(current) && lstatSync(current).isSymbolicLink()) throw new Error(`Refusing project symlink: ${relative}`);
}
return current;
}
function compileCheck(root, id, agent, registry) {
const args = [resolve(scriptDir, "resolve-context.mjs"), "--project-root", root, "--agent", agent, "--check"];
args.push(...(registry ? ["--artifact", id] : ["--from-lock", "STYLESEED.md"]));
const result = spawnSync(process.execPath, args, { encoding: "utf8", timeout: 30000, maxBuffer: 1024 * 1024 });
return {
status: result.status === 0 ? "current" : result.status === 2 ? "stale" : "invalid",
detail: (result.error?.message || result.stderr?.trim() || result.stdout?.trim()
|| (result.status === 0 ? "Compiled rules and manifest match the current project contract." : "Resolver did not complete")).trim(),
...(result.status === 0 ? {} : { next: "Run ss-resolve for this artifact after reviewing the reported drift; doctor does not recompile." }),
};
}
function evidenceCheck(root, entry, verifyEvidenceRun) {
const dir = projectPath(root, `.styleseed/evidence/${entry.id}`);
if (!present(dir)) return { status: "missing", next: "Collect code and rendered evidence with ss-score / ss-verify for this artifact." };
const runs = [];
for (const name of readdirSync(dir).sort()) {
const path = projectPath(root, `.styleseed/evidence/${entry.id}/${name}`);
if (!safeId.test(name) || !lstatSync(path).isDirectory()) {
runs.push({ id: name, status: "invalid", errors: ["Expected a safe run ID and directory"] });
continue;
}
const result = verifyEvidenceRun({ projectRoot: root, artifactId: entry.id, runId: name, writeSummary: false });
runs.push({ id: name, status: result.ok ? "current" : "invalid", gates: result.gates, errors: result.errors, warnings: result.warnings });
}
// Old invalid runs are history, not a veto on a run bound to current inputs.
const currentRunIds = runs.filter((run) => run.status === "current").map((run) => run.id);
return {
status: currentRunIds.length ? "current" : runs.length ? "invalid" : "missing",
currentRunIds,
runs,
...(currentRunIds.length ? {} : { next: "Collect fresh evidence for the current artifact; cached verification.json is not proof." }),
};
}
export async function diagnoseProject({ projectRoot = process.cwd(), artifact, agent } = {}) {
if (artifact !== undefined && !safeId.test(artifact)) throw new Error("--artifact must be a safe artifact ID");
const root = realpathSync(resolve(projectRoot));
if (!lstatSync(root).isDirectory()) throw new Error("--project-root must be a directory");
const report = { schemaVersion: 1, projectRoot: root, status: "attention", installation: { status: "not-checked" }, configuration: { status: "not-checked" }, artifacts: [], limitations };
let catalog;
try {
catalog = JSON.parse(readFileSync(resolve(scriptDir, "../references/catalog.json"), "utf8"));
const { verifyDistribution } = await import("./distribution-integrity.mjs");
report.installation = verifyDistribution({ catalog, scriptPath });
} catch (error) {
report.installation = { status: "invalid", detail: error.message };
}
if (report.installation.status !== "verified") {
report.installation.next = "Restore a complete StyleSeed distribution from your trusted install source before diagnosing project state.";
return report;
}
const { CONTRACT_ENUMS } = await import("./runtime-contract.mjs");
if (agent !== undefined && !CONTRACT_ENUMS.agents.includes(agent)) throw new Error(`--agent must be one of: ${CONTRACT_ENUMS.agents.join(", ")}`);
try {
const { loadProjectRegistry } = await import("./project-registry.mjs");
const { parseStrictJson } = await import("./runtime-contract.mjs");
const projectFile = projectPath(root, ".styleseed/project.json");
const indexFile = projectPath(root, ".styleseed/artifacts/index.json");
report.configuration = { mode: present(projectFile) || present(indexFile) ? "registry" : "legacy", status: "not-checked" };
const registry = loadProjectRegistry(root, { catalog });
if (!registry) {
const lock = projectPath(root, "STYLESEED.md");
if (!present(lock)) {
report.configuration = { mode: "missing", status: "missing", next: "Run ss-setup to choose and approve a project contract." };
return report;
}
if (artifact !== undefined) throw new Error("--artifact requires a registry project; legacy mode has one lock");
const manifest = projectPath(root, ".styleseed/manifest.json");
const storedAgent = present(manifest) ? parseStrictJson(readFileSync(manifest, "utf8")).selection?.agent : null;
const compilation = compileCheck(root, "legacy", agent ?? storedAgent ?? "codex", false);
report.configuration = { mode: "legacy", status: compilation.status === "invalid" ? "invalid" : "valid" };
report.artifacts.push({ id: "legacy", compilation, evidence: { status: "unsupported", next: "Legacy bundles do not provide registry evidence-run verification. Use explicit migration before adopting registry evidence." } });
return report;
}
report.configuration = { mode: "registry", status: "valid", projectId: registry.project.projectId };
const entries = artifact ? registry.artifacts.filter((entry) => entry.id === artifact) : registry.artifacts;
if (!entries.length) throw new Error(artifact ? `Unknown artifact: ${artifact}` : "Registry has no artifacts");
const { verifyEvidenceRun } = await import("../../ss-score/scripts/evidence-gate.mjs");
for (const entry of entries) {
const compilation = compileCheck(root, entry.id, agent ?? registry.project.defaults.agent, true);
let evidence = { status: "not-checked", next: "Resolve compilation drift before evaluating evidence against the current contract." };
if (compilation.status === "current") {
try { evidence = evidenceCheck(root, entry, verifyEvidenceRun); }
catch (error) { evidence = { status: "invalid", detail: error.message }; }
}
report.artifacts.push({ id: entry.id, compilation, evidence });
}
if (report.artifacts.every((entry) => entry.compilation.status === "current" && entry.evidence.status === "current")) report.status = "evidence-current";
} catch (error) {
report.configuration = { ...report.configuration, status: "invalid", detail: error.message, next: "Correct the reported project boundary or configuration, then rerun doctor; do not overwrite approved choices or bypass a registry error." };
}
return report;
}
function parseArgs(argv) {
const options = {};
for (let index = 0; index < argv.length; index += 1) {
const flag = argv[index];
if (["--json", "--help"].includes(flag)) { options[flag.slice(2)] = true; continue; }
if (!["--project-root", "--artifact", "--agent"].includes(flag)) throw new Error(`Unknown option: ${flag}`);
if (!argv[index + 1] || argv[index + 1].startsWith("--")) throw new Error(`Missing value for ${flag}`);
options[flag.slice(2)] = argv[++index];
}
return options;
}
async function main(argv) {
try {
const options = parseArgs(argv);
if (options.help) {
console.log("Usage: node <installed-ss-resolve>/scripts/styleseed-doctor.mjs [--project-root PATH] [--artifact ID] [--agent NAME] [--json]\nRead-only local diagnosis. Exit 0: current evidence for every selected artifact; 1: attention needed; 2: invalid invocation.");
return;
}
const report = await diagnoseProject({ projectRoot: options["project-root"], artifact: options.artifact, agent: options.agent });
if (options.json) console.log(JSON.stringify(report, null, 2));
else {
console.log(`StyleSeed doctor: ${report.status}\nInstallation: ${report.installation.status}\nConfiguration: ${report.configuration.mode ?? "unknown"} / ${report.configuration.status}`);
for (const check of [report.installation, report.configuration]) {
if (check.detail) console.log(check.detail);
if (check.mismatches?.length) console.log(JSON.stringify(check.mismatches));
if (check.next) console.log(`Next: ${check.next}`);
}
for (const entry of report.artifacts) {
console.log(`${entry.id}: compilation=${entry.compilation.status}; evidence=${entry.evidence.status}`);
for (const check of [entry.compilation, entry.evidence]) {
if (check.detail) console.log(check.detail);
if (check.next) console.log(`Next: ${check.next}`);
for (const run of check.runs ?? []) console.log(` ${run.id}: ${run.status}${run.errors.length ? ` — ${run.errors.join("; ")}` : ""}`);
}
}
for (const limitation of report.limitations) console.log(`Note: ${limitation}`);
}
process.exitCode = report.status === "evidence-current" ? 0 : 1;
} catch (error) {
console.log(JSON.stringify({ schemaVersion: 1, status: "error", error: error.message }));
process.exitCode = 2;
}
}
if (process.argv[1] && realpathSync(resolve(process.argv[1])) === realpathSync(scriptPath)) await main(process.argv.slice(2));
SKILL.md
---
name: ss-resolve
description: Compile a small, deterministic StyleSeed rule bundle for one agent, output grammar, surface adapter, domain, page type, brand recipe, palette recipe, and optional profile. Use before setup or build, when project configuration changes, when updating StyleSeed, or to diagnose local installation, rule drift, and evidence health without changing files.
---
# Resolve effective StyleSeed context
Use the bundled `scripts/resolve-context.mjs`; do not hand-compose the rule stack.
1. Resolve the project boundary first: if either `.styleseed/project.json` or
`.styleseed/artifacts/index.json` exists, require a complete, valid registry. Do not fall back
to `STYLESEED.md` on a registry error. Only use that lock when no registry exists.
2. Keep the working directory at the user's project root. Invoke the script by its installed
path; do not `cd` into the skill directory.
3. Legacy single-artifact projects should prefer `--from-lock STYLESEED.md`. Registry projects use
`.styleseed/project.json` plus `.styleseed/artifacts/*.json` and must resolve with `--artifact`
or `--all`; in registry mode, edit project-owned config instead of passing selection overrides.
4. Read the emitted bundle before building: legacy writes `.styleseed/effective-rules.md`; registry
writes `.styleseed/bundles/<artifact-id>.md`.
5. Preserve the manifest output: legacy uses `.styleseed/manifest.json`; registry uses
`.styleseed/manifests/<artifact-id>.json`.
6. Use `--check` to detect context drift without rewriting files.
For installation or project-health questions, run the read-only diagnostic first:
```bash
node <installed-ss-resolve>/scripts/styleseed-doctor.mjs --project-root . --json
```
It checks the local distribution inventory, project configuration, compiled rules, and stored
evidence against current inputs. Use `--artifact <id>` to narrow a registry check. It never
sets up, migrates, recompiles, renders, or updates the project. Follow its `next` actions only
within the user's authorization. Exit 0 means current evidence for all selected artifacts,
not an independent visual judgment; exit 1 means attention needed; exit 2 means invalid invocation.
Legacy projects can have current rules while evidence remains `unsupported`. Installation
integrity does not prove host discovery, publisher authenticity, or the latest upstream revision.
```bash
node <installed-ss-resolve>/scripts/resolve-context.mjs \
--from-lock STYLESEED.md \
--agent codex
```
Registry project:
```bash
node <installed-ss-resolve>/scripts/resolve-context.mjs \
--project-root . \
--artifact app-dashboard \
--agent codex
```
Without a lock:
```bash
node <installed-ss-resolve>/scripts/resolve-context.mjs \
--agent claude \
--grammar operations-console \
--adapter product-ui \
--domain saas \
--page dashboard \
--recipe enterprise-workbench \
--palette cobalt-instrument \
--key-color "#175CD3" \
--palette-character balanced \
--palette-mode light \
--palette-harmony auto \
--surface-temperature cool \
--profile swiss
```
Use `--list` to print supported IDs. `--recipe auto` maps the selected grammar to a maintained
default; `--palette auto` maps that recipe to a contrast-verified semantic palette. Pass explicit
values when the product needs a different morphology or color posture. The default
output directory is `.styleseed/` in the
project root. For a project-local reference grammar, pass `reference:<slug>` and ensure
`.styleseed/rulesets/<slug>/RULESET.md` exists. Registry projects require the full six-file
reference contract: `RULESET.md`, `tokens.json`, `evidence.json`, `checks.md`,
`reference-board.html`, and `adapter.json`.
When a key color is present in flags or the lock, the resolver uses the shared OKLCH generator and
writes `.styleseed/palette.json` plus `.styleseed/palette.css`. The manifest records the generation
inputs. The maintained recipe still supplies product posture and semantic restrictions; its fixed
hex values become fallbacks rather than overriding the generated system.
Do not load `llms-full.txt` after a bundle resolves successfully. Load a larger source document
only when the bundle names an unresolved ambiguity that requires it.