Skip to content

feat: external design-system registry + ve:learn token learning#3

Merged
theclaymethod merged 5 commits into
mainfrom
feat/design-system-registry
Jul 15, 2026
Merged

feat: external design-system registry + ve:learn token learning#3
theclaymethod merged 5 commits into
mainfrom
feat/design-system-registry

Conversation

@theclaymethod

@theclaymethod theclaymethod commented Jul 15, 2026

Copy link
Copy Markdown
Owner

What does this change and why?

Design systems become user-owned artifacts maintained outside the skill and repo, resolved from a registry at export time — your brand tokens survive skill upgrades because they were never inside the skill. The repo intentionally ships NO design systems: brand token sets are usually private product material, so the public repo carries the mechanism plus synthetic eval fixtures only, and your (private) systems live in your own registry. See docs/design-systems.md for the format and for creating your own with ve:learn.

1. External design-system registry

  • Format: one directory per system — tokens.css (--ve-* custom properties in a :root block) + manifest.json (description, source provenance, font loads/fallbacks, hard-rule notes). Full docs in docs/design-systems.md.
  • Resolution order (first hit wins): $ARTIFACTURE_DESIGN_DIR~/.artifacture/design-systems/<repo>/design-systems/. The search list is deduped for the case where ~/.artifacture is a clone of this repo (then slots 2 and 3 are the same directory), and repo-local design-systems/* is gitignored (only its README is tracked) so learned/private systems can't be committed by accident.
  • ve:export scans the source for preset references, re-scopes the winning tokens.css to [data-ve-preset="<slug>"] (plus derived diagram fallbacks and manifest font imports), and inlines it into the standalone HTML. Unknown names warn and fall back to built-in mono-industrial tokens; malformed systems fail loudly instead of silently falling through; built-in names never consult the registry (no shadowing).

2. ve:learn — token learning

npm run ve:learn -- <source> --name <slug> [--out <dir>] drafts a system (default output: your user-global registry) from:

  • code — TS/JS/CSS token files: named hex colors, font stacks, weight/size ramps, grid geometry, easing
  • url — fetched HTML + linked CSS: :root custom props, @font-face, dominant colors
  • image — palette quantization via a canvas in Playwright's bundled Chromium

A shared deterministic mapper (name hints → contrast/saturation/coverage statistics) produces the full --ve-* set; the draft manifest records provenance and an extraction report (per-token decisions, required-token coverage). The agent-assisted refinement flow is documented in docs/design-systems.md.

The eval suite defines "good" for token learning; the extractors are fit to it. evals/design-systems/ (second leg of npm run ve:eval, same fixture/expectation idiom as the verifier evals) pins fixture sources to golden token sets per modality — exact hex/font-stack match for code and url, tolerance match for image, no live network — plus loader cases for resolution order, the repo-clone collision, unknown-preset fallback, built-in non-shadowing, and loud malformed-system failures. All fixtures are synthetic invented brands (acme-terracotta, an Acme site, a generated palette PNG); the code fixture still exercises every mapping path (name hints, muted/faint contrast ranking, hue-classified danger, grid/weight/easing extraction, defaults). Mutation-verified: flipping the muted-contrast ranking or the accent-fill precompositing makes the suite fail.

Security (adversarial-review fixes, included)

Design systems are third-party content that gets inlined into shared HTML artifacts, so the boundary is hardened end to end:

  • Style-element breakout: token values containing < or control characters are rejected loudly before inlining (the declaration parser's value group would otherwise admit </style><script>… payloads); manifest.fonts.imports must be an array of plain http(s) URLs (no quotes/angle brackets/backslashes/parens/whitespace), rejected loudly otherwise.
  • Injection point: a single injectDesignSystemCss() helper refuses CSS containing </style, <!--, or <script, and uses a function replacer so $&-style replacement metacharacters insert verbatim instead of splicing the matched </head>.
  • Clean at the source: ve:learn strips angle brackets/control bytes from extracted values at ingestion, so learned systems never contain them.
  • SSRF guardrails in the URL modality: private/loopback hosts refused by default (--allow-private to override), ~5MB response cap, 15s fetch timeout, content-type checks.
  • Pinned by evals: a new loader-hostile-tokens-inert case (style-breakout throws; hostile font imports throw; $& lands verbatim, exactly one style block) — mutation-verified by disabling the value guard. Extraction evals now also check value sanity (parseable colors, numeric tokens, non-empty font stacks), not just key presence, since the token goldens are implementation-generated (hand-verified against fixture sources).

Why no shipped example system?

Intentional. A design system encodes a brand; the reference flow is: run ve:learn against your own token source/site/image into ~/.artifacture/design-systems/<slug>/, then refine per the docs. The synthetic acme-terracotta eval fixture doubles as the reference shape for what ve:learn consumes and produces. The end-to-end path (registry resolution from the user-global dir → tokens inlined into a standalone export) is covered by the loader evals and was verified against a real private system in a local registry.

Found and fixed along the way: derived panel-strong/row/accent-soft tints are precomposited to solid hex — opposite-polarity alpha bases read as dark surfaces to contrast tooling and flipped light systems' tables unreadable (caught by running a branded artifact through ve-verify).

Test plan

  • npm test — 13 node:test cases (loader resolution order + scoping + preset scanning; one test per extractor modality incl. pure palette quantization); newly wired into the CI static job
  • npm run ve:eval — all seeded verifier violations + clean fixtures pass, plus all 8 design-system eval cases (coverage: 47/47 required tokens in every modality)
  • npm run ve:check, npm run ve:check-integrity, npm run check:manifests (versions bumped in lockstep to 0.8.0)
  • Mutation check: two heuristic mutations each fail the eval suite; restore passes
  • End-to-end private-registry check (local, not committed): a system learned into ~/.artifacture/design-systems/ resolves by preset name and its tokens are inlined into the exported HTML with no repo-local copy present

Checklist

  • npm run ve:check passes.
  • npm run ve:eval passes.
  • No new verifier check added (no expectations entry needed); the new eval suite ships with its own fixtures/goldens.
  • No new shared component; SKILL.md preset docs updated for registry names.

🤖 Generated with Claude Code

Clayton Kim and others added 4 commits July 15, 2026 11:42
Spec-first evals for the external design-system feature, in the existing
fixture/expectation idiom: one extraction case per ve:learn modality
(code: a synthetic acme-terracotta brand token module -> golden token
set, exact match; url: saved HTML+CSS site served via injected fetch,
no live network; image: quantized-palette PNG, tolerance match) plus
loader cases (resolution order, repo-clone collision dedupe,
unknown-preset fallback warning, built-ins never shadowed, malformed
systems fail loudly). Reports required --ve-* token coverage per case.

All fixtures are invented brands — real design systems are private user
artifacts and never ship in this repo. The synthetic code fixture still
exercises every mapping path: name hints, muted/faint contrast ranking,
hue-classified danger, grid/weight/easing extraction, and defaults.

Mutation-verified: flipping the muted-contrast ranking or the
accent-fill precompositing makes the suite fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Design systems are user-owned directories (tokens.css with --ve-* custom
properties in a :root block + manifest.json with provenance/fonts/notes)
resolved in order: $ARTIFACTURE_DESIGN_DIR -> ~/.artifacture/
design-systems -> <repo>/design-systems (first hit wins; deduped when
~/.artifacture is itself a repo clone). ve:export scans the source for
preset references, re-scopes the winning tokens.css to
[data-ve-preset="<slug>"] with derived diagram fallbacks and manifest
font imports, and inlines it into the standalone HTML. Unknown names
warn and fall back to built-in mono-industrial tokens; malformed
systems throw instead of silently falling through; built-in names never
consult the registry. The repo ships NO systems: repo-local
design-systems/ is gitignored (only its README is tracked) because
brand tokens are usually private and ~/.artifacture may BE a repo
clone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r image

npm run ve:learn -- <source> --name <slug> [--out <dir>] drafts a
design system into the registry (default: $ARTIFACTURE_DESIGN_DIR,
else ~/.artifacture/design-systems — the private, user-global home for
brand tokens). Modalities: code files (named hex tokens, font stacks,
weight/size ramps, grid geometry, easing), URLs (:root custom props,
@font-face, dominant colors across fetched linked CSS), and images
(palette quantization via a canvas in Playwright's bundled Chromium).
A shared deterministic mapper turns any extraction into a full --ve-*
token set (name hints first, then contrast/saturation/coverage
statistics; panel/accent tints precomposited to solid hex so light
systems survive naive contrast tooling). The draft manifest records
provenance plus an extraction report: per-token decisions, size ramp,
and required-token coverage.

Pure heuristics live in learn-extractors.mjs; fetch/browser I/O in
learn-sources.mjs (injectable fetch, no network in tests). The
evals/design-systems suite is the spec these are fit to.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… to 0.8.0

- npm test (node:test: loader, extractors, diagram layout) added and run
  in the CI static job
- npm run ve:eval now runs the design-system eval suite as its second
  leg (image case uses the chromium already installed in the evals job)
- docs/design-systems.md: registry format, resolution order and the
  ~/.artifacture-repo-clone collision, ve:learn modalities, and the
  agent-assisted refinement flow. Design systems are intentionally
  external, user-owned artifacts — the repo ships the mechanism and
  synthetic eval fixtures only; private brand registries live outside
  the repo
- README, CONTRIBUTING, SKILL.md updated; CHANGELOG 0.8.0; version
  sources bumped in lockstep (check:manifests green)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@theclaymethod
theclaymethod force-pushed the feat/design-system-registry branch from 9ffd438 to 476c41c Compare July 15, 2026 18:46
@theclaymethod theclaymethod changed the title feat: external design-system registry, ve:learn token learning, function-health shipped example feat: external design-system registry + ve:learn token learning Jul 15, 2026
Output escaping where untrusted content reaches the shared artifact
(found in adversarial review):

- tokens.css values are validated before inlining: the declaration
  parser's value group ([^;}]+) admits '</style><script>...' payloads,
  so any value containing '<' or control characters is rejected loudly
  (scripts/ve-mdx/design-systems.mjs assertSafeTokenValue, enforced in
  scopeTokensCss)
- manifest.fonts.imports must be an array of plain http(s) URLs with no
  quote/angle-bracket/backslash/paren/whitespace characters
  (validatedFontImports); non-arrays throw instead of crashing
- new injectDesignSystemCss() is the single injection point: refuses
  CSS containing '</style', '<!--', or '<script', and uses a function
  replacer so '$&'-style sequences insert verbatim instead of splicing
  the matched '</head>' (export.mjs now uses it)
- ve:learn sanitizes extracted values at ingestion (angle brackets and
  control bytes stripped from font stacks and emitted tokens), so
  learned systems are clean at the source
- SSRF guardrails in learn-sources.mjs: private/loopback hosts refused
  by default (--allow-private to override), ~5MB response cap, 15s
  fetch timeout, content-type check

Evals/tests: new loader-hostile-tokens-inert eval (style-breakout
throws; hostile font imports throw; '$&' lands verbatim with exactly
one style block) — mutation-verified by disabling the value guard;
extraction evals now do value-sanity (parseable colors / numeric /
non-empty stacks) on top of key coverage, guarding the
implementation-generated goldens against agreeing on garbage; unit
tests for the injector and isPrivateHost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@theclaymethod
theclaymethod merged commit 31f6e9e into main Jul 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant