원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.
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-saveplaywright
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.jsonexists with at least one
directed page.
- Verify project-root
DESIGN.mdandDESIGN.jsonexist 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.mdhas 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 — variance-probed once per template before cloning, reference/fidelity-tiers.md § Sibling variance probe; deltas become variant classes, never per-page forks), 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.
- Content-count acceptance per
reference/fidelity-tiers.md § Content-count acceptance: compare role-classified node counts (headings, body/list nodes, CTAs, images) between the captured source page JSON and the rendered result. A count drop in any class not covered by a logged contentDeviations[] entry fails the page — dropped-content importer bugs must surface here, while the importer is still cheap to fix, not at a downstream fidelity gate. Record the pass in _meta.json#gatesPassed[] as "content-count".
- 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.htmland 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.txtandsitemap.xml
derived from the migrated page inventory per reference/metadata-and-jsonld.md § Sitemap entry.
- If
--cleanwas 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:
# No source-tree escapes
find stardust/migrated/ -type f -name '*.html' -exec grep -l '\.\./current/' {} +
# Error: "asset still points outside the migrated tree; rewrite via the
# asset-bundling pass per reference/asset-bundling.md § Detection"
# No absolute internal references in attribute values (404 on file:// and subpath)
grep -rE '(href|src)="/[^/]' stardust/migrated/ --include='*.html'
# Error: "absolute href `/beers/` will 404 on file:// and on subpath hosts;
# rewrite via the page map per migration-procedure.md § Reference shape"
# No absolute internal references in url() (inline style, <style> blocks, CSS)
grep -rE 'url\(\s*["'\'']?\s*/[^/]' stardust/migrated/ --include='*.html' --include='*.css'
# Error: "absolute url(/...) reference will 404 on file:// and on subpath hosts;
# rewrite via the asset-bundling pass per asset-bundling.md § Rewrite"
# No directory-only nav (doesn't resolve on file://). Pattern accepts
# only relative or root-absolute hrefs (./, ../, /, or bare segment)
# so external URLs like https://google.com/ aren't false-flagged.
grep -rE 'href="(\.{0,2}/|[a-zA-Z0-9_-])[^:"#?]*/"' stardust/migrated/ --include='*.html'
# Error: "directory-only href `./beers/` won't resolve on file://;
# append the explicit index.html (or the source URL's .html leaf)
# per § Reference shape"
# pageMap consistency — every internal href appears as an outputPath
node skills/migrate/fixtures/pagemap-audit.mjs stardust/migrated/ stardust/state.json
# Error: "internal href has no pageMap entry; link rewriting bypassed the
# page map per § Page map (build once, use everywhere)"
# Headless file:// round-trip — the test that proves zip-and-deploy works
node skills/migrate/fixtures/file-protocol-audit.mjs stardust/migrated/
# Error: "<offending file> linked <ref> that 404s under file://; see the
# Playwright network log printed above"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:
statusadvances 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
migrateblock 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 migrateOutputs
| 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 migratewith 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: trueper
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 --allre-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—:rootblock
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.

