| name | migrate |
| description | Apply DESIGN, canon, and modules to every page in the inventory, producing a deployable static HTML site. Use to migrate or render the whole captured site into the redesigned static tree ("migrate the pages", "render the migrated site", "apply the design to all pages", "build the deployable site", "convert the approved prototype into the full site") — the page-rendering step between prototype and deploy/rollout. Three render branches (approved page, template-applied sibling, unique render), with a declared fidelity tier per page. Per-page, incremental, idempotent, content-preserving by default. |
| license | Apache-2.0 |
stardust:migrate
Apply the target spec authored by direct, the visual canon
written by prototype --prep, and the brand-module catalog
extracted during prepare-migration to every page in the
inventory. Produces a self-contained, deployable static HTML site
under stardust/migrated/. Per-page, incremental, idempotent.
migrate is the final stardust phase. Output is platform-
agnostic HTML — downstream conversion (AEM EDS, a CMS, a
framework) is the job of a separate plugin that consumes
migrated/ plus DESIGN.json plus the per-page _meta.json
sidecars.
Inputs
<slug> — optional positional. Migrate just this page. Without
it, migrate every page whose status is directed,
prototyped, or approved (and not stale).
--all — migrate every page including stale ones.
--force — re-migrate every page even when the idempotent
skip would skip them.
--require-approved — refuse to migrate any non-approved
page. Default behaviour migrates directed pages too (using
Path A′ or Path B per
reference/template-and-module-rendering.md); this flag flips
approval-gating on.
--strict-canon — refuse approvals that conflict with canon.
Default logs the deviation and continues. Useful for projects
where canon discipline matters more than per-template
flexibility.
--clean — delete assets previously bundled but no longer
referenced from stardust/migrated/assets/. Off by default
(migrate is additive). Implies --force: every page is
re-rendered so the run's bundledAssets Set is the complete
union of currently-referenced assets — otherwise --clean
would risk deleting assets still referenced by
idempotent-skipped pages. See
reference/asset-bundling.md § Stale asset cleanup.
--pin-timestamp <ISO8601> — pin the migrate-provenance
timestamp so re-runs without source changes produce byte-
identical HTML. Default re-uses the current wall clock, which
is fine for normal use; CI deployment fingerprinting may want
the pin.
The mobile-adapt audit, content-sourcing scan, and placeholder
refusal are all mandatory gates — there is no --skip-* or
--allow-* flag to bypass them. If a gate refuses a page, the
remediation is to fix the proposed file (re-prototype, edit
inline, or run an impeccable command) and re-invoke migrate.
Setup
-
Playwright re-probe (mandatory first step). --no-save playwright
installs from earlier phases are pruned by any later real npm i
(extract SKILL.md § Setup → --no-save installs are ephemeral). Before
any rendering step, probe
node -e "import('playwright').then(()=>process.exit(0))" from the
project root and re-install (npm i -D playwright --no-save --legacy-peer-deps) on failure.
-
Run the master skill's setup
(skills/stardust/SKILL.md § Setup).
-
Verify stardust/state.json exists with at least one
directed page.
-
Verify project-root DESIGN.md and DESIGN.json exist with
DESIGN.json.extensions.canon populated.
-
Verify stardust/canon/ exists with at least
header.html, footer.html, canon.css.
Canon auto-bootstrap (when steps 3–4 find no canon). The
documented prototype → migrate → deploy happy path does not
run prepare-migration, so a first migrate legitimately arrives
with no canon (observed on 4 of 6 e2e sites, where every run had
to derive canon by hand to proceed — this is the fix). When
canon is absent and at least one approved prototype exists,
do not stop: run the canon write-back inline from the first
approved prototype (the canon-author, default home) per
../prototype/reference/canon-extraction.md § Five-step
procedure — extract header.html / footer.html / canon.css
to stardust/canon/, pin tokens + compositional moves to
DESIGN.json.extensions.canon, and record
canon.source: "auto-bootstrap: <slug>". This is exactly what
prototype --prep does on first approval; migrate performs it
on demand so the core pipeline never dead-ends. Only stop and
recommend $stardust prepare-migration when canon is absent
and no approved prototype exists (there is nothing to derive
canon from). Under state.json.handsOff the bootstrap is
automatic and logged; interactively, surface it as a one-line
notice before proceeding.
-
Verify stardust/direction.md has an active (not pending)
direction.
-
Read state.json.pages[] and partition into:
- inScope: status
directed, prototyped, or
approved, stale: false (or --all / explicit
<slug>).
- skipped: everything else, with reason captured.
-
Validate provenance on every in-scope page. Call
validateProvenance(page) per
skills/stardust/reference/state-machine.md § Provenance
validation for every page in inScope. Abort with the
helper's error when any page lacks live-render evidence —
migrating a synthesized page record produces deployable HTML
that misrepresents the source site, the exact failure mode
that motivated the validator. Surface Provenance OK on N pages in the migrate-plan output before Phase 1.
-
Mobile-adapt audit on every Path A / Path A′ source. For
every page whose render branch consumes a proposed or
archetype HTML file (Path A, Path A′ per
reference/template-and-module-rendering.md § Render path
selection), run the audit per skills/prototype/SKILL.md
§ Mobile-adapt audit:
<meta name="viewport" content="width=device-width, ...">
present, width not pinned to a fixed pixel value.
- At least one
@media (max-width: ...) rule.
- At least one mobile-targeted breakpoint at ≤ 640px.
Refuse pages that fail — the audit is mandatory; there is no
skip flag. The user fixes the proposed file (re-prototype or
chat-driven impeccable command) and re-invokes migrate.
Record the audit result per page in the migrate report and
in the post-render _meta.json#audit.adapt sidecar. Path B
(unique-renders) skips the audit because adapt hasn't run
on those pages — a Path B page that needs mobile coverage
gets it via $impeccable adapt invoked separately by the
user. Surface this distinction in the report so it's not
read as a silent skip.
Procedure
Phase 1 — Plan
Print the plan and wait for confirmation when the scope is large:
migrate plan
============
In scope: 127 pages
Path A (approved) 6 pages: home, news/post-housing-summit, news, ...
Path A' (template-applied) 118 pages: 84 article, 5 listing, 11 program, 2 form, 16 static
Path B (unique) 3 pages: 404, search, faq
Skipped: 0 stale, 0 unscoped
DESIGN.md sha: 1a2b3c4
DESIGN.json sha: 5d6e7f8
Canon shas: header:7g8h9i footer:9i0j1k css:1k2l3m
Output: stardust/migrated/ + per-page _meta.json sidecars
Idempotent skip: enabled (run with --force to override)
Reply "go" to proceed.
For 1-3 pages or <slug> invocation, skip the confirmation.
Phase 2 — Per-page render
For each page in scope, follow
reference/migration-procedure.md and
reference/template-and-module-rendering.md:
- Idempotent skip check first (sha-compare across
designMd, designJson, sourceCurrent, sourceProposed,
canonShas, archetypeSource).
- Placeholder gate (Path A and Path A′ only — pages with a
proposed file or archetype). Refuse when
[data-placeholder]
elements or non-empty _provenance.unsourcedContent[] are
present — the user fills the missing content in the proposed
file before re-invoking migrate. No bypass flag.
- Render branch selection (LLM judgment per T&M §
Render path selection): A / A′ / B. Declare the page's
fidelityTier from the branch — A → archetype (craft-gated),
A′ → sibling (canon-fork, the cheap default for breadth),
B/bodyless → thin — per reference/fidelity-tiers.md. Record
fidelityTier, archetypeSource, and gatesPassed[] in
_meta.json so coverage shows what was craft-gated vs cloned.
- Render per the chosen branch's procedure in T&M.
- Canon application — chrome injection, canon.css
injection, deviation logging.
- Module rendering — render module instances via
stardust/canon/modules/<id>.html; bespoke slots logged
with data-bespoke.
- Apply content-preservation rules per
reference/content-preservation.md. Internal-link rewriting
always emits migrated-tree paths; missing slugs flagged
broken.
- Compose
<head> metadata per
reference/metadata-and-jsonld.md (five categories;
page-type-driven JSON-LD).
- Validate per T&M § Validation contracts. Strict contracts
refuse the page; soft contracts log and continue.
- Compute output path per migration-procedure.md
§ Output path mapping.
- Asset bundling. Scan the final HTML for asset references
(six detection shapes per
reference/asset-bundling.md § Detection), copy each unique
referenced subpath from stardust/current/assets/<subpath> to
stardust/migrated/assets/<subpath> (preserving subdir
structure), then rewrite every reference to the root-relative
form /assets/<subpath>. Cross-page dedup uses a
module-level Set seeded from
state.json.migrate.bundledAssets[]. Missing source assets
warn-and-skip per § Edge cases; the bundle stays internally
consistent.
- Media reconciliation. For every image not bundled to
same-origin (reused source-CDN URLs under Mode A image-reuse),
decide optimize/keep/rewrite/omit per
reference/media-reconciliation.md. Cross-origin <img> kept
as source URLs must skip createOptimizedPicture (it drops
the ?v= key and corrupts the rendition); broken URLs are
repaired (missing ?-delimiter, wrong host) or omitted, never
shipped as about:error. rollout re-runs the authoritative
network resolve at delivery (media-reconcile.mjs).
- Cinematic sibling (when
<slug>-cinematic.html exists).
Migrate consumes the STATIC prototype only — the cinematic layer
is never merged. Copy the motion assets (lenis.min.js,
lenis.min.css) from stardust/prototypes/ to
stardust/migrated/assets/motion/ (idempotent) for downstream
consumers (deploy/rollout decide whether to wire them), and
record cinematic-variant-not-consumed in the page's
_meta.json#migrationDecisions[].
- Write the migrated
index.html and the _meta.json
sidecar in the same directory. Provenance block as first
child of <head>. Record assetsBundled (count of unique
asset refs on this page) in _meta.json.
Phase 3 — Sitewide assets and bundle finalisation
Per-page asset bundling already happened in Phase 2 (every
referenced media subpath is on disk under
stardust/migrated/assets/). Phase 3 fills in the sitewide
assets that no individual page references explicitly:
-
Copy stardust/current/assets/logo.<ext> to
stardust/migrated/assets/logo.<ext> (only if missing or
stale). Record under state.json.migrate.bundledAssets[].
-
Verify favicon variants and font files were generated by
prepare-migration Phase 4. If absent, log a warning and
continue (the migrated site renders without them, just
missing some platform-specific affordances).
-
Add stardust/migrated/robots.txt and sitemap.xml
derived from the migrated page inventory per
reference/metadata-and-jsonld.md § Sitemap entry.
-
If --clean was passed, compute
stale = priorBundle.filter(p => !bundledAssets.has(p))
from state.json.migrate.bundledAssets[] and remove each
stale subpath from stardust/migrated/assets/. Record the
deletions under state.json.migrate.cleanedAssets[]. Per
reference/asset-bundling.md § Stale asset cleanup.
-
Verify portability. The bundle must work via file://,
at a webserver root, and at any subpath — "one shape, works
everywhere". Run every audit; any non-empty grep output or
non-zero fixture exit fails the run with the cited error
message:
find stardust/migrated/ -type f -name '*.html' -exec grep -l '\.\./current/' {} +
grep -rE '(href|src)="/[^/]' stardust/migrated/ --include='*.html'
grep -rE 'url\(\s*["'\'']?\s*/[^/]' stardust/migrated/ --include='*.html' --include='*.css'
grep -rE 'href="(\.{0,2}/|[a-zA-Z0-9_-])[^:"#?]*/"' stardust/migrated/ --include='*.html'
node skills/migrate/fixtures/pagemap-audit.mjs stardust/migrated/ stardust/state.json
node skills/migrate/fixtures/file-protocol-audit.mjs stardust/migrated/
The audits are mandatory — there is no skip flag. The contract
is "self-contained, zip-and-deploy" and these audits are the
verifiers that back the claim.
Asset migration is idempotent — files are content-hashed and
copied only when missing; per-page bundling deduplicates across
the run.
Phase 4 — State and report
Update state.json:
- For each successfully migrated page:
status advances to
migrated, append a history entry, clear any stale flag,
set migratedPath.
- For pages skipped via idempotent skip: leave state
unchanged.
- For pages that failed validation: leave state unchanged, log
the failure in
state.json.lastRun.failures[].
- Write the top-level
migrate block per
skills/stardust/reference/migrate-output-format.md
§ State.json contract: selfContained: true, outputDir,
totalAssetsBundled, bundledAssets[], per-page
assetsBundled counts, missingAssets[], cleanedAssets[].
This is the forward-compat signal downstream consumers test
for.
Print the run summary:
migrate complete
================
122 migrated home, about, news/post-housing-summit, ...
3 unchanged about, programs/shelter, news/post-old (idempotent skip)
2 failed contact (validation: required slot missing),
legal/privacy (validation: color-reservation violated)
0 stale skipped
Render branches:
Path A 6 approved-from-prototype
Path A' 116 template-applied (84 article, 5 listing, 11 program, 2 form, 14 static)
Path B 3 unique-render (404, search, faq)
Pages with non-trivial decisions: 12
about canon-deviation: footer carries financials disclaimer
donate template-adapted: amount-pills slot moved above headline
...
Broken internal links: 5
/events referenced by 2 pages; not in inventory
/press referenced by 1 page; not in inventory
...
Bespoke slots crossing promotion threshold: 1
hotline-211: "state" (3 instances) — consider `$stardust prepare-migration --refine-module`
Missing assets: 2
generated/orphan-1.jpg referenced by 1 page (home)
generated/orphan-2.jpg referenced by 2 pages (about, contact)
(Re-extract or accept the gap — bundle is deployable; refs 404 at view time.)
Output: stardust/migrated/ (122 pages, 47 bundled assets, 4.2 MB) — self-contained, zip-and-deploy
Next:
- Review: open stardust/migrated/index.html in a browser
- Audit: $impeccable critique stardust/migrated/
- Deploy: cd stardust/migrated && zip -r ../site.zip .
upload the zip to any static host that serves at the host root
- Refine: edit DESIGN.md or canon files, then re-run $stardust migrate
Outputs
| Path | Purpose |
|---|
stardust/migrated/<source-url-path> | Migrated page. Output path mirrors the source URL literally (see reference/migration-procedure.md § Output path mapping). The bundle is zip-and-deploy: drop on any static host at any path, or open index.html directly via file://. Every internal reference is relative to the page that emits it; nav targets carry an explicit index.html (or the source URL's literal filename) so file:// resolves without a server. |
| _meta.json sidecar | Lives next to each migrated page. For <dir>/index.html the sidecar is <dir>/_meta.json; for <dir>/<name>.html the sidecar is <dir>/<name>._meta.json so multiple .html siblings don't collide. Per reference/migration-procedure.md § _meta.json sidecar. |
stardust/migrated/index.html | The home page (special case). |
stardust/migrated/_meta.json | Home sidecar. |
stardust/migrated/assets/logo.<ext> | Brand logo (sitewide). |
stardust/migrated/assets/<subpath> | Every asset referenced by any migrated page, bundled. Source subdir structure preserved verbatim. |
stardust/migrated/assets/favicon.<ext> + variants | Favicon and apple-touch-icon, manifest icons. |
stardust/migrated/assets/fonts/... | Downloaded font files (from canon @font-face URLs). |
stardust/migrated/robots.txt | Minimal robots.txt. |
stardust/migrated/sitemap.xml | Sitemap derived from migrated inventory + page types. |
stardust/state.json | Updated with migrated status, history, and the migrate block (selfContained: true, asset counts). |
Idempotent and incremental
The whole pipeline is built around two properties:
- Idempotent. Re-running
$stardust migrate with no
changes produces zero file writes. Every page is sha-compared
across designMd, designJson, sourceCurrent, sourceProposed
(Path A), canonShas, archetypeSource (Path A′) — and skipped
if all match.
- Incremental. Migrate 5 pages today, 20 pages tomorrow,
fix one page's content next week — the migrated tree is
always the union of every successful migration to date.
These properties hold even when DESIGN.md, canon, or modules are
edited mid-run: the edit changes the relevant sha, so the next
migrate run re-renders every affected page (canon and DESIGN.md
edits typically affect every page).
Stale handling
When direction.md, canon, or the module catalog changes after
some pages have been migrated:
- Affected pages are flagged
stale: true per
skills/stardust/reference/state-machine.md § Stale flagging.
Stale-flagging is content-aware in all three trigger cases.
$stardust migrate (no flags) skips stale pages and reports
the count.
$stardust migrate --all re-migrates each stale page,
clearing the flag on success.
$stardust migrate <slug> always operates on the named page,
stale or not.
The user decides whether stale pages should be refreshed —
direction/canon/module changes don't invalidate prior migrated
work, they just mark it as out-of-step.
Failure modes
- No directed pages. Recommend
$stardust direct (or
$stardust extract if no extracted state).
- No DESIGN.md or DESIGN.json. Recommend
$stardust direct.
- No canon, but an approved prototype exists. Do NOT stop —
auto-bootstrap canon from the canon-author inline (Setup step 4).
- No canon and no approved prototype. Recommend
$stardust prepare-migration (or approve a prototype first).
- Pending direction. Refuse; user must resolve direction
first.
- Validation failure on a single page. Skip that page,
continue, log the failure under
state.json.lastRun.failures[]. Do not abort the whole run.
- Asset copy failure. Continue the run; record the missing
asset in the page's
migrationDecisions[] with
kind: "asset-missing". The migrated <img src> keeps the
original absolute URL as a fallback.
- Output path collision. Two slugs mapping to the same
output path. Refuse to write the second one and surface to
the user — manual slug rename needed.
- Placeholder content in proposed/archetype file. Refuse
to ship a page whose source contains
[data-placeholder]
elements. Surface the unsourced list and recommend sourcing
real content (re-prototype, or edit the proposed file
directly). There is no bypass flag — shipping placeholders to
a public site is the failure mode this gate exists to prevent.
- Color reservation violated. Refuse the page; surface to
user with the offending color and the reserved-for context.
- Brand-faithful inversion conflict. A hard rule declared
inverted in
extensions.divergence.brand_faithful_inversions[] is lifted
from validation per T&M § Brand-faithful inversion handling.
Emit a one-line note in the run summary acknowledging the
lift.
What migrate does NOT do
- Critique or audit the migrated output. Run
$impeccable critique stardust/migrated/ after migration if
you want a quality assessment.
- Deploy. Stardust does not push, upload, or modify origin.
- Generate AEM EDS, a CMS payload, or framework components. The
output is platform-agnostic static HTML; downstream conversion
is a separate plugin's job.
- Re-fetch the live site. Offline after extract Phase 1.
- Run any iteration loop. Iteration belongs to
prototype;
migrate consumes the result.
References
reference/migration-procedure.md — per-page render procedure,
output path mapping, validation, provenance shape, idempotent
skip, sidecar schema.
reference/template-and-module-rendering.md — three render
branches in detail, slot injection, deviation policy,
validation contracts.
reference/metadata-and-jsonld.md — head composition, JSON-LD
per page-type, canonical strategy.
reference/content-preservation.md — what's kept,
transformed, dropped; internal-link rewriting; asset path
rewriting; form handling.
reference/asset-bundling.md — detection / copy / rewrite
contract for the per-page asset-bundling phase.
skills/stardust/reference/migrate-output-format.md — the
self-contained-bundle contract downstream consumers can rely
on (asset reference shape, directory layout,
state.json.migrate block).
skills/stardust/reference/token-contract.md — :root block
refreshed from DESIGN.md on every render.
skills/stardust/reference/data-attributes.md — structural
attributes including data-template, data-module,
data-slot, data-canon, data-deviation, data-bespoke,
data-broken-link.
skills/stardust/reference/state-machine.md — page lifecycle,
page typing, stale-flagging cascade.
skills/stardust/reference/artifact-map.md — provenance shape
for migrated artifacts; canon files; sidecar shape.
skills/prototype/reference/canon-extraction.md — how canon
is built (input to migrate).
skills/prepare-migration/SKILL.md — the cascade that
produces every input migrate consumes.