v1 (four-stage greenfield: brand → briefings → wireframes → prototype) is preserved at tag stardust--v0.1.0. v0.3.0 is a complete refactor with a new surface (extract → direct → prototype → migrate) and a hard runtime dependency on impeccable. - plugins/stardust: replace contents with v0.3.0 source from paolomoz/stardust-2 (skills, evals, README, tile.json, plugin.json) - plugins/stardust/.claude-plugin/plugin.json: bump to 0.3.0; add repository and v2-relevant keywords - plugins/stardust/skills/*/SKILL.md: add license: Apache-2.0 to frontmatter - .claude-plugin/marketplace.json: update stardust entry with new description, version, category, keywords, author, homepage, repository, license (mirrors adobe-for-creativity entry shape) - plugins/stardust/README.md: status bumped to v0.3.0; v1 link repointed to the stardust--v0.1.0 tag Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
13 KiB
Eval: multi-template migrate
Validates Path A (approved page), Path A′ (template-applied sibling), Path B (unique render), canon application, module reuse, broken-link reporting, idempotent skip, canon-driven re-render, and bespoke-slot promotion suggestion.
Setup
A project where prepare-migration has run and three archetypes
are approved:
stardust/state.jsonlists 25 pages:home—approved(canon-author, V01 Polish)news/post-housing-summit—approved(article archetype)news—approved(listing archetype)- 18
article-typed pages —directed - 2
program-typed pages —directed - 1
form-typed page (donate) —directed - 1
unique-typed page (404) —directed
stardust/current/populated with full inventory and typed slots per page.- Project-root
PRODUCT.md,DESIGN.md,DESIGN.jsonexist with populatedextensions.canon,extensions.modules[],extensions.colorReservations[],extensions.metadata. stardust/canon/populated (header.html,footer.html,canon.css,modules/hotline-211.html,modules/donate-band.html,modules/story-card.html,modules/stat-trio.html).stardust/direction.mdhas an active direction (Mode A brand-faithful, withbrand_faithful_inversions[]declared for #FFFFFF, #000000, hex format, 0px corners, ALL CAPS).stardust/prototypes/has the three approved files.- No
stardust/migrated/yet. - Impeccable installed.
User prompt (run 1)
$stardust migrate
Expected behavior (run 1)
The stardust:migrate skill is invoked. It:
- Runs the master setup; verifies state, DESIGN, canon, direction.
- Prints the plan with branch partition:
- Path A: 3 pages
- Path A′: 21 pages (18 article, 2 program, 1 form)
- Path B: 1 page (404)
- For Path A pages: reads each
<slug>-proposed.html, refreshes:rootfrom DESIGN.md (with canon.pinned precedence), injects canon.css, applies content-preservation, validates with brand-faithful inversions lifted, writes. - For Path A′ pages: per
reference/template-and-module-rendering.md§ Path A′, forks the matching archetype's structure, injects this page's typed slots fromcurrent/pages/<slug>.json § slots, adapts content that doesn't fit (logged astemplate-adapted), applies canon, validates, writes. - For Path B (
404): per § Path B, composes a one-off using DESIGN.md/json + canon + modules. Logsunique-renderdecision. - Each migrated page has:
:rootblock fromcanon.pinnedfirst, DESIGN.md fallback otherwise.- Canon CSS injected after
:rootin the first<style>. - Canon header and footer applied verbatim with
data-canonflag on containers. - Required
data-*vocabulary present (data-template="<type>"on body/main,data-sectionper section,data-moduleper module instance,data-slotper slot container, locally scoped). - Provenance block as first child of
<head>with all required shas. - JSON-LD per page-type (
Articleon news posts,ItemListon news, extends-Organizationon about, none on 404). - Internal links rewritten to migrated-tree paths;
missing-slug targets flagged
data-broken-link="true". - Resolved
<head>metadata composed perreference/metadata-and-jsonld.mdfive categories.
- Each page's directory contains
index.htmlAND_meta.jsonsidecar with fullmigrationDecisions[]trace, slotsFilled, modules, canonShas, deviations. - Copies referenced media to
migrated/assets/media/; verifies fonts and favicon variants present. - Writes
migrated/robots.txtandmigrated/sitemap.xmlwith page-type-derived priorities. - Updates
state.json: 25 pages move tomigrated. - Prints summary including:
- Render branch counts (3 A / 21 A′ / 1 B).
- Pages with non-trivial decisions (with one-line "why").
- Broken internal links count + target slugs.
- Bespoke slots crossing promotion threshold (if any).
- One-line note acknowledging brand-faithful inversion lifts.
User prompt (run 2 — immediately after run 1, no other changes)
$stardust migrate
Expected behavior (run 2 — idempotent re-run)
- Zero file writes. Every page is sha-compared. All match → all 25 skipped.
- Summary: 0 migrated, 25 unchanged (idempotent skip), 0 failed.
state.jsonis NOT updated for unchanged pages.
User prompt (run 3 — after editing DESIGN.md to bump section padding)
$stardust migrate
Expected behavior (run 3 — DESIGN-driven re-render)
- designMd sha changed → all 25 pages re-render.
- Per
reference/migration-procedure.md§ :root block sourcing:canon.pinnedretains precedence for pinned paths; non-pinned paths inherit the new DESIGN.md value. - Summary reflects the cause:
Re-rendered: DESIGN.md sha changed (1a2b3c4 → 4d5e6f7).
User prompt (run 4 — after editing canon.css)
$stardust migrate
Expected behavior (run 4 — canon-driven re-render)
- canon CSS sha changed → all 25 pages re-render (canon affects every page).
- No stale-flagging is triggered. Stale-flagging fires
only when an underlying skill (
prototype --prepapproval,extract --prepmodule-catalog update,direct --re-direct) commits a content-aware change. A manual edit tocanon.cssdoesn't run any of those skills — migrate detects the change purely through sha-mismatch in the idempotent-skip check. - Summary surfaces canon-driven cause:
Re-rendered: canon.css sha changed (1k2l3m → 9m8n7o).
User prompt (run 5 — broken-link reporting)
The user manually deletes
stardust/current/pages/programs__shelter.json and the
corresponding state.json entry. Then runs $stardust migrate.
Expected behavior (run 5)
- Pages that linked to
/programs/shelter(e.g., the home program-grid module instance, the programs listing) get their links flaggeddata-broken-link="true"and the href rewritten to the migrated-tree path the slug WOULD have served at (/programs/shelter/). - Run summary surfaces:
Broken internal links: 1 /programs/shelter referenced by 3 pages; not in inventory - Migration succeeds for all 24 remaining pages.
User prompt (run 6 — bespoke slot promotion)
The home, get-help, and donate pages each carry a hotline-211
module instance with a <span data-slot="state" data-bespoke>Utah</span> element. The state slot is not in
the module catalog. After $stardust migrate:
Expected behavior (run 6)
- All three instances render with
data-bespokeon thestateslot. _meta.jsonsidecar for each page records amodule-bespoke-slotdecision.- Run summary surfaces the promotion suggestion:
Bespoke slots crossing promotion threshold: 1 hotline-211: "state" (3 instances) — consider `$stardust prepare-migration --refine-module` - Promoting via the suggested command updates the module
catalog (slot
statebecomes canonical with appropriate type/required/default), and the next migrate run drops thedata-bespokeflag from those instances (sha mismatch triggers re-render).
User prompt (run 7 — color-reservation violation)
The static-typed archetype that legal/privacy would fork
from contains a .footnote-callout element with inline
style="color: #DC323D" (centennial-red, reserved to the
trh-100-lockup module). When legal/privacy renders via
Path A' and inherits that section, the rendered output carries
the violating color outside the reserved context. The page
fails validation per
DESIGN.json.extensions.colorReservations[].
Expected behavior (run 7)
- The page is not written; its directory in
migrated/is left in its prior state (or absent on first run). state.json.lastRun.failures[]records:{ "slug": "legal__privacy", "kind": "color-reservation", "color": "#DC323D", "reservedFor": ["module:trh-100-lockup"], "found": "inline style on .footnote-callout" }- Run summary surfaces the failure with a remediation hint
(re-iterate prototype to remove the reserved color, or
promote the violating element to a
trh-100-lockupmodule instance).
User prompt (run 8 — Path A′ template adaptation)
An article page (news/post-2026-04-08-volunteer-recap)
contains a video embed in its body — the news/post-housing-summit
archetype has no video slot. After $stardust migrate:
Expected behavior (run 8)
- Path A′ renders the article using the archetype's
structure, injects all standard article slots
(headline, byline, body, related), and places the video
embed in an overflow region after
data-slot="article-body". - The page's
_meta.jsonsidecar records:{ "kind": "template-adapted", "template": "article", "adaptations": [ { "what": "video embed", "where": "after article-body slot", "via": "overflow region" } ], "reason": "Content carries a video module not present in canonical article template" } - Run summary surfaces the adaptation with a one-line "why".
User prompt (run 9 — canonical strategy: deployUrl unset)
state.json.site.deployUrl is null. Run $stardust migrate.
Expected behavior (run 9)
- Each migrated page's
<link rel="canonical">preserves the canonical fromcurrent/pages/<slug>.json § metadata.canonicalverbatim (typically pointing back to the live origin). For presales/staging contexts where the migrated tree isn't deployed, this attributes search-engine signals back to the live site correctly. - Run summary makes no special mention of canonical strategy (the default-preserve behavior is silent).
User prompt (run 10 — canonical strategy: deployUrl set)
User edits state.json.site.deployUrl to
"https://migrated.example.com". Run $stardust migrate.
Expected behavior (run 10)
- All 25 pages re-render (state.json change invalidates the canonical-derived metadata; migrate recomputes per page).
- Each migrated page's canonical is rewritten to
https://migrated.example.com/<slug-path>/. Home ishttps://migrated.example.com/. - Run summary surfaces the deployUrl-driven re-render cause.
User prompt (run 11 — brand-faithful inversion negative case)
The user edits direction.md to remove #FFFFFF from the
brand_faithful_inversions[] list while leaving canon.css
unchanged (canon.css still uses #FFFFFF as the page ground).
Run $stardust migrate.
Expected behavior (run 11)
- With
#FFFFFFno longer declared as inverted, the impeccable "no pure black/white" hard rule applies to every migrated page (canon CSS rules using#FFFFFFare now in violation). - Migrate refuses every affected page and records each
failure in
state.json.lastRun.failures[]withkind: "hard-rule-violation", the rule name, and the offending token/value. - Run summary surfaces the failures with a remediation hint (re-add the inversion to direction.md, or re-iterate the canon-author prototype to move off pure white).
What this eval covers
| Behavior | Run | Branch |
|---|---|---|
| Path A render verbatim from approved prototype | 1 | A |
| Path A′ render via template fork + slot injection | 1 | A′ |
| Path B unique render with canon + modules | 1 | B |
| Canon application (chrome verbatim + canon.css) | 1 | all |
| Module rendering via canonical files | 1 | all |
_meta.json sidecar with migrationDecisions |
1 | all |
| JSON-LD per page-type | 1 | all |
| Internal-link rewriting (always migrated-tree) | 1, 5 | all |
| Brand-faithful inversion lifts | 1 | all |
| Idempotent zero-write re-run | 2 | all |
| DESIGN-driven re-render (sha change cascade) | 3 | all |
| Canon-driven re-render (sha change cascade) | 4 | all |
| Broken-link reporting on missing slugs | 5 | all |
| Bespoke slot promotion threshold (3 instances) | 6 | A′ |
| Color-reservation enforcement | 7 | all |
| Template adaptation (extra content → overflow region) | 8 | A′ |
| Canonical strategy (deployUrl unset → preserve) | 9 | all |
| Canonical strategy (deployUrl set → rewrite) | 10 | all |
| Brand-faithful inversion negative (rule restored) | 11 | all |