---
title: "wix/skills"
description: "Wix Skills"
source: https://github.com/wix/skills
ref: main
license: MIT
licenseName: "MIT License"
canonical: https://skillsdocs.com/wix/skills
base: https://github.com/wix/skills/blob/main/
chapters: 20
inlined: 20
withheld: 0
words: 49675
updated: 2026-08-11T10:29:34Z
generator: "Skills Docs"
---

> **wix/skills** — every Agent Skill in this repository, inlined verbatim.
>
> Canonical HTML: https://skillsdocs.com/wix/skills
> Per-chapter Markdown: https://skillsdocs.com/wix/skills/<skill>.md
> Machine manifest: https://skillsdocs.com/wix/skills/.well-known/agent-skills/index.json
> JSON: https://skillsdocs.com/api/v1/books/wix/skills
> Install: `npx skills add wix/skills`
> Upstream: https://github.com/wix/skills @ `main`
> Licence: MIT
>
> Content is mirrored from GitHub and © its authors, served unmodified. Takedown: https://github.com/DreambaseAI/skillsdocs/issues/new?labels=takedown&title=Takedown+request

# wix/skills

Wix Skills

- **Chapters:** 20
- **Inlined:** 20 (licence detected)
- **Words:** 49,675
- **Reading time:** 227 min
- **Stars:** 26

## Table of contents

1. [writing-great-skills](https://skillsdocs.com/wix/skills/writing-great-skills.md) — Reference for writing and editing skills well — the vocabulary and principles that make a skill predictable.
2. [wix-app](https://skillsdocs.com/wix/skills/wix-app.md) — Build and review Wix CLI app extensions — dashboard pages, modals, plugins, menu plugins, custom element widgets, Editor React components, site plugins, embedd…
3. [wix-auth](https://skillsdocs.com/wix/skills/wix-auth.md) — Authenticate with Wix to obtain an access token for calling Wix APIs. Use when an agent needs a valid Wix access token and has none, or when the stored token i…
4. [wix-design-system](https://skillsdocs.com/wix/skills/wix-design-system.md) — Wix Design System component reference. Use when building UI with @wix/design-system, choosing components, checking props and examples, or writing tests with co…
5. [wix-docs](https://skillsdocs.com/wix/skills/wix-docs.md) — Look up the Wix API/SDK documentation to confirm an exact endpoint, HTTP method, request/response shape, field, enum, or error before writing Wix code — never…
6. [wix-headless-entry](https://skillsdocs.com/wix/skills/entry.md) — Build a complete Wix Managed Headless site from a single prompt, then connect Wix Business Solutions. The deterministic first steps — system prerequisites and…
7. [wix-headless](https://skillsdocs.com/wix/skills/wix-headless.md) — Connect Wix business services (Stores, Bookings, CMS, Blog, Events, Forms, and more) to a Wix Headless frontend — infer the needed capabilities, install the ap…
8. [wix-manage](https://skillsdocs.com/wix/skills/wix-manage.md) — Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS,…
9. [rp-discovery](https://skillsdocs.com/wix/skills/rp-discovery.md) — Discovers and documents the source platform schema (entities, fields, relationships) for a migration project. Use when capturing source structure before mappin…
10. [rp-execute-import](https://skillsdocs.com/wix/skills/rp-execute-import.md) — Runs the generated extract/import pipeline and records execution results. Use when setup and codegen are complete and the user has approved the execution plan.
11. [rp-execute-setup](https://skillsdocs.com/wix/skills/rp-execute-setup.md) — Verifies and provisions Wix-side setup required before import. Use after codegen when machine-readable setup artifacts must be validated or executed against th…
12. [rp-import-codegen](https://skillsdocs.com/wix/skills/rp-import-codegen.md) — Generates migration readers, transforms, setup plans, and thin Wix write specs from schema and mapping artifacts. Use when producing runnable extract/import co…
13. [rp-mapper](https://skillsdocs.com/wix/skills/rp-mapper.md) — Maps discovered source entities and fields to Wix targets and documents lossiness. Use when creating machine-readable mapping artifacts and review markdown aft…
14. [rp-setup-discovery](https://skillsdocs.com/wix/skills/rp-setup-discovery.md) — Derives Wix environment prerequisites (apps, collections, schemas) from an approved mapping plan. Use after mapping review and before import code generation.
15. [rp-source-csv](https://skillsdocs.com/wix/skills/rp-source-csv.md) — CSV file source adapter: parse core, vendor fingerprinting (Shopify, WooCommerce, Magento, BigCommerce), layout inference, file-set handling, and read contract…
16. [rp-source-wordpress](https://skillsdocs.com/wix/skills/rp-source-wordpress.md) — WordPress and WooCommerce source adapter: REST capture, auth, pagination, and read contract for codegen. Use when the source platform is WordPress or WooCommer…
17. [rp-target-wix](https://skillsdocs.com/wix/skills/rp-target-wix.md) — Wix target adapter with verified write primitives, shared execution runtimes, and contract tests. Use when vendoring Wix writers, validating API shapes, or Wix…
18. [rp-telemetry](https://skillsdocs.com/wix/skills/rp-telemetry.md) — Always-active telemetry companion for RePlatform migration runs. Records what happened during a run — halts, errors, fidelity losses, API gaps, skill coverage…
19. [replatform](https://skillsdocs.com/wix/skills/wix-replatform.md) — Routes RePlatform source-to-Wix migrations to the next workflow step by inspecting migration project artifacts. Use when starting, continuing, or recovering a…
20. [wix-vibe-headless](https://skillsdocs.com/wix/skills/wix-vibe-headless.md) — Client-only, dependency-free REST scaffolds for connecting an already-built front end (a vibe-coded app, an HTML/JSX/Vite project, a design-tool export) to a l…


## Front matter

_The repository README, verbatim except that relative links are resolved against https://github.com/wix/skills/blob/main/._

# Wix Skills

> ⚠️ **EXPERIMENTAL**: This project is in early development. APIs, skill definitions, and behavior may change without notice. Use at your own risk.

Agent skills for building Wix app extensions, managing Wix business solutions, developing headless sites, and using the Wix Design System with AI agents.

> **Note**: These skills are designed for the **new Wix CLI**. See [About the Wix CLI](https://dev.wix.com/docs/wix-cli/guides/about-the-wix-cli) to learn more. For an overview of how skills work with AI tools, see [About Wix Skills](https://dev.wix.com/docs/api-reference/articles/ai-tools/about-wix-skills).

## Installation

### Claude Code Plugin

In [Claude Code](https://docs.anthropic.com/en/docs/claude-code), run: 

```bash
/plugin marketplace add wix/skills
/plugin install wix@wix
```

### Codex

**Codex App** — [Install the Wix plugin](https://chatgpt.com/plugins/share/b15215ad8e954c96a1108d176d53f572).

**Codex CLI** — run `/plugins`, select **Wix**, and choose **Install Plugin**.

### VS Code Plugin

In VS Code, open the Command Palette (`CMD+SHIFT+P`), select **Chat: Install Plugin From Source**, and enter `https://github.com/wix/skills`.

### Cursor Plugin

Go to **Settings > Rules > New Rule > Add from Github** with `https://github.com/wix/skills.git`.

### Gemini CLI

Install using [Gemini CLI](https://geminicli.com):

```bash
gemini extensions install https://github.com/wix/skills
```

### Skills CLI

Install using [skills CLI](https://github.com/vercel-labs/skills):

```bash
# Install all skills
npx skills add wix/skills

# Install globally
npx skills add wix/skills -g
```

## Available Skills

| Skill                                    | Purpose                          | When to Use                                                                                                                         |
| ---------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [wix-app](https://github.com/wix/skills/blob/main/skills/wix-app/SKILL.md)       | Build Wix app extensions         | Adding any extension — dashboard pages, site widgets, backend events, service plugins, embedded scripts, data collections, and more |
| [wix-design-system](https://github.com/wix/skills/blob/main/skills/wix-design-system/SKILL.md) | Wix Design System reference      | Looking up WDS component props, examples, icons                                                                                     |
| [wix-manage](https://github.com/wix/skills/blob/main/skills/wix-manage/SKILL.md) | Wix business solution management | REST API operations for configuring and managing Wix business solutions                                                             |
| [wix-headless](https://github.com/wix/skills/blob/main/skills/wix-headless/SKILL.md) | Connect Wix business services to a headless frontend (SDK + Wix CLI), and optionally build & host the site | Setting up a Wix Headless backend or adding Wix business features (Stores, Bookings, CMS, Blog, Events, Forms, Members, Restaurants, Portfolio, Pricing Plans) — install apps, seed content, and produce an SDK-integration guide; for managed projects also scaffold a new site (create) or wire an existing design (connect), then build and release. Works across managed, self-managed, and stripe project types |
| [wix-vibe-headless](https://github.com/wix/skills/blob/main/skills/wix-vibe-headless/SKILL.md) | Connect an existing front end to Wix over client-only REST | Wiring a vibe-coded / HTML / Vite app to a live Wix site (storefront, bookings, blog, events, portfolio, restaurants, CMS, pricing plans) from the browser with a public `WIX_CLIENT_ID` — no SDK, no backend |
| [wix-docs](https://github.com/wix/skills/blob/main/skills/wix-docs/SKILL.md) | Look up the Wix API/SDK docs (shared fallback) | Confirming an exact Wix endpoint, method schema, field, or enum before writing code — `curl` doc-search + the `.md`-twin trick, or the Wix MCP tools. Referenced by the other skills as their docs-lookup fallback |
| [replatform](https://github.com/wix/skills/blob/main/replatform/README.md) | Migrate sites from WordPress and other platforms into Wix | Migrating an exiting business from another platform into Wix. Both backend data and website. `npx skills add wix/skills/replatform` |

## Supported Agents

These skills work with any agent that supports the [Agent Skills specification](https://github.com/vercel-labs/add-skill):

- Cursor
- Claude Code
- Gemini CLI
- Codex CLI
- GitHub Copilot
- Windsurf
- And [many more](https://github.com/vercel-labs/add-skill#available-agents)

## Versioning

`@wix/agent-skills` follows semver. Bumps target **AI-generated-code stability** — i.e., whether a change could cause an agent using these skills to produce broken code on the previous-major `wix-cli`:

| Bump | Examples |
| --- | --- |
| **patch** | Wording fix, typo, link update, clarification of existing guidance |
| **minor** | New skill added, new section in an existing skill, additive guidance for a non-breaking `wix-cli` feature |
| **major** | Skill rename/removal, rewrite of guidance for a deprecated `wix-cli` API, anything that would cause AI-generated code to fail on the previous-major `wix-cli` |

When a major bump is required (a breaking change in the underlying `wix-cli`), the previous major continues on a `release/<N>.x` maintenance branch and receives backports for genuine bugs only — no new features.

## Releasing

Run the [`release-bump`](https://github.com/wix/skills/blob/main/.github/workflows/release-bump.yml) workflow from the **Actions** tab and pick a `version_strategy`. The rest is automatic — the bump PR auto-merges once checks pass and [`release.yml`](https://github.com/wix/skills/blob/main/.github/workflows/release.yml) publishes to npm via Trusted Publishing.

## Serving (`www.wix.com/skills`)

Besides the npm package, skills are served as raw markdown / `.tgz` at `https://www.wix.com/skills/<name>` (also `dev.wix.com/skills`), directly from this repo's `main` branch with no build step. Responses are cached, so a merge to `main` reflects there within a few minutes.

## Contributing

See [CONTRIBUTING.md](https://github.com/wix/skills/blob/main/CONTRIBUTING.md) for guidelines on adding new skills.

### Eval Token Budgets

Eval scenarios under `yaml/wix-manage-evals/` may define a top-level `maxTokens` value. The GitHub Actions eval gate compares that budget against the PR run's total tokens for the scenario and fails the PR check when the budget is exceeded. This gate-owned field is separate from `llm_judge.maxTokens`, which only configures the judge assertion.

## License

MIT

---

<!-- chapter:begin slug=writing-great-skills position=1 -->

## 1. writing-great-skills

- **Source:** https://github.com/wix/skills/blob/main/.claude/skills/writing-great-skills/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/.claude/skills/writing-great-skills/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/writing-great-skills.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (2), referenced from this skill's directory:
  - `agents/openai.yaml` — https://raw.githubusercontent.com/wix/skills/main/.claude/skills/writing-great-skills/agents/openai.yaml
  - `GLOSSARY.md` — https://raw.githubusercontent.com/wix/skills/main/.claude/skills/writing-great-skills/GLOSSARY.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: writing-great-skills
description: Reference for writing and editing skills well — the vocabulary and principles that make a skill predictable.
disable-model-invocation: true
---

A skill exists to wrangle determinism out of a stochastic system. **Predictability** — the agent taking the same _process_ every run, not producing the same output — is the root virtue; every lever below serves it.

**Bold terms** are defined in [`GLOSSARY.md`](GLOSSARY.md); look them up there for the full meaning.

## Invocation

Two choices, trading different costs:

- A **model-invoked** skill keeps a **description**, so the agent can fire it autonomously _and_ other skills can reach it (you can still type its name too). It contributes to **context load** — the description sits in the window every turn. Mechanics: omit `disable-model-invocation`, and write a model-facing description with rich trigger phrasing ("Use when the user wants…, mentions…").
- A **user-invoked** skill strips the description from the agent's reach: only you, typing its name, can invoke it — and no other skill can. Zero context load, but it spends **cognitive load**: _you_ are the index that must remember it exists. Mechanics: set `disable-model-invocation: true`; the `description` becomes human-facing — a one-line summary, trigger lists stripped.

Pick model-invocation only when the agent must reach the skill on its own, or another skill must. If it only ever fires by hand, make it user-invoked and pay no context load.

When user-invoked skills multiply past what you can remember, that piled-up cognitive load is cured by a **router skill**: one user-invoked skill that names the others and when to reach for each.

## Writing the description

A model-invoked **description** does two jobs — state what the skill is, and list the **branches** that should trigger it. Every word increases **context load**, so a description earns even harder pruning than the body:

- **Front-load the skill's leading word** — the description is where it does its invocation work.
- **One trigger per branch.** Synonyms that rename a single branch are **duplication** — "build features using TDD … asks for test-first development" is one branch written twice. Collapse them; keep only genuinely distinct branches.
- **Cut identity that's already in the body.** Keep the description to triggers, plus any "when another skill needs…" reach clause.

## Information hierarchy

A skill is built from two content types — **steps** and **reference** — that mix freely: a skill can be all steps, all reference, or both. The core decision is which to use and where each sits on the **information hierarchy**, a ladder ranked by how immediately the agent needs the material:

1. **In-skill step** — an ordered action in `SKILL.md`, the primary tier: what the agent does, in order. Each step ends on a **completion criterion**, the condition that tells the agent the work is done. Make it _checkable_ (can the agent tell done from not-done?) and, where it matters, _exhaustive_ ("every modified model accounted for", not "produce a change list") — a vague criterion invites **premature completion**.
2. **In-skill reference** — a definition, rule, or fact in `SKILL.md`, consulted on demand. Often a legitimately flat peer-set (every rule of a review on one rung) — a fine arrangement, not a smell. _This skill is all reference._
3. **External reference** — reference pushed out of `SKILL.md` into a separate file, reached by a **context pointer**, loaded only when the pointer fires. (Spans _disclosed_ reference — a sibling file like `GLOSSARY.md`, still part of the skill — through fully **external reference** that lives outside the skill system and any skill can point at.)

A demanding completion criterion drives thorough **legwork** — the digging the agent does within the work — whether the skill has steps or not, since "every rule applied" binds flat reference just as "every step done" binds a sequence.

Push too little down and the top bloats; push too much and you hide material the agent actually needs. That tension is the whole decision.

**Progressive disclosure** is the move down the ladder — out of `SKILL.md` into a linked file — so the top stays legible. Mechanics: a linked `.md` file in the skill folder, named for what it holds (this skill discloses its full definitions to `GLOSSARY.md`). Some skills are used in more than one way, and each distinct way is a **branch** — different runs taking different paths through the skill. Branching is the cleanest disclosure test: inline what every branch needs, and push behind a pointer what only some branches reach. A **context pointer**'s _wording_, not its target, decides when and how reliably the agent reaches the material.

Where the ladder decides _how far down_ a piece sits, **co-location** decides _what sits beside it_ once there: keep a concept's definition, rules, and caveats under one heading rather than scattered, so reading one part brings its neighbours with it.

## When to split

**Granularity** is how finely you divide skills, and each cut spends one of the two loads, so split only when the cut earns it. Two cuts:

- **By invocation** — split off a **model-invoked** skill when you have a distinct **leading word** that should trigger it on its own, or another skill must reach it. You pay **context load** for the new always-loaded **description**, so that independent reach has to be worth it.
- **By sequence** — split a run of **steps** when the steps still ahead (a step's **post-completion steps**) tempt the agent to rush the one in front of it (**premature completion**). Keeping them out of view encourages the agent to do more **legwork** on the current task.

## Pruning

Keep each meaning in a **single source of truth**: one authoritative place, so changing the behaviour is a one-place edit.

Check every line for **relevance**: does it still bear on what the skill does?

Then hunt **no-ops** sentence by sentence, not just line by line: run the no-op test on each sentence in isolation, and when one fails, delete the whole sentence rather than trim words from it. Be aggressive — most prose that fails should go, not be rewritten.

## Leading words

A **leading word** is a compact concept already living in the model's pretraining that the agent thinks with while running the skill (e.g. _lesson_, _fog of war_, _tracer bullets_). Repeated throughout the text (though not necessarily - a strong leading word might only be needed once), it accumulates a distributed definition and anchors a whole region of behaviour in the fewest tokens, by recruiting priors the model already holds.

It serves predictability twice. In the body it anchors _execution_: the agent reaches for the same behaviour every time the word appears. In the description it anchors _invocation_: when the same word lives in your prompts, docs, and code, the agent links that shared language to the skill and fires it more reliably.

Hunt for opportunities to refactor skills to use leading words. A triad spelled out at three sites (**duplication**), a description spending a sentence to gesture at one idea — each is a passage begging to **collapse** into a single token. Examples include:

- "fast, deterministic, low-overhead" -> _tight_ — one quality restated across a phase — into a single pretrained word (a _tight_ loop).
- "a loop you believe in" -> _red_ — converts a fuzzy gate into a binary observable state (the loop goes _red_ on the bug, or it doesn't).

You win twice over: fewer tokens, _and_ a sharper hook for the agent to hang its thinking on. Assume every skill is carrying restatements that leading words retire — go find them.

## Failure modes

Use these to diagnose issues the user may be having with the skill.

- **Premature completion** — ending a step before it's genuinely done, attention slipping to _being done_. Defence, in order: sharpen the completion criterion first (cheap, local); only if it is irreducibly fuzzy _and_ you observe the rush, hide the post-completion steps by splitting (the sequence cut).
- **Duplication** — the same meaning in more than one place. Costs maintenance and tokens, and inflates a meaning's prominence on the ladder past its real rank.
- **Sediment** — stale layers that settle because adding feels safe and removing feels risky. The default fate of any skill without a pruning discipline.
- **Sprawl** — a skill simply too long, even when every line is live and unique. Hurts readability and maintainability and wastes tokens. The cure is the ladder: disclose **reference** behind pointers, and split by **branch** or sequence so each path carries only what it needs.
- **No-op** — a line the model already obeys by default, so you pay load to say nothing. The test: does it change behaviour versus the default? A weak leading word (_be thorough_ when the agent is already thorough-ish) is a no-op; the fix is a stronger word (_relentless_), not a different technique.
- **Negation** — steering by prohibition backfires: _don't think of an elephant_ names the elephant and makes it more available, not less. Prompt the **positive** — state the target behaviour so the banned one is never spoken; keep a prohibition only as a hard guardrail you can't phrase positively, and even then pair it with what to do instead.

<!-- chapter:end slug=writing-great-skills -->

---

<!-- chapter:begin slug=wix-app position=2 -->

## 2. wix-app

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-app/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-app.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (90), referenced from this skill's directory:
  - `references/APP_IDENTIFIERS.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/APP_IDENTIFIERS.md
  - `references/APP_MARKET_REVIEW.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/APP_MARKET_REVIEW.md
  - `references/APP_TOOLS.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/APP_TOOLS.md
  - `references/APP_VALIDATION.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/APP_VALIDATION.md
  - `references/app-tools/TOOLS.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/app-tools/TOOLS.md
  - `references/AUTO_PATTERNS_DASHBOARD.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md
  - `references/auto-patterns-dashboard/action-cell.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/action-cell.md
  - `references/auto-patterns-dashboard/app-config-structure.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md
  - `references/auto-patterns-dashboard/app-context.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/app-context.md
  - `references/auto-patterns-dashboard/bulk-actions.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md
  - `references/auto-patterns-dashboard/collection-page-actions.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md
  - `references/auto-patterns-dashboard/collection-page.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/collection-page.md
  - `references/auto-patterns-dashboard/custom-actions-override.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md
  - `references/auto-patterns-dashboard/custom-columns-override.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md
  - `references/auto-patterns-dashboard/custom-components-override.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md
  - `references/auto-patterns-dashboard/custom-header-override.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md
  - `references/auto-patterns-dashboard/custom-sections-override.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md
  - `references/auto-patterns-dashboard/custom-slots-override.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md
  - `references/auto-patterns-dashboard/entity-page-actions.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md
  - `references/auto-patterns-dashboard/entity-page-view-actions.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md
  - `references/auto-patterns-dashboard/entity-page.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/entity-page.md
  - `references/auto-patterns-dashboard/example-patterns.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json
  - `references/auto-patterns-dashboard/pages-configuration.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md
  - `references/auto-patterns-dashboard/resolved-action.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md
  - …and 66 more, listed in https://skillsdocs.com/api/v1/books/wix/skills/skills/wix-app

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-app
description: "Build and review Wix CLI app extensions — dashboard pages, modals, plugins, menu plugins, custom element widgets, Editor React components, site plugins, embedded scripts, backend APIs, backend events, service plugins, data collections, and App Market readiness. Use when building ANY feature or extension for a Wix CLI app or preparing a Wix app for App Market review. Triggers on: add, build, create, implement, help me, dashboard, widget, plugin, backend, API, event, collection, embedded script, service plugin, Editor React component, checkout, shipping, tax, discount, SPI, CMS, schema, tracking, popup, admin panel, menu item, modal, validate, test, verify, register extension, App Market, app review, submission readiness."
compatibility: requires `@wix/cli` >= 1.1.192.
---

# Wix App Builder

Helps build extensions for Wix CLI applications. Covers all extension types: dashboard pages, modals, plugins, menu plugins, custom element widgets, Editor React components, site plugins, embedded scripts, backend APIs, events, service plugins, and data collections.

**Scaffolding is owned by the Wix CLI.** For every extension type except Backend API, files, folders, builder boilerplate, UUIDs, and `src/extensions.ts` registration are generated by `wix generate --params`. This skill provides the decision logic, API guidance, configuration semantics, and business-logic patterns that fill in the generated stubs.

## ⚠️ MANDATORY WORKFLOW CHECKLIST ⚠️

**Before reporting completion to the user, ALL boxes MUST be checked:**

- [ ] **Step 1:** Determined extension type(s) needed
  - [ ] Asked clarifying questions if requirements were unclear
  - [ ] **🛑 Auto-Patterns Gate (MANDATORY):** If the use case is a single-collection CRUD admin page (table/grid + entity form), you MUST use [AUTO_PATTERNS_DASHBOARD.md](references/AUTO_PATTERNS_DASHBOARD.md). Auto-patterns is the DEFAULT; custom Dashboard Pages are the opt-out. Only fall back to Dashboard Page if a disqualifier applies: multi-collection joins, custom business logic, embedded script configuration, external API integrations, or the user explicitly requested a custom React page.
  - [ ] **🛑 Iteration Gate (MANDATORY):** Before editing ANY file under `src/extensions/dashboard/pages/<page>/`, check for a sibling `patterns.json`. If it exists, this is an auto-patterns page — you MUST follow [AUTO_PATTERNS_DASHBOARD.md](references/AUTO_PATTERNS_DASHBOARD.md) Part B and use the override topic-index (banners → `custom-slots-override.md`, header → `custom-header-override.md`, actions → `custom-actions-override.md`, columns → `custom-columns-override.md`, sections → `custom-sections-override.md`). Do NOT edit the page component (`<page-name>.tsx`) to add UI elements directly.
  - [ ] Checked for implicit Data Collection need — unless user provided a collection ID directly (see [Data Collection Inference](#data-collection-inference))
  - [ ] Obtained app namespace if Data Collection extension is being created
  - [ ] Determined full scoped collection IDs if Data Collection extension is being created (see [Collection ID Coordination](#collection-id-coordination))
  - [ ] Explained recommendation with reasoning
- [ ] **Step 2:** Read extension reference file(s) for the chosen type(s) and the project-wide [CODE_QUALITY.md](references/CODE_QUALITY.md)
- [ ] **Step 3:** Checked API references; used MCP discovery only for gaps
- [ ] **Step 4a:** Scaffolded each CLI-supported extension via `wix generate --params`
- [ ] **Step 4b:** Filled in business logic in the generated files
  - [ ] Invoked `wix-design-system` skill ONLY before editing the first `.tsx`/`.jsx` file that imports `@wix/design-system`. Skip for backend-only or data-only extensions.
  - [ ] WDS: imported `@wix/design-system/styles.global.css` in the main component entry file (`page.tsx`, modal `.tsx`, etc.) — not child/tab/helper files.
- [ ] **Step 5:** Ran validation (see [Validation](#validation))
  - [ ] Dependencies installed
  - [ ] TypeScript compiled
  - [ ] Build succeeded
  - [ ] Preview deployed
- [ ] **Step 6:** Collected and presented ALL manual action items to user

**🛑 STOP:** If any box is unchecked, do NOT proceed to the next step.

---

## ❌ ANTI-PATTERNS (DO NOT DO)

| ❌ WRONG                                    | ✅ CORRECT                                     |
| ------------------------------------------- | ---------------------------------------------- |
| Hand-writing builder files, folders, UUIDs, or extension registration | Run `wix generate --params` — it owns scaffolding |
| Implementing without reading the extension reference | Always read the relevant reference file first |
| Using MCP discovery without checking refs   | Check reference files first                    |
| Reporting done without validation           | Always run validation at the end               |
| Letting manual action items get buried      | Aggregate all manual steps at the very end     |
| Writing custom React for single-collection CRUD when auto-patterns applies | Default to [AUTO_PATTERNS_DASHBOARD.md](references/AUTO_PATTERNS_DASHBOARD.md) for CRUD admin pages |
| Editing the page component (`<page-name>.tsx`) to add UI (banners, headers, custom actions, slots, sections) when `patterns.json` exists | Use the matching `custom-*-override.md` from [AUTO_PATTERNS_DASHBOARD.md](references/AUTO_PATTERNS_DASHBOARD.md) Part B |

---

## Quick Decision Helper

1. **What are you trying to build?**
   - Admin interface → Dashboard Extensions
   - Backend logic → Backend Extensions
   - Data storage / CMS collections → Data Collection
   - Editor React component → Site Extensions (app projects only)

2. **Who will see it?**
   - Admin users only → Dashboard Extensions
   - Site visitors → Site Extensions
   - Server-side only → Backend Extensions

3. **Where will it appear?**
   - Dashboard sidebar/page →
     - **Single-collection CRUD admin (default, MANDATORY):** [Auto Patterns Dashboard](references/AUTO_PATTERNS_DASHBOARD.md) — declarative `patterns.json`, faster to author, iterate by editing JSON, no React rewrite.
     - Custom logic / multi-collection / embedded scripts / external APIs: Dashboard Page
     - Popup/form: Dashboard Modal
   - Existing Wix app dashboard (widget) → Dashboard Plugin
   - Existing Wix app dashboard (menu item) → Dashboard Menu Plugin
   - Anywhere on site → custom element widget
   - Anywhere on site (with editor manifest) → Editor React component
   - Wix business solution page → Site Plugin
   - During business flow → Service Plugin
   - Exposing tools to the Wix AI assistant → App Tools
   - After event occurs → Backend Event Extension

## Decision Flow (Not sure?)

- **Admin:** Single-collection CRUD admin page? → **Auto Patterns Dashboard (DEFAULT)**. Custom React page (multi-collection / custom logic / embedded scripts / external APIs / explicit user request)? → Dashboard Page. Need popup/form? → Dashboard Modal. Extending Wix app dashboard with a visual widget? → Dashboard Plugin. Adding a menu item to a Wix app dashboard's more-actions or bulk-actions menu? → Dashboard Menu Plugin. **Modal constraint:** Dashboard Pages cannot use `<Modal />`; use a separate Dashboard Modal extension and `dashboard.openModal()`.
- **Backend:** During business flow (checkout/shipping/tax)? → Service Plugin. Exposing tools to the Wix AI assistant? → App Tools (requires both `APP_TOOLS` declaration + `TOOLS_PROVIDER_CONFIG` handler — see [APP_TOOLS.md](references/APP_TOOLS.md)). After event (webhooks/sync)? → Backend Event Extension. Custom HTTP endpoints? → Backend API. Need CMS collections for app data? → Data Collection.
- **Site:** User places anywhere (standalone)? → custom element widget. Editor React component with editor manifest (styling, content, elements)? → Editor React component. Fixed slot on Wix app page? → Site Plugin. Scripts/analytics only? → Embedded Script.

---

## Extension Types Reference Table

| Extension Type | Category | `extensionType` (for `wix generate --params`) | Reference File |
| --- | --- | --- | --- |
| Dashboard Page | Dashboard | `DASHBOARD_PAGE` | [DASHBOARD_PAGE.md](references/DASHBOARD_PAGE.md) |
| Dashboard Modal | Dashboard | `DASHBOARD_MODAL` | [DASHBOARD_MODAL.md](references/DASHBOARD_MODAL.md) |
| Dashboard Plugin | Dashboard | `DASHBOARD_PLUGIN` | [DASHBOARD_PLUGIN.md](references/DASHBOARD_PLUGIN.md) |
| Dashboard Menu Plugin | Dashboard | `DASHBOARD_MENU_PLUGIN` | [DASHBOARD_MENU_PLUGIN.md](references/DASHBOARD_MENU_PLUGIN.md) |
| Service Plugin | Backend | `SERVICE_PLUGIN` | [SERVICE_PLUGIN.md](references/SERVICE_PLUGIN.md) |
| App Tools (AI assistant tools) | Backend | `APP_TOOLS`, then `SERVICE_PLUGIN` with `pluginType: TOOLS_PROVIDER_CONFIG` | [APP_TOOLS.md](references/APP_TOOLS.md) |
| Backend Event Extension | Backend | `EVENT` | [BACKEND_EVENT.md](references/BACKEND_EVENT.md) |
| Backend API | Backend | — (manual, see banner below) | [BACKEND_API.md](references/BACKEND_API.md) |
| Data Collection | Backend | `DATA_COLLECTION` | [DATA_COLLECTION.md](references/DATA_COLLECTION.md) |
| Editor React component | Site | `EDITOR_REACT_COMPONENT` | [EDITOR_REACT_COMPONENT.md](references/EDITOR_REACT_COMPONENT.md) |
| Custom element widget | Site | `CUSTOM_ELEMENT` | [CUSTOM_ELEMENT_WIDGET.md](references/CUSTOM_ELEMENT_WIDGET.md) |
| Site Plugin | Site | `SITE_PLUGIN` | [SITE_PLUGIN.md](references/SITE_PLUGIN.md) |
| Embedded Script | Site | `EMBEDDED_SCRIPT` | [EMBEDDED_SCRIPT.md](references/EMBEDDED_SCRIPT.md) |

**Key constraints:**
- Dashboard Page cannot use `<Modal />`; use a separate Dashboard Modal and `dashboard.openModal()`.

> **⚠️ Backend API is the only extension type the CLI does NOT scaffold.** `wix generate` has no `BACKEND_API` handler. Create files directly per [BACKEND_API.md](references/BACKEND_API.md).

## Extension Comparison

| Custom element widget vs Editor React component vs Site Plugin | Dashboard Page vs Modal | Service Plugin vs Event |
| -------------------------------------------------------------- | ----------------------- | ----------------------- |
| Custom element widget: standalone interactive component. Editor React component: React with editor manifest (CSS/data/elements). Plugin: fixed slot in Wix app page. | Page: full page. Modal: overlay; use for popups. | Service: during flow. Event: after event. |

---

## Cross-Cutting References

| Topic | Reference |
| --- | --- |
| Code Quality Requirements (applies to all generated code) | [CODE_QUALITY.md](references/CODE_QUALITY.md) |
| Extension Registration | [EXTENSION_REGISTRATION.md](references/EXTENSION_REGISTRATION.md) |
| App Validation | [APP_VALIDATION.md](references/APP_VALIDATION.md) |
| App Market Review | [APP_MARKET_REVIEW.md](references/APP_MARKET_REVIEW.md) |
| App Identifiers (Namespace, Code ID) | [APP_IDENTIFIERS.md](references/APP_IDENTIFIERS.md) |
| Wix Stores Versioning (V1/V3) | [STORES_VERSIONING.md](references/STORES_VERSIONING.md) |
| Official Documentation Links | [DOCUMENTATION.md](references/DOCUMENTATION.md) |
| Auto-patterns Dashboard Pages | [AUTO_PATTERNS_DASHBOARD.md](references/AUTO_PATTERNS_DASHBOARD.md) |

---

## Data Collection Inference

**CRITICAL:** Data collections are often needed implicitly — don't wait for the user to explicitly say "create a CMS collection." Infer the need automatically.

**Skip this section if the user provides a collection ID directly** (e.g., an existing site-level collection). In that case, use the provided ID as-is — no Data Collection extension or namespace scoping needed.

**Always include a Data Collection extension when ANY of these are true:**

| Indicator | Example |
| --- | --- |
| User mentions saving/storing/persisting app-specific data | "save the fee amount", "store product recommendations" |
| A dashboard page will **manage** (CRUD) domain entities | "dashboard to manage fees", "admin page to edit rules" |
| A service plugin reads app-configured data at runtime | "fetch fee rules at checkout", "look up shipping rates" |
| User mentions "dedicated database/collection" | "save in a dedicated database collection" |
| Multiple extensions reference the same custom data | Dashboard manages fees + service plugin reads fees |

**Why this matters:** Without the Data Collection extension, the collection won't be created when the app is installed, the Wix Data APIs may not work (code editor not enabled), and collection IDs won't be properly scoped to the app namespace.

**If data collection is inferred, follow the [App Namespace Requirement](#app-namespace-requirement) to obtain the namespace before proceeding.**

### App Namespace Requirement

When creating a Data Collection, you MUST ask the user for their app namespace from Wix Dev Center. This is a required parameter that must be obtained from the user's Dev Center dashboard and cannot be recommended or guessed.

If the user hasn't provided their app namespace, read [APP_IDENTIFIERS.md](references/APP_IDENTIFIERS.md) and give the user the instructions to obtain it.

### Collection ID Coordination

**Applies ONLY when a Data Collection extension is being created.** If the user provides a collection ID directly, use it as-is — no namespace scoping, no Data Collection extension needed.

When a Data Collection is created alongside other extensions that reference the same collections:

1. **Get the app namespace** (see App Namespace Requirement above)
2. **Determine the `idSuffix`** for each collection (the Data Collection reference documents the full ID format)
3. **Use the full scoped collection ID** (`<app-namespace>/<idSuffix>`) in all extensions that reference the collection via Wix Data API calls

---

## Wix Stores Versioning Requirement

**Applies when ANY Wix Stores API is used** (products, inventory, orders, etc.):

1. **Read the Stores Versioning reference** — see [STORES_VERSIONING.md](references/STORES_VERSIONING.md). It contains the module map, permissions cheatsheet, copy-paste dual-catalog recipes (list/get/create/update/delete products, inventory, categories), the V1→V3 field map, webhook mapping, and the major V3 gotchas. **Use it before searching SDK docs** — it covers the common 80%.
2. **All Stores operations must check catalog version first** using `getCatalogVersion()`
3. **Use the correct module** based on version: `productsV3` (V3) vs `products` (V1)
4. **Apps MUST support both V1 and V3** — single-version apps cannot list in the App Market and break on new sites
5. **Request both V1 and V3 permission scopes** for every Stores operation

This is non-negotiable — V1 and V3 are NOT backwards compatible.

---

## App Market Review

**Applies when a user wants to submit their app to the Wix App Market, list it publicly, prepare for App Market review, audit decline risk, or fix App Market review feedback.** Not needed for private apps or routine version releases.

Read [APP_MARKET_REVIEW.md](references/APP_MARKET_REVIEW.md) — it contains the full technical checklist, implementation notes with Wix doc links, and the review taxonomy IDs for traceability.

---

## Implementation Workflow

### Step 1: Ask Clarifying Questions (if needed)

Only ask for configuration values when **absolutely necessary** for the implementation to proceed. If a value can be configured later or added as a manual step, don't block on it.

If unclear on approach (placement, visibility, configuration, integration), ask clarifying questions. If the answer could change the extension type, wait for the response before proceeding. Otherwise, proceed with the best-fit extension type.

### Step 2: Make Your Recommendation

Use the Extension Types Reference Table and decision content above. State extension type and brief reasoning (placement, functionality, integration).

### Step 3: Read Extension Reference, Check API References, Then Discover (if needed)

**Workflow: Read extension reference → Check API references → Use MCP only for gaps.**

1. **Read the extension reference file** for the chosen extension type from the table above
2. **Identify required APIs** from user requirements
3. **Check relevant API reference files:**
   - Backend events → `references/backend-event/COMMON-EVENTS.md`
   - Wix Data → `references/data-collection/WIX_DATA.md`
   - Dashboard SDK → `references/dashboard-page/DASHBOARD_API.md`
   - Service Plugin SPIs → read `references/SERVICE_PLUGIN.md` together with the matching `references/service-plugin/<NAME>.md` leaf
   - App Tools (AI assistant tools) → read `references/APP_TOOLS.md`; it links to `references/app-tools/TOOLS.md` (declaration) and `references/service-plugin/TOOLS_PROVIDER.md` (handler)
4. **Verify the specific method/event exists** in references
5. **ONLY use MCP discovery if NOT found** in reference files

**Platform APIs (never discover - in references):**
- Wix Data, Dashboard SDK, Event SDK (common events), Service Plugin SPIs

**Vertical APIs (discover if needed):**
- Wix Stores (**⚠️ MUST use Stores Versioning reference** — V1/V3 catalog check required), Wix Bookings, Wix Members, Wix Pricing Plans, third-party integrations

**Decision table:**

| User Requirement                     | Check References / Discovery Needed? | Reason / Reference File                             |
| ------------------------------------ | ------------------------------------ | --------------------------------------------------- |
| "Display store products"             | ✅ YES (MCP discovery)               | Wix Stores API — **include Stores Versioning reference** |
| "Show booking calendar"              | ✅ YES (MCP discovery)               | Wix Bookings API not in reference files             |
| "Send emails to users"               | ✅ YES (MCP discovery)               | Wix Triggered Emails not in reference files         |
| "Get member info"                    | ✅ YES (MCP discovery)               | Wix Members API not in reference files              |
| "Listen for cart events"             | Check `COMMON-EVENTS.md`             | MCP discovery only if event missing in reference    |
| "Store data in collection"           | WIX_DATA.md ✅ Found                 | ❌ Skip discovery (covered by reference)             |
| "Create CMS collections for my app"  | Data Collection reference            | ❌ Skip discovery (covered by dedicated reference)   |
| "Show dashboard toast"               | DASHBOARD_API.md ✅ Found            | ❌ Skip discovery                                   |
| "Show toast / navigate"              | DASHBOARD_API.md ✅ Found            | ❌ Skip discovery                                   |
| "UI only (forms, inputs)"            | N/A (no external API)                | ❌ Skip discovery                                   |
| "Settings page with form inputs"     | N/A (UI only, no external API)       | ❌ Skip discovery                                   |
| "Dashboard page with local state"    | N/A (no external API)                | ❌ Skip discovery                                   |

**MCP Tools for discovery (when needed):**

- `SearchWixSDKDocumentation` - SDK methods and APIs (**Always use maxResults: 5**)
- `ReadFullDocsMethodSchema` - Full type schema for a specific SDK method (parameters, return type, permissions)
- `ReadFullDocsArticle` - Prose guides and conceptual articles only (not for SDK method signatures)

### Step 4a: Scaffold via the CLI

For each extension **except Backend API**, run `npx wix generate --params '<json>'`. The command returns `{"success":true,"extensionType":"...","newFiles":[...]}` on success.

If the command fails because of unknown or invalid params, run `npx wix schema generate --type <extensionType>` to print the JSON Schema for that extension type, fix the `--params` payload, and retry. Do not fall back to manual scaffolding.

**What the CLI does automatically:**
- Creates folders and stub files
- Generates a fresh UUID for the extension `id`
- Updates `src/extensions.ts` with the import and `.use()` call
- Enforces naming rules (kebab-case, hyphen-required custom elements, etc.)

**Backend API exception:** Create `src/pages/api/*.ts` files manually per [BACKEND_API.md](references/BACKEND_API.md).

### Step 4b: Fill in business logic

Open every path returned in `newFiles` and replace stubbed handler bodies / UI / queries with the user's actual logic, guided by the extension reference file's API and configuration sections.

- ⚠️ MANDATORY when using WDS: Invoke the `wix-design-system` skill **before editing your first `.tsx`/`.jsx` file that imports `@wix/design-system`**. Do NOT invoke it preemptively for backend-only or data-only jobs — it adds large content to context that you won't use.
- ⚠️ MANDATORY when using WDS: Add `import "@wix/design-system/styles.global.css";` in the **main component** entry file (`page.tsx`, modal `.tsx`, etc.) — not in child/tab/helper files.
- ⚠️ MANDATORY when using Data Collections: Use the EXACT collection ID from `idSuffix` (case-sensitive). If `idSuffix` is `"product-recommendations"`, use `<app-namespace>/product-recommendations` NOT `productRecommendations`.

### Step 5: Run Validation

After all implementation is complete, you MUST run validation. See [APP_VALIDATION.md](references/APP_VALIDATION.md) for the complete validation workflow:

1. Package installation (detect package manager, run install)
2. TypeScript compilation check (`npx tsc --noEmit`)
3. Build validation (`npx wix build`)
4. Preview deployment (`npx wix preview`)

**Do NOT report completion to the user until validation passes.**

If validation fails, fix the errors and re-validate until it passes.

### Step 6: Report Completion

Only after validation passes, provide a **concise summary section** at the top of your response:

```markdown
## ✅ Implementation Complete

[1-2 sentence description of what was built]

**Extensions Created:**
- [Extension 1 Name] - [Brief purpose]
- [Extension 2 Name] - [Brief purpose]

**Build Status:**
- ✅ Dependencies: [Installed / status message]
- ✅ TypeScript: [No compilation errors / status]
- ✅ Build: [Completed successfully / status]
- ✅/⚠️ Preview: [Running at URL / Failed - reason]

**⚠️ IMPORTANT: [X] manual step(s) required to complete setup** (see "Manual Steps Required" section below)
```

- If there are NO manual steps, state: "✅ No manual steps required — you're ready to go!"

### Step 7: Surface Manual Action Items

Present any manual steps the user must perform (e.g., configuring settings in the Wix dashboard, enabling permissions, setting up external services).

**Format:**

```markdown
## 🔧 Manual Steps Required

The following actions need to be done manually by you:

### 1. [Action Category/Title]
[Detailed description with specific instructions]

### 2. [Action Category/Title]
[Detailed description]
```

---

## Extension Registration

`wix generate --params` updates `src/extensions.ts` automatically for every CLI-supported extension type. The only case that still requires manual editing is Backend API. For background, troubleshooting, and the manual recovery pattern when `src/extensions.ts` drifts, see [EXTENSION_REGISTRATION.md](references/EXTENSION_REGISTRATION.md).

---

## Validation

Execute these steps sequentially after all implementation is complete. See [APP_VALIDATION.md](references/APP_VALIDATION.md) for the complete guide.

1. **Package Installation** — Detect package manager, run install
2. **TypeScript Compilation** — `npx tsc --noEmit`
3. **Build** — `npx wix build`
4. **Preview** — `npx wix preview`

Stop and report errors if any step fails. Check `.wix/debug.log` on failures.

---

## Cost Optimization

- **Let the CLI scaffold** — don't burn tokens describing folder layouts or builder boilerplate
- **Only run `wix schema generate --type <extensionType>`** when `wix generate --params` fails — don't pre-fetch it
- **Read extension reference first** — always read the relevant extension reference file before implementing
- **Check API references first** — read relevant API reference files before using MCP discovery
- **Skip discovery** when all required APIs are in reference files
- **maxResults: 5** for all MCP SDK searches
- **ReadFullDocsMethodSchema** for SDK method schemas; **ReadFullDocsArticle** for prose guides only
- **Invoke wix-design-system** first when using WDS (prevents import errors)

## Documentation

For links to official Wix CLI documentation for all extension types, see [DOCUMENTATION.md](references/DOCUMENTATION.md).

<!-- chapter:end slug=wix-app -->

---

<!-- chapter:begin slug=wix-auth position=3 -->

## 3. wix-auth

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-auth/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-auth/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-auth.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (1), referenced from this skill's directory:
  - `references/device-flow.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-auth/references/device-flow.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-auth
description: "Authenticate with Wix to obtain an access token for calling Wix APIs. Use when an agent needs a valid Wix access token and has none, or when the stored token is expired."
---

# Wix Auth

Wix supports multiple authentication flows. This skill currently covers:

- [Device Flow](references/device-flow.md) — interactive login via browser, no client secret required. Use when the agent is acting on behalf of a logged-in Wix user.

Other flows (OAuth authorization code, API key) are documented at [https://www.wix.com/skills/docs](https://www.wix.com/skills/docs).

<!-- chapter:end slug=wix-auth -->

---

<!-- chapter:begin slug=wix-design-system position=4 -->

## 4. wix-design-system

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-design-system/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-design-system/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-design-system.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (2), referenced from this skill's directory:
  - `references/file-structure.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-design-system/references/file-structure.md
  - `scripts/wds.cjs` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-design-system/scripts/wds.cjs

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-design-system
description: Wix Design System component reference. Use when building UI with @wix/design-system, choosing components, checking props and examples, or writing tests with component testkits. Triggers on "what component", "how do I make", "WDS", "show me props", "testkit", "driver", or component names like Button, Card, Modal, Box, Text.
metadata:
  verified: true
---

# WDS Documentation Navigator

**Prerequisite:** `@wix/design-system` must be installed (`npm i @wix/design-system` or `yarn add @wix/design-system`).

## Helper Script

This skill bundles `scripts/wds.cjs` — a Node.js helper that auto-discovers `@wix/design-system` in node_modules (handles monorepos and workspaces) and provides focused lookups. Run it from the user's project directory using the absolute path to the bundled script:

```bash
# WDS is the absolute path to this skill's scripts/wds.cjs
WDS="<this-skill-dir>/scripts/wds.cjs"

node $WDS search <keyword>                 # Find components by keyword
node $WDS component <Name>                 # Get props + example list (one component)
node $WDS components <Name1> <Name2>...    # Same as `component`, but for several at once
node $WDS example <Name> "<ExampleName>"   # Get a specific example
node $WDS testkit <Name> [method]          # Get testkit imports + driver API
node $WDS icons <query>                    # Search for icons
```

## Workflow

### Step 1: Find the right component

```bash
node $WDS search table
node $WDS search form input validation
node $WDS search modal dialog popup
```

Multiple keywords are OR-matched. Returns component names, descriptions, and usage guidance.

### Step 2: Get props and available examples

```bash
node $WDS component Button
```

Returns the full props list (types and descriptions) plus a list of all available examples. For large prop files (>200 lines), returns a summary with prop names and types.

If you already know which several components you'll need (e.g. after Step 1 returned a shortlist), prefer the batch form to avoid one round-trip per component:

```bash
node $WDS components Button Card Table Input Text Thumbnail
```

Output is each component's props block separated by `---`. Missing components are logged to stderr and skipped; the command only fails if every requested component is missing.

### Step 3: Get a specific example

```bash
node $WDS example Button "Loading state"
```

Returns the example description and JSX code. Matching is case-insensitive and supports substrings (e.g., "loading" matches "Loading state").

### Step 4: Write tests with the component testkit

```bash
node $WDS testkit Button             # Imports + full driver API for Button
node $WDS testkit Button click       # Just the click() method details
```

Returns import snippets for unidriver, vanilla, puppeteer, and playwright flavors plus the driver method API (name, args, return type, description). Method name matching is case-insensitive substring.

### Step 5: Find icons

```bash
node $WDS icons Add Edit Delete
```

Icons are from `@wix/wix-ui-icons-common`. Each icon has a `Small` variant (e.g., `Add` + `AddSmall`).

## Fallback: Direct File Access

If the script is unavailable, docs are at `node_modules/@wix/design-system/dist/docs/`:

- `components.md` — component catalog (~978 lines, grep only)
- `components/{Name}Props.md` — props per component
- `components/{Name}Examples.md` — examples per component (grep `^### ` for section list)
- `components/{Name}Testkit.md` — testkit imports + driver API per component (grep `^### ` for method list)
- `testkits.md` — testkit catalog (list of components with generated testkit docs)
- `icons.md` — icon catalog (~818 lines, grep only)

Don't read these files fully. Grep for keywords, then read specific sections with offset/limit. See [references/file-structure.md](references/file-structure.md) for the exact docs file layout and section shapes.

---

## Quick Component Mapping (Design to WDS)

| Design Element | WDS Component | Notes |
| --- | --- | --- |
| Rectangle/container | `<Box>` | Layout wrapper |
| Text button | `<TextButton>` | Secondary actions |
| Input with label | `<FormField>` + `<Input>` | Wrap inputs |
| Toggle | `<ToggleSwitch>` | On/off settings |
| Modal | `<Modal>` + `<CustomModalLayout>` | Use together |
| Grid | `<Layout>` + `<Cell>` | Responsive |

## Spacing (px to SP conversion)

When designer specifies pixels, convert to the nearest SP token:

| Token | Classic | Studio |
| --- | --- | --- |
| `SP1` | 6px | 4px |
| `SP2` | 12px | 8px |
| `SP3` | 18px | 12px |
| `SP4` | 24px | 16px |
| `SP5` | 30px | 20px |
| `SP6` | 36px | 24px |

```tsx
<Box gap="SP2" padding="SP3">
```

Only use SP tokens for `gap`, `padding`, `margin` — not for width/height.

## Imports

```tsx
import { Button, Card, Image } from "@wix/design-system";
import { Add, Edit, Delete } from "@wix/wix-ui-icons-common";
```

### Troubleshooting

- **Components render unstyled** (plain HTML look, missing WDS spacing/typography): add `import "@wix/design-system/styles.global.css";` once to the root/main component (e.g. `page.tsx`, modal entry) — not child/tab/helper files.

<!-- chapter:end slug=wix-design-system -->

---

<!-- chapter:begin slug=wix-docs position=5 -->

## 5. wix-docs

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-docs/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-docs/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-docs.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (2), referenced from this skill's directory:
  - `references/API_SPEC_SEARCH.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-docs/references/API_SPEC_SEARCH.md
  - `references/EXTRACTING.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-docs/references/EXTRACTING.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-docs
description: "Look up the Wix API/SDK documentation to confirm an exact endpoint, HTTP method, request/response shape, field, enum, or error before writing Wix code — never guess a Wix API from memory. A lookup is a short flow: find the right page, then read it. Two ways: (1) plain `curl` (zero dependencies) — find a page by **semantic search** (`POST /mcp-docs-search/v1/docs/search`, natural-language `{ search_term, document_type }`) **or by browsing** the docs tree as a menu — a structured, typed, counted browse of the REST API reference (`POST /mcp-docs-search/v1/docs/menu/browse`), or the `.md` menu tree from the `llms.txt` root for any portal — then read the page by appending `.md` to its URL; and (2) the Wix MCP doc tools when your agent has them. Triggers: look up a Wix API, find the Wix endpoint/method, confirm a Wix request body or field, verify a Wix API shape, explore Wix docs, which Wix API do I call, read a Wix method schema."
---

# Wix Docs — look up the Wix API/SDK documentation

Get the **exact** truth about a Wix API — endpoint, HTTP method, request/response body, a field, an
enum, or an error. **Never invent a Wix endpoint, path, body, or enum from memory** — confirm it
here first.

A lookup is a short flow: **find the right page, then read it.** Do it with `curl` (default, below)
or the Wix MCP doc tools if your agent has them (Lane 2).

## Lane 1 — `curl` (default)

The docs are one tree of markdown pages: **append `.md` to any `https://dev.wix.com/docs/…` URL**
to get that page as markdown. No SDK, no MCP.

### 1. Find the page — search, browse, or query the index

Three ways to reach the right page — use whichever fits.

**A. Semantic search.** Describe what you want in natural language ("let a customer book an
appointment"), not just keywords; hits come back ranked by relevance. Same `POST` body for both
variants: `search_term` (required, 1–500), `document_type` (`REST` default · `SDK` · `WIX_HEADLESS` ·
`BUSINESS_SOLUTIONS` · `VELO` · `WDS` · `BUILD_APPS` · `CLI`), `maximum_results` (1–20, def 15),
`lines_in_each_result` (1–200, def 20). Two variants — pick by what you're doing:

**`/docs/search/markdown` → read it (start here).** Returns JSON with a single `content` field
holding one LLM-ready markdown string (extract it with `jq -r '.content'`) where each hit is a
**condensed method doc**: the API **endpoint**, **real request code examples**, the **response
shape**, and the **method description** (with its gotchas) — each truncated to `lines_in_each_result`
with a "read more" link. For *"how do I call X?"* this is usually all you need in **one call** — hand
it straight to the model; no page fetch, no schema dig.

```bash
curl -sS -X POST 'https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown' \
  -H 'Content-Type: application/json' \
  --data-raw '{"search_term":"create a booking","document_type":"REST","maximum_results":3}' \
  | jq -r '.content'      # no jq? → python3 -c 'import sys,json;print(json.load(sys.stdin)["content"])'
```

**`/docs/search` (JSON) → route on it.** Returns `{ results: [ { title, url, content,
relevance_score, … } ] }` — structured hits. Use it when you want to **pick/route programmatically**:
grab a hit's `url` to read that page (§2) or feed it to the schema query (§C). (Method hits carry a
`url`; article hits keep their link inside `content`.)

```bash
curl -sS -X POST 'https://www.wixapis.com/mcp-docs-search/v1/docs/search' \
  -H 'Content-Type: application/json' \
  --data-raw '{"search_term":"create a booking","document_type":"REST","maximum_results":5}' \
  | jq -r '.results[] | select(.url) | "\(.title)\t\(.url)"'
# no jq? → python3 -c 'import sys,json;[print(r["title"],r["url"]) for r in json.load(sys.stdin)["results"] if r.get("url")]'
```

**B. Browse the docs tree as a menu.** Two ways: the **structured browse endpoint** for the REST
API reference (preferred there — typed, counted, filterable), and the **`.md` menu tree** for every
portal and for reading pages.

**B1. Structured browse — REST API reference (`api-reference`).** `POST
/mcp-docs-search/v1/docs/menu/browse` walks the tree and returns each child with its **kind**, its
**HTTP verb** (for methods), and **subtree counts** ("Catalog V3 — 121 methods, 32 articles"), so
you pick the right area by shape — in ~2 KB, not a ~40 KB menu page you have to `grep`. `include`,
`name_filter`, and `depth` jump straight to what you want. Body: `menu_url?` (absolute docs URL;
omit for the portal root — the top-level verticals), `document_type?` (`REST`, default), `depth?`
(1, max 6), `include?` (`CATEGORY`·`RESOURCE`·`METHOD`·`ARTICLE`·`WEBHOOK`·`OBJECT`·`SKILL`),
`deprecated?` (`HIDE` default·`SHOW`·`ONLY`), `name_filter?`, `format?` (`MARKDOWN` default →
`content` string; `STRUCTURED` → JSON tree with `url`/`http_method`/`resource_id`/`child_counts`).

```bash
# a vertical's structure, with per-child subtree counts
curl -sS -X POST 'https://www.wixapis.com/mcp-docs-search/v1/docs/menu/browse' \
  -H 'Content-Type: application/json' \
  --data-raw '{"menu_url":"https://dev.wix.com/docs/api-reference/business-solutions/stores"}' \
  | jq -r '.content'

# jump straight to a method by name — no multi-level grep
curl -sS -X POST 'https://www.wixapis.com/mcp-docs-search/v1/docs/menu/browse' \
  -H 'Content-Type: application/json' \
  --data-raw '{"menu_url":"https://dev.wix.com/docs/api-reference/business-solutions/bookings","include":["METHOD"],"name_filter":"cancel","depth":4}' \
  | jq -r '.content'
```

REST (`api-reference`) only, and browse-only: it hands you the page **URL** — read it by appending
`.md` (§2), and get the exact schema from §C.

**B2. `.md` menu tree — every portal, and how you read pages.** Every docs path has a `.md` twin, so
you can navigate any portal with zero dependencies; use it for the **non-REST portals** (SDK, Velo,
Headless, CLI) and to read leaves. `curl https://dev.wix.com/docs/llms.txt` is the
top-level map; the portals under it:

| Portal | Start here for |
|---|---|
| [`api-reference.md`](https://dev.wix.com/docs/api-reference.md) | **All backend / business-solution APIs — the main one.** Each page documents **both** its REST and SDK usage (`.md?apiView=SDK` for the SDK view). |
| [`sdk.md`](https://dev.wix.com/docs/sdk.md) | **SDK-only surfaces not in the API reference:** client setup (`createClient`, `OAuthStrategy`), core modules (`@wix/sdk`, `@wix/essentials`), host modules (`dashboard`/`editor`/`site`), and frontend modules (`members`, `pay`, `seo`, `storage`, `pricing-plans`, …). |
| [`go-headless.md`](https://dev.wix.com/docs/go-headless.md) | Headless setup, auth, hosting, framework integration. |
| [`build-apps.md`](https://dev.wix.com/docs/build-apps.md) | Building Wix apps / extensions. |
| [`wix-cli.md`](https://dev.wix.com/docs/wix-cli.md) · [`velo.md`](https://dev.wix.com/docs/velo.md) | Wix CLI commands; Velo site-coding APIs. |

**Drill like a menu** — append `.md` to any path (a *section* → a menu of child links, a *leaf* →
the content/method page); truncate to go up, extend to go down. **Read the sibling intro / "About …"
/ flow articles too**, not just the method page. Example — drill to the create-booking method,
grepping each menu for the next link:

```bash
curl -sS https://dev.wix.com/docs/api-reference/business-solutions.md            | grep -i bookings   # → .../bookings.md
curl -sS https://dev.wix.com/docs/api-reference/business-solutions/bookings.md   | grep -iE 'bookings|flow'  # → resource/flow pages
curl -sS https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings.md | grep -i create      # → the create method leaf
curl -sS https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking.md  # read it
```

A 2-level map of the API-reference portal (all verticals, one level down) is in
`references/EXTRACTING.md`.

**C. Query the API index — one call, structured.** The `code-mode` search endpoint runs a JS
function over `lightIndex` (the whole REST API spec: every resource + method with `operationId`,
`httpMethod`, `menuPath`, `docsUrl`, and executable `publicUrl`). Best when you want to
**enumerate/filter methods programmatically** — browse a vertical, or grep across *all* methods —
and get the `docsUrl` + `publicUrl` back in one shot, no menu-drilling:

```bash
# pinpoint a method by keyword across the whole index → its docsUrl + executable publicUrl
curl -sS -X POST 'https://mcp.wix.com/api/code-mode/search' -H 'Content-Type: application/json' \
  --data-raw '{"code":"async function(){ return lightIndex.flatMap(r=>r.methods).filter(m=>/createBooking$/i.test(m.operationId)).map(m=>({op:m.operationId, httpMethod:m.httpMethod, publicUrl:m.publicUrl, docsUrl:m.docsUrl})); }"}'
```

**Filter narrowly and return only the fields you need** — the index is large, so an unfiltered dump
is huge. Scope: **REST API methods only** (not concept/guide articles, headless prose, or SDK-only
surfaces — use A/B for those). More examples (browse a whole vertical, `menuPath` walk,
whole-resource schema) and the `getResourceSchema` reader → **`references/API_SPEC_SEARCH.md`**.

If the Wix MCP is present, it exposes these same capabilities as native tools (no `curl`/JSON
boilerplate) — Lane 2.

### 2. Read what you land on

Appending `.md` to a URL gives one of **three kinds of page**. Know which you're looking at, and
handle it accordingly:

- **Menu page** — a *section* path (from browsing, §1B). A list of child links, often tens of KB —
  **don't read it whole; `grep` it** for the child you want, then drill into that page:

  ```bash
  curl -sS 'https://dev.wix.com/docs/api-reference/business-solutions/bookings.md' | grep -i 'booking'
  ```

- **Article / guide** — introductions, concepts, sample-flow pages. Prose markdown, usually small —
  **read it whole**:

  ```bash
  curl -sS 'https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/introduction.md'
  ```

- **Method page** — one API method, and the heavy one: it carries **both** a REST and a JavaScript
  SDK section, the full request/response schema, and code examples — often 100 KB+. **Don't swallow
  the whole page** — map it, then pull the part you need (the examples are usually enough to model a
  call):

  ```bash
  curl -sS "$URL.md" | grep -nE '^#{1,3} '                                              # 1. map the outline
  curl -sS "$URL.md" | awk '/^## REST API/{r=1} r&&/^### Examples/{f=1} /^## JavaScript SDK/{f=0} f'  # 2. just the REST examples
  curl -sS "$URL.md" | grep -nE 'name: (selectedPaymentOption|totalParticipants)'       # 3. grep specific schema fields
  ```

  More recipes (split REST vs SDK, resolve an enum) → `references/EXTRACTING.md`.

  For the exact **structured** schema and enum values, don't hand-slice the markdown — query the API
  spec with a `curl` `POST` to `https://mcp.wix.com/api/code-mode/search` (the no-MCP equivalent of
  the MCP `SearchWixAPISpec`). The `code` is a JS function with `lightIndex` and
  `getResourceSchemaByUrl(docsUrl)` in scope; return only what you need:

  ```bash
  # find a method by keyword → its docsUrl + executable publicUrl
  curl -sS -X POST 'https://mcp.wix.com/api/code-mode/search' -H 'Content-Type: application/json' \
    --data-raw '{"code":"async function(){ return lightIndex.flatMap(r=>r.methods).filter(m=>/createBooking$/i.test(m.operationId)).map(m=>({op:m.operationId, httpMethod:m.httpMethod, publicUrl:m.publicUrl, docsUrl:m.docsUrl})); }"}'

  # pull one method's request/response schema by its docsUrl (resolve $circular refs via s.components.schemas)
  curl -sS -X POST 'https://mcp.wix.com/api/code-mode/search' -H 'Content-Type: application/json' \
    --data-raw '{"code":"async function(){ const u=\"https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking\"; const s=await getResourceSchemaByUrl(u); const m=s.methods.find(x=>x.docsUrl===u); return { publicUrl:m.publicUrl, requestBody:m.requestBody, responses:m.responses }; }"}'
  ```

  Full example set (resource listing, partial-URL resolution, enum/nested-ref expansion) →
  `references/API_SPEC_SEARCH.md`.

## Lane 2 — Wix MCP doc tools (only if your agent has them)

If the Wix MCP is connected, these are the **same backends as Lane 1** (the doc-search service and
the API-spec index) wrapped as native tools — schema-validated, response-size handled, no
`curl`/JSON boilerplate. A convenience over the curl lane, **not a richer data source**; use them
when present, fall back to Lane 1 when not. Optional — skip this lane if the tools aren't present.

| Tool | Use for |
|---|---|
| `SearchWixRESTDocumentation` | Find a REST method/recipe by keyword |
| `SearchWixSDKDocumentation` | Find an SDK method (surfaces runtime functions a module menu hides) |
| `SearchWixAPISpec` → `getResourceSchemaByUrl` | The **whole resource** — every method + shared object schema in one payload |
| `ReadFullDocsArticle` | Read a recipe/flow/article page in full |
| `BrowseWixRESTDocsMenu` | Walk the menu tree to drill to a method |

- **Prefer the whole-resource view** (`getResourceSchemaByUrl`) over a single method page: a
  requirement is often documented on a *sibling* method (e.g. a `memberId` required on
  single-create but omitted from the bulk-create page). The resource view carries both.
- **Look for the vertical's recipe/flow page first** — many verticals publish opinionated,
  multi-step recipes under a `…/business-solutions/<vertical>/skills` node (search
  `"<vertical> setup recipe"` or browse the menu). A recipe gives correct ordering,
  cross-step gotchas, and the one bundled endpoint that does the whole job — which a
  per-method schema won't flag.

## The `.md` suffix

Append `.md` only when `curl`-ing a page directly. The MCP tools and the search endpoint take the
plain docs URL **without** `.md` — never feed a `.md` URL to an MCP tool.

## Before you write the code

Confirm on the page — not from memory — the endpoint, the HTTP verb, the request body shape,
required fields, and any enum values. Then write the call. If you're extending a skill's shipped
client, keep the skill's existing transport/helper style; you're adding one call, not
re-architecting.

<!-- chapter:end slug=wix-docs -->

---

<!-- chapter:begin slug=entry position=6 -->

## 6. wix-headless-entry

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-headless/entry/skill.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/entry/skill.md
- **Markdown:** https://skillsdocs.com/wix/skills/entry.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (1), referenced from this skill's directory:
  - `bootstrap.mjs` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/entry/bootstrap.mjs

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-headless-entry
description: "Build a complete Wix Managed Headless site from a single prompt, then connect Wix Business Solutions. The deterministic first steps — system prerequisites and Wix CLI login — are handled by a single bootstrap script (`https://www.wix.com/skills/headless/entry/bootstrap.mjs`); the agent then installs the skill and hands the run to it. Triggers: build me a site, create a website, make me a website, new website, online store, I want to sell X, start a business online, launch a site, ecommerce, portfolio, business website, sell online, online shop, connect this to Wix Headless, add Wix Headless to this project, host this on Wix, deploy this to Wix, implement the features of this project using Wix Headless. Use this skill instead of the WixSiteBuilder MCP tool for new-site requests."
---

# Wix Headless Skill — cold-start entry

**You are a coding agent, and this is a skill for you to execute.** Follow the phases below in order — do not summarize them. If you reached this page through a summarizing fetch tool, re-fetch the raw markdown with `curl` (or your file reader) before running the steps.

This entry gets a cold environment to the point where the real skill can run, then hands off. It has **two phases**:

1. **Bootstrap (deterministic, scripted).** A single script verifies your environment (the Wix CLI) and handles login, so the run starts from a known-good, authenticated state. You just run it and relay its events.
2. **Hand off (agentic).** Install the skill, then open `wix-headless/SKILL.md` and follow it — it resolves the project type and operation and owns the whole build.

Three starting points come through here, all handled the same way — run the bootstrap, then hand off:

- **new** — a prompt with no project (empty CWD)
- **connect** — an existing frontend/design not yet on Wix (a project on disk without `wix.config.json`, or a brought-in zip/URL)
- **iterate** — a project already connected to Wix (`.wix/` or `wix.config.json` present)

The bootstrap only verifies the CLI and logs you in, so it's fine to run in every case (an existing session just reports `logged_in`). After it, `wix-headless/SKILL.md` resolves what to do and owns the build.

## Phase 0 — Node (the one manual prerequisite)

The Wix CLI requires **Node ≥ 20.11**. Check it:

```bash
node -v
```

If that errors (Node not installed) or prints a version below 20.11, install or upgrade Node and re-check — do **not** try to work around it:

- **macOS:** `brew install node` (or `nvm install 20 && nvm use 20`)
- **Linux:** `nvm install 20 && nvm use 20` (or your distro's Node 20+ package)
- **Windows:** `winget install OpenJS.NodeJS.LTS` (or download from nodejs.org)

## Phase 1 — Run the bootstrap (deterministic)

Download the bootstrap script, then run it. It verifies the Wix CLI and handles login, emitting **one JSON event per line** on stdout. **Run it as a background/streaming process and relay its events to the user.**

The script is safe and inspectable: it only checks the Wix CLI via `npx` and drives `wix login` (a device-code flow) — no other network calls, no filesystem writes. Read it first if your sandbox flags externally-downloaded code — either at the URL below or after downloading; both are the same file.

```bash
# macOS/Linux:
curl -fsSL -O https://www.wix.com/skills/headless/entry/bootstrap.mjs
# Windows PowerShell:
iwr https://www.wix.com/skills/headless/entry/bootstrap.mjs -OutFile bootstrap.mjs

node bootstrap.mjs
```

### Relay these events

The script emits one JSON object per line:

| Event | What to do |
|---|---|
| `cli_ok` | Wix CLI reachable — continue. |
| `awaiting_user` (`verificationUri`, `userCode`) | Show the URL and code in plain prose; wait for the user to finish the login in their browser. |
| `logged_in` / `success` | Login done — continue. |
| `cli_unreachable` / `login_failed` (with `detail`) | Stop and show the user the `detail`. **Do not** improvise a parallel setup by hand. |

## Phase 2 — Install the skill and hand off

Install the Wix Headless skills (`CI=1` forces plain non-interactive CLI output — keep it on every Wix CLI command):

```bash
CI=1 npx skills@latest add wix/skills --yes
```

The skills land in `.agents/skills/`.

Then **open `wix-headless/SKILL.md` and follow it.** That skill owns the rest of the run — it resolves the **project type** (`managed` / `self-managed` / `stripe`) and, for managed, the **operation** (create / connect), then runs Discovery → Setup → Seed → the SDK handoff, and for managed create/connect it also scaffolds, builds, and releases.

- **Don't** scaffold, install apps, or release by hand here — the skill does all of that (`SETUP.md`, `references/managed/CREATE.md`, etc.). This entry stops at *logged in*.
- You're already authenticated from Phase 1, so the skill's managed auth step (`references/managed/AUTHENTICATION.md` §1 `whoami`) will pass without prompting again.

<!-- chapter:end slug=entry -->

---

<!-- chapter:begin slug=wix-headless position=7 -->

## 7. wix-headless

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-headless/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-headless.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (45), referenced from this skill's directory:
  - `references/AI_FEATURES.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/AI_FEATURES.md
  - `references/astro.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/astro.md
  - `references/CAPABILITIES.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/CAPABILITIES.md
  - `references/DISCOVERY.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/DISCOVERY.md
  - `references/DOC_DISCOVERY.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/DOC_DISCOVERY.md
  - `references/FEEDBACK.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/FEEDBACK.md
  - `references/IMAGE_GENERATION.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/IMAGE_GENERATION.md
  - `references/inline-recipes/how-to-code-a-blog.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md
  - `references/inline-recipes/how-to-code-a-store.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md
  - `references/inline-recipes/how-to-code-bookings.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md
  - `references/inline-recipes/how-to-code-cms.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-cms.md
  - `references/inline-recipes/how-to-code-events.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-events.md
  - `references/inline-recipes/how-to-code-forms.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-forms.md
  - `references/inline-recipes/how-to-code-members-astro.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md
  - `references/inline-recipes/how-to-code-members-custom-login.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md
  - `references/inline-recipes/how-to-code-members-non-astro.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md
  - `references/inline-recipes/how-to-code-portfolio.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md
  - `references/inline-recipes/how-to-code-pricing-plans.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md
  - `references/inline-recipes/how-to-code-restaurant-orders.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md
  - `references/inline-recipes/how-to-code-restaurant-reservations.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md
  - `references/inline-recipes/how-to-code-restaurants.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md
  - `references/inline-recipes/setup-blog.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/setup-blog.md
  - `references/inline-recipes/setup-bookings.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/setup-bookings.md
  - `references/inline-recipes/setup-cms.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-headless/references/inline-recipes/setup-cms.md
  - …and 21 more, listed in https://skillsdocs.com/api/v1/books/wix/skills/skills/wix-headless

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-headless
description: "Connect Wix business services (Stores, Bookings, CMS, Blog, Events, Forms, and more) to a Wix Headless frontend — infer the needed capabilities, install the apps, seed backend content, and produce an SDK-integration guide. For managed (Wix-hosted) projects it can also build the frontend: scaffold a new site (create) or wire an existing/brought-in design (connect), then build and release. Works across managed, self-managed, and stripe project types. Triggers: set up a Wix Headless backend, add Wix business features to my app, build or host a Wix site, connect/implement this design with Wix."
allowed-tools:
  - Bash(curl *)
  - Bash(npx @wix/cli@latest *)
  - Bash(npx @wix/cli *)
  - Bash(npm create @wix/new@latest *)
  - Bash(npm install *)
  - Bash(npm run *)
  - Bash(node *)
  - Bash(bash *)
  - Bash(cd *)
  - Bash(ls *)
  - Bash(mkdir *)
  - Bash(cp *)
  - Bash(mv *)
  - Bash(uuidgen)
  - Read
  - Write
  - Edit
---

# Wix Headless

This skill connects **Wix business services** to a **Wix Headless frontend**. Its core job is to **configure the Wix backend**: infer the needed capabilities, **install the Wix apps**, **seed the backend content**, and produce an SDK-integration guide describing how to call Wix from the frontend.

What happens with the frontend depends on the project type and operation:
- **Backend-only** (self-managed, stripe, or a managed "just set up the backend" run) — the skill **emits the SDK guide** as its final output; the **host owns** the frontend, build, and hosting.
- **Managed create / connect** — the skill **also owns the frontend**: it scaffolds a new project (create) or attaches Wix to an existing one (connect), wires it to the backend using that same guide, builds, and releases to Wix. (There is no Designer or template library — the frontend is built ad-hoc to intent.)

> **On a backend-only run the skill does not own the project scaffolding.** When the work needs a frontend, **scaffold it according to user intent** — framework and structure follow the prompt; the skill's job is the Wix backend plus the **emitted SDK guide** describing how to call Wix from whatever frontend exists, not choosing or generating the app. This is the norm for a **stripe** run: the project is provisioned via Stripe Projects (credentials land in `.env` — see `stripe/AUTHENTICATION.md`), the skill configures the backend and emits the guide, and the live site is finalized per `stripe/DEPLOYMENT.md` — while the frontend is scaffolded and wired to intent, by the host.

## Project types

The skill behaves identically across project types **except for authentication and deployment**, which are isolated in a per-type folder under `references/`:

| Project type | Hosting | Authentication & deployment live in |
|---|---|---|
| `managed` | Wix infrastructure (maintained by Wix) | `references/managed/` |
| `self-managed` | the user's own host (TBD) | `references/self-managed/` |
| `stripe` | the user's own host, provisioned via Stripe Projects | `references/stripe/` |

Everything else — Discovery, Setup, Seed, the SDK handoff — is **project-type-agnostic** and refers to "the provided authentication mechanism" rather than any specific method.

### Resolving the project type

Resolve the type **before Discovery**, in this order — stop at the first that decides:

1. **Caller-provided.** If the host/caller passed a project type (`managed` | `self-managed` | `stripe`), use it. An explicit value always wins.
2. **Detect from project signals (on disk).** Check the working directory (read-only):
   - **`stripe`** — `.env` carries `WIX_WIX_CLIENT_ID` / `WIX_WIX_CLIENT_SECRET` / `WIX_WIX_METASITE_ID` (or the plain `WIX_*` fallback). This is the Stripe-Projects fingerprint.
   - **`managed`** — a Wix CLI project: `wix.config.json` present, or a `.wix/` directory, or `@wix/cli` / `@wix/astro` in `package.json`.
   - **`self-managed`** — a frontend project on disk (e.g. `package.json` with a bundler/framework) with **neither** the Stripe `.env` fingerprint **nor** the Wix-CLI markers above.
3. **Detect from user intent.** If disk is inconclusive, read the prompt:
   - **`managed`** — "managed", "host on Wix", "Wix-hosted", "deploy to Wix", "`wix release`".
   - **`stripe`** — "stripe projects", "I added Wix via Stripe", a Stripe-Projects context.
   - **`self-managed`** — "self-hosted", "my own host", names a non-Wix host, "I'll deploy it myself".
4. **Ask.** If signals are absent or conflict, **ask the user** which type applies — don't guess.

Hold the resolved type in scratch; it selects `<TYPE_DIR>` (see Path resolution). Note `self-managed` is **TBD** — once resolved to it, the auth step will stop with a clear "not wired yet" error.

### Resolving the operation (managed only)

create/connect/iterate are **managed-only**. For `managed`, resolve the operation **by intent first, directory second** — never let an empty directory override what the user is asking for. Check `iterate` first (it's decided by an unambiguous on-disk signal):

- **`iterate`** → `references/managed/CONNECT.md`. The project is **already connected to Wix** — a `wix.config.json` (or `.wix/`) is already present — and the owner wants to add or change capabilities. Same flow as `connect`, but §1 **skips `init`** and reuses the existing `wix.config.json`; **never re-`init` an already-connected project**.
- **`connect`** → `references/managed/CONNECT.md`. The user **brings a design not yet connected to Wix and wants it wired**: a frontend project on disk **without** a `wix.config.json`, **or** a brought-in/fetched design (a `.zip`/folder/file you unzip or read, a design-file URL, a Claude-Design/v0/Lovable export), **or** language like "connect this / implement this design (… connecting to Wix) / host this on Wix / deploy this to Wix / add Wix Headless to this project". **A brought-in or fetched design is `connect` even when the current directory is empty** — the design arrives from elsewhere (zip/fetch/URL), so emptiness at trigger time is *not* a create signal. (`CONNECT.md` step 1 places the brought design into CWD, then `init`s it.)
- **`create`** → `references/managed/CREATE.md`. The user wants a **new site built from a prompt with nothing brought in** — "build me a site / store / blog…", no design file, no project on disk.
- **`backend-only`** — the user only wants the backend configured (no frontend work). → the shared spine + emit the SDK handoff.

For `self-managed` and `stripe`, the operation is always **backend-only** (the host owns the frontend). Only when there is **genuinely no brought-in design and no connect language** does directory emptiness decide: empty → `create`; an existing frontend on disk → `connect`.

## Preconditions (the host provides these — we read, never create)

1. **The project type** — `managed` | `self-managed` | `stripe` — provided by the caller, else **detected from project signals / user intent** (see Project types § "Resolving the project type"), else asked. It selects `<TYPE_DIR>` (see Path resolution).
2. **A Wix metasite + a headless OAuth app exist, and credentials are available** — obtained via the project type's `AUTHENTICATION.md`. The skill needs a bearer token authorized for the metasite and the metasite id.
3. **The user intent** — free text describing what Wix should power ("add a store", "blog + contact form", "persist my app's data").
4. *(Optional)* the project on disk — read-only, to sharpen brand/capability inference.

If the credentials are absent, the Wix backend isn't reachable — **stop with a clear error**.

## What this skill does

**Always open `DISCOVERY.md` first** (it's agnostic — capability/brand/intent inference + the imagery gate, no auth). Then route by project type + operation:

**Backend-only** (self-managed, stripe, or managed backend-only) — the lean spine:
1. **Discovery** (`references/DISCOVERY.md`) — infer capabilities + brand + intent + imagery.
2. **Setup** (`references/SETUP.md`) — **install** the Wix apps those capabilities need.
3. **Seed** (`references/SEED.md`) — **create** the backend content (+ entity images if imagery is on).
4. **Handoff** (`references/SDK_HANDOFF.md`) — after Setup and Seed, **emit** the integration guide: SDK bootstrap, per-capability call shapes, the **seeded IDs**, and the `@wix/*` package list.
5. **Finalize deployment** (`<TYPE_DIR>/DEPLOYMENT.md`) — run the project-type's finalize steps.

**Throughout any run** — if the user asks to send feedback to Wix, complains/gets frustrated, or the
run hits any real friction (a confusing error, a doc gap, a tooling dead end, a workaround you had to
invent — it doesn't need to repeat), **offer** to relay it to Wix per `references/FEEDBACK.md`. Default
to offering rather than waiting to be asked. Every flow's finalize step (`<TYPE_DIR>/DEPLOYMENT.md` /
`managed/CREATE.md` / `managed/CONNECT.md`) ends with the same checkpoint — treat it as a required
self-check, not optional. Send only after an explicit yes — never automatically.

**Managed create / connect / iterate** — after Discovery, hand the whole run to the managed flow:
- **create** → **`references/managed/CREATE.md`** (scaffold → Setup → Seed → build the frontend → release).
- **connect** → **`references/managed/CONNECT.md`** (init → Setup → Seed → wire the existing UI → release).
- **iterate** → **`references/managed/CONNECT.md`** — the project is already connected, so it reuses the existing `wix.config.json` (no `init`). Setup / Seed / wiring are **incremental**: it may already be set up and seeded from a prior run, so the agent **checks current state first** (installed apps, already-seeded content, existing wiring) and applies only the delta the new intent needs — never blindly re-installing or re-seeding. Re-release only if the frontend build output changed.
  These reuse the same `SETUP.md`/`SEED.md`/`SDK_HANDOFF.md`, but **apply** the SDK guide to build/wire the frontend themselves rather than emitting it, and release via `managed/DEPLOYMENT.md`.

Each Wix call uses the universal call shape (`SETUP.md` §1) with `$TOKEN`/`$SITE_ID` obtained per `<TYPE_DIR>/AUTHENTICATION.md`. The skill runs non-interactively except for the one imagery question (and asking the project type if it can't be resolved).

> **Don't smoke-test the frontend locally unless the user asks to verify.** The deliverable is the built-and-released site (managed) or the SDK guide (backend-only) — not a local test report. By **default do not** start a dev server (`wix dev` / `astro dev` / `npm run dev`) to curl pages, drive the cart / booking / login flow, or launch a headless browser: correctness comes from following the recipes, real errors surface at `wix build` / `wix release`, and a headless run can't complete an interactive login or a real payment anyway — so these loops routinely burn minutes of wall for little signal (this is distinct from, and in addition to, the "release once at the very end" rule — that one only bans extra *build+release* cycles, not dev-server testing). **Only** spin up a dev server and smoke-test when the user's prompt **explicitly asks to verify / test / confirm it works** — and then keep it to a single lightweight pass (pages compile and render), not a full purchase/booking/login drive. The one post-release check in `<TYPE_DIR>/DEPLOYMENT.md` is the sanctioned verification.

## Path resolution

Compute `<SKILL_ROOT>` from this file (`<SKILL_ROOT>/SKILL.md` — strip `/SKILL.md`); hold the absolute path in scratch. Then resolve the project type to its folder and hold it too:

> **`<TYPE_DIR>` = `<SKILL_ROOT>/references/<projectType>/`** — one of `managed/`, `self-managed/`, `stripe/`.

| What | Path |
|---|---|
| Vertical index (intent matching + per-vertical site spec) | `<SKILL_ROOT>/references/CAPABILITIES.md` |
| Discovery (infer capabilities + brand + intent; existing site → the Dynamic Context call in §1) | `<SKILL_ROOT>/references/DISCOVERY.md` |
| Setup (install apps) | `<SKILL_ROOT>/references/SETUP.md` |
| Seed (create backend content) | `<SKILL_ROOT>/references/SEED.md` |
| SDK-integration handoff (emitted, or applied by create/connect) | `<SKILL_ROOT>/references/SDK_HANDOFF.md` |
| Image generation (opt-in; agnostic) | `<SKILL_ROOT>/references/IMAGE_GENERATION.md` |
| AI features — text/chat + embeddings (opt-in; agnostic) | `<SKILL_ROOT>/references/AI_FEATURES.md` |
| Feedback — relay the user's headless-experience feedback to Wix (opt-in; user-approved) | `<SKILL_ROOT>/references/FEEDBACK.md` |
| **Authentication** — obtain `$TOKEN`/`$SITE_ID`/`clientId` (project-type-specific) | `<TYPE_DIR>/AUTHENTICATION.md` |
| **Deployment** — finalize the live site (project-type-specific) | `<TYPE_DIR>/DEPLOYMENT.md` |
| Managed **create** flow (scaffold a new project) | `<SKILL_ROOT>/references/managed/CREATE.md` |
| Managed **connect** flow (wire an existing project) | `<SKILL_ROOT>/references/managed/CONNECT.md` |
| Frontend-axis references (how a frontend wires to Wix) | `<SKILL_ROOT>/references/astro.md`, `non-astro.md` |

**Start a run by opening `DISCOVERY.md`.** The flow files (`CAPABILITIES`, `DISCOVERY`, `SETUP`, `SEED`, `SDK_HANDOFF`, `IMAGE_GENERATION`, `AI_FEATURES`) are project-type-agnostic; the per-type specifics live under `<TYPE_DIR>/` (`AUTHENTICATION.md`, `DEPLOYMENT.md`, and — managed only — `CREATE.md`/`CONNECT.md`).

## Where the *how* comes from

This skill has **no skill upstream** — the *how* is read from the **live Wix docs** at `dev.wix.com/docs`. **Read-priority: a page this skill links is read by `curl`-ing its `.md` twin directly (first priority — don't re-discover a curated link with search); the Wix MCP doc/search tools are second priority, for finding pages the skill doesn't link or as a fallback if a fetch fails.** (Append `.md` to any docs URL for raw markdown; menu pages list child links, content pages carry the schema.)

**Doc discovery is the shared fallback for both tracks — never the first move.** Each track's *primary* source is its pinned material (below); when that doesn't cover what you need, fall back to `references/DOC_DISCOVERY.md` — a semantic doc-search + schema lookup that works with or without the Wix MCP.

- **Seed** reads each capability's create flow from its **inline recipe** — a **self-contained local `inline-recipes/setup-*.md`** (mapped per capability in `SEED.md` § "What to seed per capability") that inlines the calls and **supersedes** the REST doc pages, so read it and seed from it alone. Only a capability with **no** inline recipe (e.g. `coupons`) falls back to doc discovery (`DOC_DISCOVERY.md`).
- **Handoff** links the **SDK docs** for each capability's API shape, and supplies the runtime package set from the inlined map in `SDK_HANDOFF.md` (the SDK `.md` pages don't expose `@wix/*` import strings to navigation, so packages are mapped, not navigated).

Setup carries its app-install call (and the appDefId constants) inline in `SETUP.md`; `CAPABILITIES.md` is the vertical index that lets Discovery match intent **and** declares, per built vertical, the *Required site features* + *Implementation checklist* that Seed enables (backend-backed features) and the Handoff carries into the guide (so the host builds a complete site, not a bare data dump). This skill carries the *what* (which capabilities, how much content, what a finished site includes) and reads the *how* off the docs.

<!-- chapter:end slug=wix-headless -->

---

<!-- chapter:begin slug=wix-manage position=8 -->

## 8. wix-manage

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-manage/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-manage.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (105), referenced from this skill's directory:
  - `references/analytics/analytics-dashboard-navigation.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/analytics/analytics-dashboard-navigation.md
  - `references/analytics/query-site-analytics.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/analytics/query-site-analytics.md
  - `references/app-installation/app-installation-dashboard-navigation.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/app-installation/app-installation-dashboard-navigation.md
  - `references/app-installation/install-wix-apps.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/app-installation/install-wix-apps.md
  - `references/app-installation/list-installed-apps.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/app-installation/list-installed-apps.md
  - `references/blog/blog-dashboard-navigation.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/blog/blog-dashboard-navigation.md
  - `references/blog/how-to-create-blog-posts.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/blog/how-to-create-blog-posts.md
  - `references/bookings/booking-service-policy-setup.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/booking-service-policy-setup.md
  - `references/bookings/booking-system-integration-gaps.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/booking-system-integration-gaps.md
  - `references/bookings/bookings-dashboard-navigation.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/bookings-dashboard-navigation.md
  - `references/bookings/bookings-staff-setup.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/bookings-staff-setup.md
  - `references/bookings/create-and-update-booking-services.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/create-and-update-booking-services.md
  - `references/bookings/create-appointment-service.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/create-appointment-service.md
  - `references/bookings/create-booking-service-from-prompt.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md
  - `references/bookings/create-class-service.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/create-class-service.md
  - `references/bookings/create-course-service.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/create-course-service.md
  - `references/bookings/diagnose-availability-issues.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/diagnose-availability-issues.md
  - `references/bookings/end-to-end-booking-flow.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/end-to-end-booking-flow.md
  - `references/bookings/external-calendar-integration.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/external-calendar-integration.md
  - `references/bookings/multi-resource-service-creation.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/bookings/multi-resource-service-creation.md
  - `references/calendar/configure-default-business-hours.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/calendar/configure-default-business-hours.md
  - `references/cms/cms-dashboard-navigation.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/cms/cms-dashboard-navigation.md
  - `references/cms/cms-data-items-crud.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/cms/cms-data-items-crud.md
  - `references/cms/cms-data-operations-extended.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-manage/references/cms/cms-data-operations-extended.md
  - …and 81 more, listed in https://skillsdocs.com/api/v1/books/wix/skills/skills/wix-manage

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-manage
description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, dashboard-navigation."
compatibility: Requires Wix REST API access (API key or OAuth).
---

# Management Recipes Index

> **Standard call shape for every curl example across these recipes.** The `<AUTH>` placeholder in example curls is shorthand for the `Authorization` header only; body-bearing calls also need `Content-Type: application/json`.

## What Are Management Recipes?

**Management recipes are for REST API operations** that configure, set up, and manage Wix business entities on your site. These recipes use REST API calls and are designed for:

- **Site setup and configuration** — Initial setup of stores, bookings, payments, and other business apps
- **Entity management** — Creating, updating, and deleting products, services, staff members, pricing plans
- **Administrative operations** — Bulk updates, contact labeling, data migrations
- **Backend integrations** — Server-to-server automations, webhooks, data synchronization

These recipes do NOT cover frontend development or SDK usage for displaying data to users.

---

## App Installation

### [Install Wix Apps](references/app-installation/install-wix-apps.md)
**Technical:** Installs Wix apps on a site using Apps Installer API. Covers enabling Velo (Wix Code), app installation, and common app definition IDs.

### [List Installed Apps](references/app-installation/list-installed-apps.md)
**Technical:** Lists all apps installed on a site using Apps Installer API. Useful for verifying app installations before making API calls and diagnosing authorization errors.

### [App Management Dashboard Navigation](references/app-installation/app-installation-dashboard-navigation.md)
**Technical:** Direct links to the App Market and installed-apps management dashboard pages on manage.wix.com, paired with the List Installed Apps read API.

---

## Analytics

### [Query Site Analytics](references/analytics/query-site-analytics.md)
**Technical:** Reads a site's analytics through the Semantic Model API. Covers listing semantic models, inspecting a model's schema (measures, dimensions, parameters), and querying data with a required time interval, filters, sorting, paging, and human-readable formatting. Key endpoints: /analytics/semantic-model/v3/semantic-models, /semantic-models/{id}, /semantic-models/query-data.

### [Analytics Dashboard Navigation](references/analytics/analytics-dashboard-navigation.md)
**Technical:** Direct links to Wix Analytics dashboard pages on manage.wix.com (highlights, reports, custom reports, traffic/behavior/sales/marketing overviews, performance insights, benchmarks), paired with the Semantic Model read API for "see it in your dashboard" links.

---

## Blog

### [How to Create Blog Posts](references/blog/how-to-create-blog-posts.md)
**Technical:** Creates and publishes blog posts using Blog Posts API. Covers resolving the required author memberId (including creating an author member when the site has none), Ricos rich content format, image upload via Media Manager, category/tag assignment, and bulk post creation.

### [Blog Dashboard Navigation](references/blog/blog-dashboard-navigation.md)
**Technical:** Direct links to Wix Blog dashboard pages on manage.wix.com (posts list with published/draft tabs, categories, tags, writers, comments, analytics, monetization, settings), pairing each main Blog entity with its read API for "view it in your dashboard" links.

---

## Bookings

### [Booking Service Policy Setup](references/bookings/booking-service-policy-setup.md)
**Technical:** Sets up booking policies, cancellation rules, and waitlist configuration using the Services API policy fields. Covers bookingPolicy, cancellationPolicy, and waitlist settings.

### [Booking System Integration Gaps](references/bookings/booking-system-integration-gaps.md)
**Technical:** Documents undocumented API patterns for booking payments. Covers Bookings→Ecommerce integration, booking ID transformation to catalog items, and async payment confirmation flows.

### [Bookings Staff Setup](references/bookings/bookings-staff-setup.md)
**Technical:** Creates staff members and configures custom working hours using Staff API + Calendar Events API. Critical two-step process: create staff → assign schedule → create working hours events.

### [Create and Update Booking Services](references/bookings/create-and-update-booking-services.md)
**Technical:** Full CRUD operations for Wix Bookings services using Services API. Covers service types (APPOINTMENT, CLASS, COURSE), pricing configuration, location setup, and schedule management.

### [Create Booking Service from Prompt](references/bookings/create-booking-service-from-prompt.md)
**Technical:** Use when the user wants to create a booking service — e.g. "create a yoga class for $50", "set up consultations", "add a personal training appointment", "create a hidden free test course with 8 sessions". Routes to the correct type-specific recipe (APPOINTMENT, CLASS, or COURSE), gathers business context, applies defaults, and creates the service. For COURSE services with session dates/counts, the course is not bookable until separate Calendar Events are created on the returned service schedule.

### [Create Appointment Service](references/bookings/create-appointment-service.md)
**Technical:** Use when the user wants to create an appointment/consultation/1-on-1 service — e.g. "set up consultations for $75", "create a meeting service". Handles staff assignment, session duration, and pricing via bulkCreateServices API.

### [Create Class Service](references/bookings/create-class-service.md)
**Technical:** Use when the user wants to create a group class — e.g. "create a yoga class for $50", "set up a pilates class". Handles group capacity, recurring sessions, and pricing via bulkCreateServices API.

### [Create Course Service](references/bookings/create-course-service.md)
**Technical:** Use when the user wants to create a multi-session COURSE — e.g. "create a 6-week workshop", "set up a training program for $300", "create a hidden free test course with 8 online sessions". Handles group capacity, full-course pricing, `bulkCreateServices`, then creates bookable course session events with Calendar `bulkCreateEvents` using the returned `service.schedule.id`. Never put session dates under `course.sessions` in the Services V2 payload.

### [Check Bookings Availability (and Diagnose Issues)](references/bookings/diagnose-availability-issues.md)
**Technical:** Use when someone asks whether an appointment-based service has bookable availability, or why it shows no times / "customers can't book". Reports the current availability status first (via ListAvailabilityTimeSlots); diagnoses the cause only when there's no availability or the owner asks why — ruling out service-level blockers (hidden / online booking off), then running DiagnoseAvailability (`POST /v2/time-slots/diagnose`) for ordered reason codes, with a policy/capacity fallback.

### [End-to-End Booking Flow](references/bookings/end-to-end-booking-flow.md)
**Technical:** Complete booking flow from service discovery to payment. Query services, check availability with Time Slots V2, create bookings, and process payment via eCommerce checkout.

### [External Calendar Integration](references/bookings/external-calendar-integration.md)
**Technical:** OAuth-based integration with Google Calendar, Microsoft Outlook, and Apple Calendar. Covers authentication flows, sync configuration, and bidirectional event management.

### [Multi-Resource Service Creation](references/bookings/multi-resource-service-creation.md)
**Technical:** Creates resource types and individual resources using Resources API. Enables services that require multiple resources (rooms + equipment + staff) with automatic allocation.

### [Bookings Dashboard Navigation](references/bookings/bookings-dashboard-navigation.md)
**Technical:** Direct links to Wix Bookings dashboard pages on manage.wix.com (services list, edit service, calendar, booking list, staff, availability, resources, settings), pairing each main Bookings entity with its read API for "view it in your dashboard" links.

---

## Calendar

### [Configure Default Business Hours](references/calendar/configure-default-business-hours.md)
**Technical:** Uses Calendar Events API to create WORKING_HOURS events on the business schedule. Covers the critical distinction between Calendar Events API (correct) vs Site Properties API (incorrect) for setting base availability.

> Dashboard links for calendar surfaces (availability, default business hours) are in [Bookings Dashboard Navigation](references/bookings/bookings-dashboard-navigation.md).

---

## CMS

### [CMS Data Items CRUD](references/cms/cms-data-items-crud.md)
**Technical:** Add, query, update, and delete items in CMS collections. Use this to insert content, bulk insert/update/patch/delete items, query with filters, and manage collection data. Key endpoints: /wix-data/v2/items, /wix-data/v2/bulk/items/*.

### [CMS Data Operations Extended](references/cms/cms-data-operations-extended.md)
**Technical:** Additional CMS data operations including count, upsert (bulk save), and update by filter patterns.

### [CMS eCommerce Catalog Integration](references/cms/cms-ecommerce-catalog-integration.md)
**Technical:** The recommended way to sell existing CMS collection items (tickets, bookings, memberships) through Wix checkout. Add the CATALOG plugin to convert any CMS collection into purchasable products with cart and payment integration.

### [CMS References & Relationships](references/cms/cms-references-and-relationships.md)
**Technical:** Add, replace, or remove items from MULTI_REFERENCE fields. Use insert-references, replace-references, remove-references endpoints. Required for managing multi-reference relationships - these CANNOT be set via regular insert/update/patch operations. Also covers single references and querying with expanded references.

### [CMS Schema Management](references/cms/cms-schema-management.md)
**Technical:** Create and modify CMS collection structures. Covers listing collections, creating collections with fields, adding/removing fields, and updating collection settings.

### [CMS Publishing Flow & Visible/Hidden](references/cms/cms-publishing-flow.md)
**Technical:** Interact with collections that gate items behind a draft/publish workflow — Visible/Hidden and Publishing Flow (Review, with DRAFT/PUBLISHED/CHANGED states). Detect the mode, read the combined draft+live view (`publishPluginOptions.includeDraftItems`), author/edit drafts against the `<collectionId>__drafts` shadow, and publish/unpublish/revert/delete items. Key endpoints: /wix-data/v2/items/publish-draft, /wix-data/v2/items/unpublish, /wix-data/v2/collections/add-plugin.

### [CMS Dashboard Navigation](references/cms/cms-dashboard-navigation.md)
**Technical:** Direct links to the Wix CMS (Content Manager) dashboard pages on manage.wix.com (collections list, a specific collection's items view), pairing collections and data items with their read APIs for "view it in your dashboard" links.

---

## Contacts

### [Bulk Delete Contacts](references/contacts/bulk-delete-contacts.md)
**Technical:** Deletes multiple contacts using filter-based bulk delete. Covers safe deletion patterns, GDPR compliance, soft delete alternatives, and batch processing strategies.

### [Bulk Label and Unlabel Contacts](references/contacts/bulk-label-and-unlabel-contacts.md)
**Technical:** Adds/removes labels from multiple contacts using Contacts API bulk operations. Covers label creation, contact filtering, batch processing, and rate limit handling.

### [Create a Contact](references/contacts/create-a-contact.md)
**Technical:** Creates a contact in one Contacts API call. Covers the minimum identifying fields, the single-object shape of `email` and `phone` (a list is accepted and silently discarded), optionally attaching a physical address, and the ISO 3166-2 `subdivision` format (`US-NY`, not `NY`) that state, region and province codes are validated against.

### [Update a Contact](references/contacts/update-a-contact.md)
**Technical:** Updates an existing contact's email, phone, name or address. Covers locating the contact with Search Contacts when the user names it (Query Contacts cannot filter by name), passing the current `revision`, appending an address via the contact's `addresses` sub-resource, and the ISO 3166-2 `subdivision` format (`US-NY`, not `NY`).

### [Contacts Dashboard Navigation](references/contacts/contacts-dashboard-navigation.md)
**Technical:** Direct links to Wix Contacts (CRM) dashboard pages on manage.wix.com (contacts list, view a specific contact, contact import, segments), pairing each main contacts entity with its read API for "view it in your dashboard" links.

---

## Dashboard Navigation

### [Dashboard Navigation](references/dashboard-navigation/dashboard-navigation.md)
**Index** — for any "where do I manage X in the dashboard" / "give me a dashboard link" request: the shared URL structure for all dashboard pages (`https://manage.wix.com/dashboard/{metaSiteId}/{route}`, app-ID fallback, legacy redirects, entity deep links), routing to the per-business-solution recipes (e.g. [Bookings](references/bookings/bookings-dashboard-navigation.md), [Stores](references/stores/stores-dashboard-navigation.md)) which live in their solution's section below.

---

## Domains

### [Domain Search and Purchase](references/domains/domain-search-and-purchase.md)
**Technical:** Search for available domains, get domain suggestions, and generate purchase links using Domain Search V2 API. Covers availability checks, TLD filtering, and connecting domains to Wix sites.

### [Domains Dashboard Navigation](references/domains/domains-dashboard-navigation.md)
**Technical:** Direct links to the site-level domain settings page and the account-level My Domains page on manage.wix.com, paired with the Domain Search read APIs.

---

## eCommerce

**Routing — pick the right entry point:**
- **Any sales/business improvement request** (boost sales, promotions, help my business, holiday deals, improve revenue, discounts, shipping, coupons, clearance, gift cards) → use [Recommend: eCommerce Strategy](references/ecommerce/recommend-ecommerce-strategy.md). This is the **default entry point** — it analyzes ALL domains (discounts, shipping, gift cards) and generates cross-domain recommendations. Do NOT ask clarifying questions.
- **Traffic acquisition is NOT an eCommerce-strategy request** ("grow my traffic", SEO, ads, social, content) → do NOT use Recommend: eCommerce Strategy; it only converts visitors a store already has. Route these to marketing.
- **Pricing & promotions** (coupons, discount rules, ribbons, sales) → use the [Pricing & Promotions](references/ecommerce/ecom-pricing.md) dispatcher.
- **Shipping setup** (rates, regions, pickup, free shipping, fix coverage) → use the [Shipping](references/ecommerce/ecom-shipping.md) dispatcher.
- **Gift cards** ("should I sell gift cards", "add a gift card", "what amounts should my gift card have") → these are recommendations, so they go through [Recommend: eCommerce Strategy](references/ecommerce/recommend-ecommerce-strategy.md), which activates its GIFT_CARDS domain and loads the gift-cards goal itself. Issuing/redeeming an individual gift card is the Gift Cards API, not a recommendation.

### [eCommerce: Load Context](references/ecommerce/ecom-load-context.md)
**L1 loader** — loads general site data (siteId, country, currency, industry, catalog analytics) needed by every eCommerce category. Each category dispatcher loads this before tag-matching; runs once per session.

### [Recommend: eCommerce Strategy](references/ecommerce/recommend-ecommerce-strategy.md)
**Entry point for all eCommerce recommendation requests.** Unified skill that analyzes site data across ALL domains (discounts + shipping + gift cards), generates up to 5 cross-domain recommendations, and persists them to the tracking database. Covers discount strategies (seasonal, upsell, stock mover, bundling), shipping optimization (coverage gaps, free shipping, rate strategy, carrier backup), AND selling gift cards (denominations sized from the site's own AOV and catalog prices). Use this for business improvement requests about earning more from existing visitors. **Traffic acquisition (SEO, ads, social, content) is out of scope** — route "grow my traffic" to marketing.

### [Pricing & Promotions](references/ecommerce/ecom-pricing.md)
**Dispatcher** — routes coupon/discount/sale/ribbon/bundle requests to the right leaf recipe (create coupon, create discount rule, troubleshoot discount-not-applying), and routes strategic "run a sale / boost sales" requests to `recommend-ecommerce-strategy`.

### [Shipping](references/ecommerce/ecom-shipping.md)
**Dispatcher** — routes shipping-setup requests (rates, regions, pickup, free shipping, fix coverage, optimize rates) to the right leaf recipe. The Shipping Options + Delivery Profiles APIs have no public docs page; `ecom-shipping-api.md` is the authoritative inline reference.

<details>
<summary>Internal skills (loaded automatically by the dispatchers / orchestrator above — do NOT use directly)</summary>

#### Pricing & promotions leaves (loaded by the Pricing dispatcher or by the strategy orchestrator)
- [Pricing: Create Coupon](references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md)
- [Pricing: Create Discount Rule](references/ecommerce/pricing-promotions/ecom-pricing-create-discount-rule.md)
- [Pricing: Discount Not Applying](references/ecommerce/pricing-promotions/ecom-pricing-troubleshoot-not-applying.md)
- Goals: [Increase AOV](references/ecommerce/pricing-promotions/ecom-pricing-goal-increase-aov.md), [Clear Inventory](references/ecommerce/pricing-promotions/ecom-pricing-goal-clear-inventory.md), [Seasonal Revenue](references/ecommerce/pricing-promotions/ecom-pricing-goal-seasonal-revenue.md), [Drive Cross-Sells](references/ecommerce/pricing-promotions/ecom-pricing-goal-drive-cross-sells.md)
- Flows: [Upsell Boost](references/ecommerce/pricing-promotions/ecom-pricing-flow-upsell-boost.md), [Bundle and Save](references/ecommerce/pricing-promotions/ecom-pricing-flow-bundle-and-save.md), [Stock Mover](references/ecommerce/pricing-promotions/ecom-pricing-flow-stock-mover.md), [Seasonal Promotion](references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md)

#### Gift-cards leaf (loaded by the strategy orchestrator when it activates the GIFT_CARDS domain)
- [Goal: Sell Gift Cards](references/ecommerce/gift-cards/ecom-gift-cards-goal-sell-gift-cards.md) — existing-product gate (one per site), eligibility, denomination sizing from AOV / catalog prices, no-expiry-by-default policy, and the mapping onto Create Gift Card Product

#### Shipping leaves (loaded by the Shipping dispatcher)
- [Set Up Rates](references/ecommerce/shipping/ecom-shipping-setup-rates.md)
- [Set Up Regions](references/ecommerce/shipping/ecom-shipping-setup-regions.md)
- [Set Up Pickup / Local Delivery](references/ecommerce/shipping/ecom-shipping-setup-pickup.md)
- [Add Free Shipping](references/ecommerce/shipping/ecom-shipping-free-shipping.md)
- [Optimize Rates](references/ecommerce/shipping/ecom-shipping-optimize-rates.md)
- [Fix Coverage Gaps](references/ecommerce/shipping/ecom-shipping-fix-coverage.md)
- [API Reference](references/ecommerce/shipping/ecom-shipping-api.md) — inline spec for Shipping Options + Delivery Profiles

#### Cross-cutting tracking
- [API: Recommendation Tracking](references/ecommerce/api-recommendation-tracking.md) — load BEFORE generating any recommendation; persists PROPOSED state and tracks MarkExecuting → MarkDone/MarkFailed.


</details>

> Dashboard links for eCommerce surfaces (orders, abandoned checkouts, gift cards, shipping, tax, checkout settings) are in [Stores Dashboard Navigation](references/stores/stores-dashboard-navigation.md).

---

## Events

### [Create Event](references/events/create-wix-event.md)
**Technical:** Creates an event with the Wix Events V3 API — required request body, ISO-8601 date and time settings, venue/online/TBD location and street addresses, RSVP vs ticketed registration, guest capacity, and short vs Ricos rich-text descriptions. Distinguishes Wix Events from the Calendar, Marketing Calendar and Automations APIs that share the "events" name. Key endpoint: /events/v3/events.

### [Manage Events](references/events/manage-wix-events.md)
**Technical:** Manages existing events with the Wix Events V3 API — ticket definitions and pricing (fixed, free, donation, multiple tiers), publishing a draft, cancelling, deleting, cloning, updating an event's date, counting events, and building recurring series from explicit occurrence dates. Key endpoints: /events/v3/events, /events/v3/ticket-definitions.

---

## Forms

### [Create Form](references/forms/create-form.md)
**Technical:** Creates a form with fields (name, email, etc.) using the Form Schemas API. Covers field configuration, layout, and post-submission triggers.

### [Forms Dashboard Navigation](references/forms/forms-dashboard-navigation.md)
**Technical:** Direct links to Wix Forms dashboard pages on manage.wix.com (forms list, submissions table, form builder for a specific form, standalone forms, templates, settings), pairing forms and submissions with their read APIs for "view it in your dashboard" links.

---

## Get Paid

### [Create Payment Links](references/get-paid/create-payment-links.md)
**Technical:** Creates payment links for collecting payments without a checkout flow. Covers store products (catalog items), custom line items, variants, due dates, and sending links via email.

### [How to Setup Wix Payments](references/get-paid/how-to-setup-wix-payments.md)
**Technical:** Configures Wix Payments as the payment provider. Covers eligibility checking, business verification, bank account setup, and payment method configuration (cards, PayPal, Apple Pay).

### [Payment Links for Bookings](references/get-paid/payment-links-for-bookings.md)
**Technical:** Creates payment links for unpaid bookings using Payment Links API. Links booking IDs to payment requests with proper redirect handling.

### [Get Paid Dashboard Navigation](references/get-paid/get-paid-dashboard-navigation.md)
**Technical:** Direct links to payments and invoicing dashboard pages on manage.wix.com (payment links, invoices list, new invoice, invoice settings, recurring invoices, accept-payments settings), pairing each get-paid entity with its read API for "view it in your dashboard" links.

---

## Google Ads

**Routing — Google paid-advertising campaigns for a site (Smart & Performance Max).** All flows require a Google Ads account, created once via the setup recipe. Budgets are in micros (1,000,000 = 1 currency unit). REST base: `https://www.wixapis.com/google-ads/v1`.
- **First-time setup / "connect Google Ads" / `ACCOUNT_NOT_FOUND`** → [Install and Create an Account](references/google-ads/install-and-create-account.md) (do this before anything else).
- **Suggested keywords / geo / budget / ad copy / images** → [Get AI Campaign Suggestions](references/google-ads/get-campaign-suggestions.md).
- **Create a multi-channel / lead-gen / Shopping campaign** → [Create a Performance Max Campaign](references/google-ads/create-performance-max-campaign.md).
- **Pause / resume / launch / update budget / delete / history** → [Manage Campaign Lifecycle](references/google-ads/manage-campaign-lifecycle.md).
- **Performance, conversions, search terms, per-product / per-asset metrics** → [Query Campaign Performance Analytics](references/google-ads/query-campaign-analytics.md).
- **Ad spend, fees, upcoming charges, credit balance** → [Retrieve Billing and Payment Details](references/google-ads/billing-and-payment.md).

### [Install Google Ads and Create an Account](references/google-ads/install-and-create-account.md)
**Technical:** One-time setup prerequisite for all Google Ads flows. Installs the Wix Google Ads app (`POST /v1/install-if-not-installed`) then creates the linked account (`POST /v1/accounts` with `currency`). Covers checking for an existing account (`GET /v1/accounts/current-site`, empty when none), optional promotional incentives, Merchant Center linking, and account deletion.

### [Get AI Campaign Suggestions for Google Ads](references/google-ads/get-campaign-suggestions.md)
**Technical:** Read-only Suggestions API reference — keyword themes, geo options, Smart budget tiers, PMAX budget recommendations, text/image assets, search themes, full AI campaign configs from a campaign brief (`POST /v1/campaign-suggestions`), and promotional incentive offers. Budgets in micros; generation endpoints have 60–120s SLAs.

### [Create and Launch a Performance Max Campaign](references/google-ads/create-performance-max-campaign.md)
**Technical:** Creates and launches a PMAX campaign — `PERFORMANCE_MAX`, `PERFORMANCE_MAX_LEADS`, or retail/Shopping. Generates AI text/image assets and search themes, gets a Google budget recommendation, assembles an asset group meeting Google's minimum asset counts (headlines/descriptions/images), creates in `PAUSED`, then launches. Bidding is server-enforced to `MAXIMIZE_CONVERSIONS`.

### [Manage Campaign Lifecycle](references/google-ads/manage-campaign-lifecycle.md)
**Technical:** Lists/gets campaigns and runs lifecycle actions: launch (first activation) vs resume (reactivate after pause), pause (with optional `scheduledResumeDate`/reminder), partial `UpdateCampaign` (name, budget, targeting), delete (irreversible), and read the change log / status history. Covers the 5-live-campaign cap and budget-boundary validation.

### [Query Campaign Performance Analytics](references/google-ads/query-campaign-analytics.md)
**Technical:** Reads campaign analytics via six endpoints — daily performance metrics (with previous-period trends), conversion metrics (orders/revenue/ROAS from Wix), search terms, per-product shopping performance, and per-asset PMAX-Leads metrics. Explains `campaignResourceName` vs Wix `campaignId`, the `dateRange` shape, field enums, sorting, and paging.

### [Retrieve Google Ads Billing and Payment Details](references/google-ads/billing-and-payment.md)
**Technical:** Reads billing for the site's Google Ads account (`GET /v1/payment-details`, 30s SLA): current-period ad spend (`usageAmount`), Wix service fee, coupon adjustment, `totalAmount`, billing period, and `creditBalance` (positive = credits, negative = debt). Contrasts with the account's `currentBudget`/`spentBudget`.

### [Google Ads Dashboard Navigation](references/google-ads/google-ads-dashboard-navigation.md)
**Technical:** Direct link to the Wix Google Ads dashboard page on manage.wix.com where API-created campaigns are managed.

---

## Marketing

### [Create and Publish a Social Media Post (with AI generation)](references/marketing/create-and-publish-social-post.md)
**Technical:** Creates and publishes (or schedules) a social media post to a connected channel (Instagram, Facebook, LinkedIn, TikTok, Pinterest, YouTube, Google Business Profile) via the Publisher API. Optionally generates the whole post from a free-text idea or the site's own assets (products, blog posts, events, bookings, coupons, categories), generates caption/title suggestions, and edits an existing image with AI. Verifies the channel is connected (and runs the OAuth connect flow if not), checks premium publishing quota, creates a draft item, then publishes it immediately or schedules it for a future date. Use when the user wants to create, generate, write, post, or schedule a social post, wants caption ideas or suggestions, or wants to connect a social channel (e.g. "post this to Instagram", "make a post from my product", "write a caption", "give me caption ideas", "connect my Pinterest", "schedule a post").

### [Generate a Marketing Plan and Schedule Its Posts](references/marketing/generate-and-publish-marketing-plan.md)
**Technical:** Generates a site's AI social media marketing plan (a calendar of marketing activities, each with per-channel post drafts) via the Marketing Plan API, then schedules the drafts for publishing. Covers optional marketing settings (goal, channels, tone, frequency, content pillars), asynchronous generation with polling, and generating posts for additional activities. Use for "generate a marketing plan", "create a social media plan/calendar", or "schedule my plan's posts".

### [Marketing Dashboard Navigation](references/marketing/marketing-dashboard-navigation.md)
**Technical:** Direct links to Wix marketing dashboard pages on manage.wix.com (social posts hub with drafts/scheduled/published posts, post design templates, saved designs, email campaigns list, campaign templates, campaign analytics), pairing each main marketing entity with its read API for "view it in your dashboard" links.

---

## Media

### [Upload Media to Wix](references/media/upload-media-to-wix.md)
**Technical:** Uploads images and files to the Wix Media Manager using the Import File API. Covers importing from external URLs, checking file status, and using the returned wixstatic.com URL in other APIs.

---

## Pricing Plans

### [Create and Update Pricing Plans](references/pricing-plans/create-and-update-pricing-plans.md)
**Technical:** Creates subscription and one-time payment plans using Plans API. Covers pricing models (recurring, one-time, free), trial periods, perks configuration, and plan visibility.

### [Pricing Plans Bookings Integration](references/pricing-plans/pricing-plans-bookings-integration.md)
**Technical:** Links Pricing Plans to Bookings services using the Benefit Programs API. Enables package deals and memberships that grant booking access.

### [Pricing Plans Dashboard Navigation](references/pricing-plans/pricing-plans-dashboard-navigation.md)
**Technical:** Direct links to Wix Pricing Plans dashboard pages on manage.wix.com (plans list, create plan, edit plan, new manual order, settings), pairing each main Pricing Plans entity with its read API for "view it in your dashboard" links.

---

## Restaurants

### [Wix Restaurants Setup](references/restaurants/wix-restaurants-setup.md)
**Technical:** Configures restaurant menus, sections, and items using Menus API. Covers menu structure (Menu → Section → Item), the two-step item modifier / modifier group flow, pricing, availability schedules, and ordering settings.

### [Restaurants Dashboard Navigation](references/restaurants/restaurants-dashboard-navigation.md)
**Technical:** Direct links to Wix Restaurants dashboard pages on manage.wix.com (menus, edit menu, items, online orders board, online-ordering fulfillment settings, reservations list, floor plans, reservation experiences), pairing each main Restaurants entity with its read API for "view it in your dashboard" links.

---

## Rich Content

### [Ricos Converter Service](references/rich-content/ricos-converter-service.md)
**Technical:** Validates and converts content between Ricos documents and HTML/Markdown/plain text using the Ricos Documents API. Covers plugin configuration, format conversion in both directions, and document validation.

### [Author Ricos Rich Content](references/rich-content/author-ricos-rich-content.md)
**Technical:** Hand-authoring valid Ricos rich-content JSON (the richContent/nodes tree) reused across Blog, Stores, Events, and CMS. Covers every common node shape — paragraphs, headings, lists, blockquotes, dividers, tables with cell fills, code blocks, images — plus inline text decorations and the nesting rules the format enforces.

---

## Site Properties

### [Change Payment Currency](references/site-properties/change-payment-currency-site-properties.md)
**Technical:** Updates the site-level payment currency (store billing currency) using Site Properties API, including the required request body shape and field mask. Also covers the site time zone and primary language, which use the same field-mask PATCH with top-level field names — not nested `locale.*` paths.

### [Site Settings Dashboard Navigation](references/site-properties/site-properties-dashboard-navigation.md)
**Technical:** Direct links to the site-settings dashboard pages on manage.wix.com (settings hub, website settings, language & region), paired with the Site Properties read API.

---

## Sites

### [Create Site from Template](references/sites/create-site-from-template.md)
**Technical:** Creates new Wix sites from templates using account-level APIs. Covers template search, site creation, and publishing. Not for headless sites.

### [Create Headless Site](references/sites/create-headless-site.md)
**Technical:** Creates a Wix Headless site (headless business) with one account-level API call — site, Wix Business Solution apps, and a configured OAuth client.

### [Query Sites](references/sites/query-sites.md)
**Technical:** Lists and queries all sites associated with a Wix account using Sites API. Covers pagination with cursor-based navigation.

### [Site Import](references/sites/site-import.md)
**Technical:** Drives the autonomous Wix Site Import agent over REST (`/site-import/v1/imports`) to migrate a store/site from another platform (Shopify, WooCommerce, Magento, or any URL) into Wix. Covers Start/Poll/Reply/Cancel, relaying agent questions and progress in plain language, handling `DEPLOYED`/`FAILED`/`AUTH_EXPIRED`/`SESSION_EXPIRED` states, and post-deploy follow-up changes. Use when the user wants to import, migrate, or clone an existing store/site into Wix.

### [Sites Dashboard Navigation](references/sites/sites-dashboard-navigation.md)
**Technical:** Direct links to the account-level My Sites list (manage.wix.com/account/websites) and per-site dashboard homes, paired with the Query Sites read API.

---

## Stores

### [Add Store Pages to Site](references/stores/add-store-pages-to-site.md)
**Technical:** Adds missing checkout and cart pages to a site when Stores app is installed. Used when store pages are missing after migration or setup issues.

### [Bulk Create Products with Options](references/stores/bulk-create-products-with-options.md)
**Technical:** Uses bulk products endpoint to create multiple products with inventory in a single request. Handles variant generation from options, media format requirements, and error handling for partial failures.

### [Create Product from Image](references/stores/create-product-from-image.md)
**Technical:** **MANDATORY entry point** for any "create product from image" or "create product from photo" request. STEP 1 auto-detects the site's catalog version (V1/V3) via the provision endpoint, then runs the matching flow inline — V3 supports up to 3 images, info sections, SEO, options/variants, and atomic creation; V1 supports a single image, simple product, and a separate media-attach call. Combines Media Upload + LLM analysis + Product Creation + (V1 only) Add Product Media in one self-contained recipe.


### [Create Product (Catalog V1)](references/stores/create-product-catalog-v1.md)
**Technical:** Create products using the Catalog V1 Products API. Use this recipe when the site's catalog version is CATALOG_V1. Covers simple product creation, product with options, and key V1 request structure differences from V3.

### [Create Product with Options (Catalog V3)](references/stores/create-product-with-options-catalog-v3.md)
**Technical:** Single product creation with options using Catalog V3 Products API. Covers option types (TEXT_CHOICES, SWATCH_CHOICES), choice configuration, and automatic variant generation.

### [Find Products (Query and Search, Catalog V3)](references/stores/find-products-query-and-search-catalog-v3.md)
**Technical:** Find, search, query, and list products from a Wix Store using Catalog V3 Search Products and Query Products endpoints. Explains when to use each endpoint, correct fields enum values, filtering, sorting, and paging.

### [Query Products (Catalog V1)](references/stores/query-products-catalog-v1.md)
**Technical:** Query and list products from a Wix Store using the Catalog V1 Query Products endpoint. Use this recipe when the site's catalog version is CATALOG_V1. Covers basic queries, filtering, sorting, and paging.

### [Setup Online Store (Catalog V3)](references/stores/setup-online-store-catalog-v3.md)
**Technical:** Initializes a Stores catalog with Catalog V3 Products API, bulk products endpoint, and Categories API. Covers product creation, option configuration, variant management, and category assignment.

### [Update Product Pre-Order](references/stores/update-product-pre-order.md)
**Technical:** Manages pre-order settings for product variants using V3 Inventory API. Covers enabling/disabling pre-orders, setting messages, configuring limits, and handling trackQuantity requirements.

### [Update Product with Options](references/stores/update-product-with-options.md)
**Technical:** Modifies existing products and variants using Catalog V3 Products API. Covers adding/removing option choices, variant-specific pricing, and revision-based updates to prevent conflicts.

### [Stores Dashboard Navigation](references/stores/stores-dashboard-navigation.md)
**Technical:** Direct links to Wix Stores and eCommerce dashboard pages on manage.wix.com (products list, edit product, categories, inventory, orders list, order details, abandoned checkouts, gift cards, shipping, tax), pairing each main Stores/eCommerce entity with its read API for "view it in your dashboard" links.

<!-- chapter:end slug=wix-manage -->

---

<!-- chapter:begin slug=rp-discovery position=9 -->

## 9. rp-discovery

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-discovery/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-discovery/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-discovery.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-discovery
description: >-
  Discovers and documents the source platform schema (entities, fields, relationships) for
  a migration project. Use when capturing source structure before mapping to Wix.
---

# rp-discovery

Discover and document the source-platform schema for the active migration project.

## Purpose

Use this skill to inspect the source system, identify entities, relationships, fields, identifiers, media, rich content, and platform-specific constraints. Examples include Shopify, WordPress, WooCommerce, and custom CMS platforms.

This skill owns the **platform-agnostic discovery process and its output contract**
(`source-profile.md` + `source-schema.json`). Platform-specific details — how to capture a
given source, its auth model, REST quirks — live in a dedicated **source adapter** skill,
not here. For WordPress / WooCommerce, that adapter is `rp-source-wordpress`. To support a
new platform, add a sibling adapter (e.g. `rp-source-shopify`) and leave this skill
unchanged.

## Inputs

Expected inputs may include:

- `migrations/<project>/orchestration/run.json`
- `migrations/<project>/orchestration/decisions.json`
- source site/app URL
- source platform name when it cannot be inferred
- source acquisition mode when the platform offers multiple read paths
- source API docs
- credentials, tokens, or local dump files when available
- export files when the run is file-based rather than URL-based
- current project under `migrations/<project>/`
- project-local config under `migrations/<project>/config/`

## Config gate before capture

Before running source capture, verify the project-local config files created by
`replatform`:

- `config/wix.env` should always exist with `WIX_SITE_STRATEGY`, `WIX_SITE_ID`, and
  `WIX_AUTH_TOKEN` keys, even though discovery itself may not use Wix credentials yet.
- `config/source.<platform>.env` should exist once the source platform is known. For
  WordPress this is `config/source.wordpress.env`.

If a required key is missing or blank, ask the user for that value and fill the config
file for them before continuing. Ask one value at a time. Never print secret values back
to the user; report only present/missing.

Treat `migrations/<project>/config/*.env` as secret-bearing once they may contain real
values. Do not inspect them with whole-file reads that echo contents into tool output.
Use secret-safe checks only: existence, required key names, and `present` / `blank` /
`missing` status.

## Site creation precedence

If this skill encounters conflicting Wix guidance about how to create a `new site` +
`headless` destination, `replatform`'s migration contract wins.

- Route headless destination creation back to the **Wix CLI headless scaffold** defined in
  `replatform` → "Headless site creation" (`npm create @wix/new@latest headless`). This is
  the verified way to get a genuine headless site; the account-level Projects API is
  deprecated for this workflow (it produced non-headless sites).
- Discovery is source-side and does not create the site itself — just defer to that section.

## Workflow

1. Confirm the active project under `migrations/<project>/`.
2. Start from the source URL when available and try to identify the source platform
   yourself before asking the user. Use lightweight signals such as a REST index,
   headers, HTML/application markers, or platform-specific route patterns. Only ask the
   user to name the platform if detection remains inconclusive.
3. Once the platform is inferred, resolve the acquisition mode before requesting source
   credentials when the platform has materially different read paths.
   - For Shopify URL-based migrations, ask whether to use the `Admin API` or only
     publicly available `storefront` data.
   - For WordPress / WooCommerce URL-based migrations, ask whether to import `public
   content only` or `also include private/authenticated data`.
   - Only the WordPress / WooCommerce `also include private/authenticated data` branch
   should trigger a credentials request. The `public content only` branch proceeds
   without credentials and should be described as limited to public data. For WooCommerce,
   this branch should still probe public Store API catalog routes such as
   `/wc/store/v1/products` and `/wc/store/v1/products/categories` before declaring
   commerce out of scope.
   Treat file/export ingestion as a separate flow that starts from user-provided files
   instead of a site URL probe; do not offer exports as a third option in the URL-based
   acquisition-mode question.
4. Then select the matching source adapter skill (e.g. `rp-source-wordpress` for
   WordPress / WooCommerce, `rp-source-csv` when the run is file-based). If no adapter
   exists for the platform, capture entities manually following the same output contract.
   - **File-based runs** (`sourceMode=files_only`, `sourcePlatform=csv`) use
     `rp-source-csv` regardless of which system produced the files; that adapter identifies
     the originating vendor from the header row. There is no acquisition-mode question and
     no credentials request for this path.
5. Run the adapter's capture step to produce a raw, machine-captured dump under
   `<migrations-root>/<project>/data/<source>-discovery/`. For WordPress, the capture
   script lives in `rp-source-wordpress/scripts/` — run it from that skill directory
   (see `rp-source-wordpress` Capture section and `CONVENTIONS.md`). The adapter owns the
   capture mechanics, auth model, and platform quirks; this skill consumes its output.
   For long runs, pass `--progress-log <path>` and poll it per
   `CONVENTIONS.md#progress-log-polling`.
   - Distinguish **supported** entities (advertised by the source) from **used** entities
     (those with `recordCount > 0`). Entities advertised but empty should be flagged, not
     mapped as if they hold data.
   - A capture made without credentials is usually incomplete (gated entities, private
     fields, PII return 401/403). Do not treat an unauthenticated capture as
     authoritative — the adapter documents what auth a complete run requires.
   - For WordPress / WooCommerce captures, read `data/wp-discovery/skipped-routes.json`
     when present. Treat it as the canonical route-scope audit trail: skipped routes are
     evidence, not source entities, unless they were explicitly force-included by an
     audited override.
   - For CSV captures, the capture script lives in `rp-source-csv/scripts/csv-discovery.js`
     and takes the **whole file set in one run** (`--file` is repeatable) so roles and split
     files resolve together. Read `data/csv-discovery/fileset.json` — it is the canonical
     machine capture, and `source-schema.json` is synthesized from it:
     - carry `sourceFiles[]` (with `role`, `vendor`, `partOf`), `vendor`, `dialect`, `drift`,
       `mappingHints`, and `csvInputRoot` into `sourceMeta`, keeping file paths **relative**
       to `csvInputRoot` so the project stays movable;
     - give every entity an `origin` (`file-rows` | `row-group` | `column-values`) with the
       parameters that origin needs, and set `hierarchical: true` on nested derived entities
       so the mapper's faithfulness-ledger rule fires;
     - surface `drift.unmappedColumns` as `unknowns` so the mapper handles them explicitly;
     - **honor `halt: true`.** An ambiguous layout, an unknown file role, conflicting
       split-file headers, or a near-miss vendor detection is a question for the user, not
       something to resolve by picking the highest-scoring candidate. The warning text names
       the decision to put to them.
6. Capture field-level schema details, including type, cardinality, requiredness, and example values.
7. When bundled Wix domain knowledge recognizes a source route or source entity, annotate
   the discovered entity with `sourceMeta.candidateTargetRefs[]` such as
   `["stores/product"]`. Discovery must still record source facts only; these refs are
   mapper hints, not target decisions.
8. Note operational constraints such as pagination, rate limits, auth model, and incremental sync options.
   If the source base URL or discovered media/file URLs use `localhost`, `127.0.0.1`, or
   another private-only host, record a **media reachability note** in `source-profile.md`.
   Localhost is fine for discovery and local source reads, but Wix Media import is
   URL-based and Wix servers cannot fetch the user's localhost. This is an optional
   preparation step and, as far as we know today, only affects media import. State the two
   acceptable choices:
   - expose the source with a public HTTPS tunnel such as ngrok before live media import
   - skip/defer media import while continuing non-media entities

   Include concise ngrok setup instructions when relevant:

   ```bash
   brew install ngrok
   ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
   ngrok http 8090
   export WP_BASE_URL=https://<id>.ngrok-free.app
   ```
9. Synthesize the raw capture into the normalized artifacts below.

## Artifacts to create or update

- `migrations/<project>/discovery/run.json`
- `migrations/<project>/discovery/entities/`
- `migrations/<project>/discovery/warnings.json`
- `migrations/<project>/discovery/llm-handoff.json`
- `migrations/<project>/orchestration/checkpoints.json`

- `migrations/<project>/data/<source>-discovery/`: raw machine-captured output from the source adapter. Treated as evidence, not a hand-off artifact — downstream skills reference it for traceability but do not read it wholesale.
- `migrations/<project>/source-profile.md`: source platform, access method, limits, auth, and operational notes. Synthesized from the raw capture. Capture the operational facts the adapter documents (auth model, pagination, rate limits) so `rp-import-codegen` has them without re-deriving.
- `migrations/<project>/source-schema.json`: machine-readable schema for entities and fields. **Synthesized from the raw capture** — this and `source-profile.md` are the canonical hand-off to `rp-mapper`. Include traceability pointers so the mapper can drill into a specific entity's raw file when needed:
  - top-level `rawDiscovery`: relative path to the raw capture dir, e.g. `data/wp-discovery/`.
  - per-entity `rawFile`: file name within that dir, e.g. `wp-v2--posts.md`.
  - per-entity `recordCount` and `inUse` so consumers can distinguish supported vs. actually-used entities.
  - per-entity `relations` derived from the source-declared relationships in the raw capture, so relationships are evidence-backed rather than guessed. Each relation should carry an `evidence` pointer back to the source signal it came from.
  - For WordPress / WooCommerce, synthesize entities only from sampled `backend_data` and
    accepted `backend_metadata` route artifacts. Do not synthesize entities from routes
    listed in `skipped-routes.json` unless the skipped-route record has
    `includedByOverride: true`; in that case, include `originalDiscoveryCategory`,
    `includedByOverride: true`, and `overrideReason` when present in the entity
    `sourceMeta`.
  - Follow the adapter's `source-schema.example.json` for the shape (e.g. `rp-source-wordpress/source-schema.example.json`). It is a template to follow, not a strict schema to validate against — keep the platform-agnostic core stable and push platform quirks into each entity's open `sourceMeta` blob.
- Optional supporting notes under `migrations/<project>/research/` if needed.

## Output quality rules

- Separate confirmed facts from assumptions.
- Record per-entity volume (record counts) so downstream skills know what the site actually uses, not just what it supports.
- Preserve source-specific identifiers exactly.
- Include enough detail for downstream mapping and code generation.
- Flag unknowns explicitly instead of inventing structure.

<!-- chapter:end slug=rp-discovery -->

---

<!-- chapter:begin slug=rp-execute-import position=10 -->

## 10. rp-execute-import

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-execute-import/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-execute-import/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-execute-import.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-execute-import
description: >-
  Runs the generated extract/import pipeline and records execution results. Use when setup
  and codegen are complete and the user has approved the execution plan.
---

# rp-execute-import

Execute the generated migration pipeline and capture import results.

## Purpose

This skill runs the generated extract/import pipeline for the active project once setup and code generation are complete.

## Required inputs

- generated code under `migrations/<project>/src/`
- `migrations/<project>/execution/execution-manifest.json`
- `migrations/<project>/execution/llm-handoff.json`
- `migrations/<project>/execution/review/import-plan.md`
- `migrations/<project>/execution/review/code-safety-review.md` when `SAFE_MODE=true` or
  `DRY_RUN=true`
- `migrations/<project>/setup/setup-verification.json`

## Preconditions

Do not proceed until:

- setup verification shows required items are passed or accepted; an unrecovered blocker
  halts to needs-user
- reader, transform, and writer code exist for the intended entities
- execution artifacts define batching, retries, checkpoints, and write order clearly
- when `SAFE_MODE=true` or `DRY_RUN=true`, the post-codegen code-safety review exists and
  has been accepted before execution approval is sought
- the **execution plan report has been presented and the user has accepted it** (see
  below)

Prefer the machine-readable execution artifacts above. `execution/review/import-plan.md`
is a human-facing review artifact, not the primary execution contract.

Before live import and again before final reporting, validate
`execution/review/import-plan.freshness.json` with the deterministic freshness helper:

```bash
node skills/replatform/scripts/artifact-freshness.js check migrations/<project> \
  --domains-dir skills/replatform/resources/rp-target-wix/domains \
  --delta execution/review/import-plan-delta.md
```

If the check is stale, do not silently continue from the old approval text. Regenerate
`execution/review/import-plan.md` or append and present
`execution/review/import-plan-delta.md`; completion reports must reference the latest
accepted plan or delta.

## Execution plan & user acceptance (required gate)

This gate precedes **all** writes to the user's site — both `rp-execute-setup`
provisioning and this import. Before writing anything, produce a human-readable
**execution plan report** and obtain explicit user acceptance. Do not write anything until
the user accepts. The report must show:

- **Setup changes to be made first:** apps to install (Blog / Members / Wix-Data enabler),
  Wix Data enablement, and collections to create — so the user sees the site changes, not
  just the content writes.
- **What will be imported and where:** each source entity → its Wix target (app or
  collection) with record counts — e.g. posts → Wix Blog (1088); episodes →
  `PodcastEpisodes` (86); categories/tags → Blog taxonomies; media → Media Manager (~1499).
  Make media reporting explicit:
  - referenced media imported through entity-native background ingestion
  - referenced media imported through Media Manager first
  - unattached media skipped by default unless explicitly in scope
- **What will NOT migrate cleanly / needs manual action:** the lossy and blocked items,
  drawn from the mapping plan's faithfulness ledger and any `setup/setup-verification.json` items
  still manual or blocked — e.g. category hierarchy flattened, comments anonymized,
  drafts absent without auth, storage-plan upgrade required. **This must also include any
  target with no verified Wix primitive** — state whether it falls back to a generic CMS
  collection, to an `unverified`/best-effort runtime-derived call, or is skipped. Nothing
  unverified or lossy may be written without first appearing here for consent.
  Coupons follow the same native-first rule as other native Wix entities: prefer native
  Wix Coupons, and mention CMS fallback only for truly unsupported coupon semantics.
  - **Always state the analytics-data exclusion explicitly.** Historical analytics data —
    traffic / visitor stats accumulated on the source — is **out of scope** and is **not**
    imported (see "Out of scope" below). Call this out in the plan so the user knows before
    accepting that analytics data will not migrate; do not let it pass silently.
- **Order & idempotency:** the write order and how re-runs dedupe. Be explicit that
  source IDs are the stable migration key, while many native Wix target IDs are
  server-assigned. The plan must state whether each entity re-run resolves via a
  client-controlled source-id field on the target or via the durable local
  `sourceId -> targetId` crosswalk. The plan must state the crosswalk authority
  (`local`) and CMS mirror mode (`none`, `download`, `upload`, or `download-and-upload`).
- **URL preservation:** for public routed entities, state that source URL artifacts will
  be captured locally under `state/url-preservation/`. List the public entity types whose
  base paths and slugs will be tracked, any entity whose target route is deferred to the
  website-builder phase, and whether redirects will be recorded as plans. The current
  import phase must say that redirects and site routing are **not applied**.
- **Notification mute (spec 0012).** State that site notifications will be muted before
  migration writes begin when mute is in effect. For a **new site** this is a one-line
  factual disclosure (the mute is unconditional). For an **existing site that opted in**
  (`WIX_MUTE_NOTIFICATIONS=on`) the disclosure is **mandatory and prominent** and must
  state all three points:
  - all site notifications will be muted before migration writes begin;
  - visitor activity during the migration window (e.g. contact-form submissions,
    Back in Stock alerts) will **not** generate notifications;
  - the flow does **not** unmute automatically — after the migration the owner must
    re-enable notifications themselves, or simply ask the agent to unmute at any point.

  The owner accepting the plan is the consent for muting their live site. If the owner
  rejects the mute line, revert to the existing-site default (`off`) and regenerate the
  affected artifacts before seeking acceptance again.

Persist this in `execution/review/import-plan.md` (or a sibling report). This is the defined approval
checkpoint: the job pauses, surfaces the plan to the user, and resumes **only** on
accept. Nothing is written before acceptance.

If `SAFE_MODE=true` or `DRY_RUN=true`, this execution-plan approval gate comes only after
the separate post-codegen code-safety review checkpoint has passed. Do not merge the two
checkpoints into one prompt.

## Out of scope — analytics (future enhancement)

**Do not attempt to import analytics data.** Historical traffic / visitor statistics —
the accumulated analytics records on the source (page views, sessions, visitor counts,
time-series reports) — are **not** part of the migration. Do not generate readers,
transforms, or writers for them. (This is about the *data*, not analytics
configuration/setup such as tracking tags — that is a separate concern and not what this
exclusion covers.)

- **Surface it before execute.** This exclusion must appear in the execution-plan report's
  "What will NOT migrate" list (see the gate above) so the user is told **before** they
  accept and we begin writing — not discovered afterward.
- **Future enhancement.** Analytics migration is a deferred scope item, not a permanent
  limitation. If/when a faithful source→Wix analytics path exists, revisit and lift this
  exclusion. Until then, treat analytics as explicitly skipped.

## Execute the generated scripts — never an agentic MCP flow (required)

The **import** is performed by **running the generated artifact** (`node` the project's
entrypoint under `migrations/<project>/src/`), which writes to Wix via its own transport
(`fetch` + injected credentials to `www.wixapis.com`, or the Wix client SDK). The agent
**must not** perform the import writes itself by issuing per-record Wix MCP calls
(`CallWixSiteAPI`) and hand-translating shapes.

(Scope: this rule is **import-specific**. Setup execution (`rp-execute-setup`) may
currently use the agent+MCP for provisioning writes — an interim decision, with other
options still under discussion.)

Why the import must run the artifact:

- **Reproducibility & idempotency.** Re-runs, resume-from-checkpoint, write ordering, and
  dedup keyed by source ID live in the artifact. For native Wix entities whose target IDs
  are server-assigned, that means the artifact must maintain and consult the local
  `state/crosswalk/crosswalk.ndjson` authority. An agent reconstructing writes ad hoc
  bypasses all of it — a bulk, restartable data pipeline can't be driven by hand per
  record.
- **Verified shapes.** The artifact calls `rp-target-wix`'s verified primitives. An agent
  rebuilding request bodies live re-opens the exact shape-bug class we eliminated
  (Ricos plugin enum case, oversized-HTML tag body, `heroImage.id`).
- **MCP may be absent at runtime.** Interactively-authenticated MCP servers can be missing
  in headless/cron runs, so MCP can't be depended on as the write transport regardless of
  whether the runtime is a pure script-runner or an agent. Either way the writes should
  flow through the tested artifact, not be reconstructed by the model.
- **Validation honesty.** Writing by hand via MCP leaves the artifact's own auth, request
  execution, async-media polling, retry, and checkpoint code unexercised — a green test
  then says nothing about the path real users get. The Wix MCP's role here is
  grounding/verification at codegen time and the one-time live contract test in
  `rp-target-wix`, **not** the import transport.

Consequence for credentials: the artifact needs real Wix write credentials to run. If they
are absent, **halt to needs-user** — do **not** substitute the agent's MCP account auth to
"get the writes done." Missing credentials is a blocker to surface, not a path to route
around.

This skill should execute the machine import artifacts and generated entrypoints. It
should not re-decide write strategy live when `execution/execution-manifest.json`
already defines the ordered task graph and write contract.

## Config files

Before running the generated entrypoint, verify the project-local config files exist and
contain required values:

- `migrations/<project>/config/wix.env`
  - `WIX_SITE_STRATEGY`
  - `WIX_SITE_ID`
  - `WIX_AUTH_TOKEN` or another generated-code-supported Wix auth key
  - `DRY_RUN`
  - `SAFE_MODE`
  - `SAFE_MODE_PHONE_NUMBER` when `SAFE_MODE` is enabled
- `migrations/<project>/config/source.<platform>.env`
  - platform-specific source values, for example WordPress:
    `WP_BASE_URL`, `WP_USERNAME`, `WP_APPLICATION_PASSWORD`

The generated script should load these files and then allow process env to override them.
`WIX_SITE_STRATEGY` is always required. `WIX_SITE_ID` is required before execution writes
begin; if the strategy is `new` and the site has not been created yet, halt to needs-user
and return to the site-creation step rather than assuming an existing-site flow. Never
print secret values.

`WIX_AUTH_TOKEN` is the canonical Wix auth key in project-local config. It may have been
used earlier for account-level site creation and may also power site-level writes when
the generated runtime supports that same key. If the generated import path needs a
different Wix credential, name that key explicitly in the generated artifacts.

If execution is blocked because a RePlatform `new site` + `headless` target was never
created, route back to the **Wix CLI headless scaffold** step defined in `replatform` →
"Headless site creation" (`npm create @wix/new@latest headless`). The account-level Projects
API is deprecated for this workflow (it produced non-headless sites).

When the accepted next run is dry-run, missing or blank `WIX_AUTH_TOKEN` and
`WIX_SITE_ID` are not blockers unless a local artifact requires the site ID as a stable
namespace. Report them as `would_block_live`, do not mint a Wix CLI token for dry-run, and
make the execution plan state that a later live run needs separate acceptance and valid
credentials.

If `DRY_RUN=true`, do not override it with `--no-dry-run` for setup probes, setup
provisioning, demo-catalog cleanup, extraction-side verification against Wix, or import
writes unless the user has explicitly approved leaving dry-run for that step or phase.
Prefer to avoid that override when a dry-run artifact or report can be produced instead.
The upstream new-site creation exception does not authorize this skill to make any other
live Wix API call.

Treat `migrations/<project>/config/*.env` as secret-bearing once they may contain real
values. Do not inspect them with whole-file reads that print contents into tool output;
check only existence and required-key status (`present`, `blank`, `missing`).

For CLI-scaffolded headless sites, acquire the site write token from the scaffolded
frontend folder with:

```bash
npx @wix/cli@latest token --site "$WIX_SITE_ID"
```

Persist the returned token as `WIX_AUTH_TOKEN` and send it as a Bearer token. The same Wix
CLI account should create the site and mint this token.

## Workflow

1. Resolve the active project.
2. Review the machine execution artifacts and generated code; present the execution plan
   report and obtain acceptance (see above) before any write.
2b. Prepare deterministic local execution state before any setup/import write:

   ```bash
   node skills/replatform/scripts/execution-state-prepare.js migrations/<project>
   ```

   This validates `execution/execution-manifest.json`, initializes or validates
   `state/crosswalk/crosswalk.ndjson`, rebuilds crosswalk indexes, initializes
   `state/attempts/write-attempts.ndjson`, initializes `state/url-preservation/`
   artifacts when URL preservation is enabled, and blocks CMS mirror download flows unless
   local state is absent and explicit CMS mirror rows have been provided to seed it.
3. Run a safe validation path first when possible, such as dry-run, sample batch, or read-only validation.
   For dry-run, invoke the same generated setup/import entrypoints with `DRY_RUN=true` or
   `--dry-run`. Do not use an agent/MCP hand-built substitute. The shared Wix runtime must
   skip Wix calls at `wix.send`, write `state/attempts/wix-request-captures.ndjson`, and
   keep placeholder target IDs out of `state/crosswalk/crosswalk.ndjson`.
   Do not switch to `--no-dry-run` unless the user explicitly approves leaving dry-run for
   that phase.
   If media import is in scope and source media URLs are local/private (`localhost`,
   `127.0.0.1`, Docker-only hosts, etc.), do not treat a successful dry-run as proof that
   live media import can work. Wix Media import fetches URLs from Wix servers, so the user
   must either expose the source through a public HTTPS tunnel or skip/defer media. This
   is optional and, as far as we know today, only affects media import.
   For Stores products carrying `subscriptionDetails`, the generated dry-run must execute
   the same transform path as live import and call the vendored
   `validateStoresProductSubscriptionDetails` helper before writes. Known contract
   failures such as missing cadence or an invalid required nested field are record-level
   preflight failures/deferred outcomes; they must be counted and reported without sending
   that record to Wix.
   If the run needs live Stores evidence during setup or recovery, use the shared
   verification CLI and persist its JSON artifact:
   `node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-count --artifact migrations/<project>/execution/stores-product-count-verification.json`
   or `stores product-by-source-marker --marker-path <path> --marker-value <value>`.
   Do not write migration-local verification snippets for Stores product counts,
   source-marker lookups, subscription probes, or probe cleanup.
3b. **Clear the default demo catalog first.** A freshly provisioned Wix Stores catalog (and
   `wix-headless`'s seed) ships ~12 placeholder demo products + demo categories. Delete them
   before importing (keep the system `All Products` category) so the final store holds only
   the migrated data — otherwise a clean 100-product import reads as 112. Applies in both
   delivery modes.
4. **Run source extraction first** using the generated extraction entrypoint (for example
   `node src/extract/run-extract.js`). This step writes durable source files under the
   project and must complete before the write phase unless the extraction artifacts are
   already present and accepted for resume.
5. **Execute the import by running the generated import entrypoint** (for example
   `node src/import/run-import.js`) with credentials injected via config/env. The import
   must read from the extracted files on disk — not by re-reading the source into memory,
   and not by issuing writes through the agent/MCP.
   For targeted recovery, run the generated main import entrypoint with its selective
   resume flags rather than writing a migration-local one-off script:
   - `--entity <entity>`
   - `--source-type <subtype>`
   - `--missing-only`
   - `--failed-only`
   - `--deferred-only`

   `--missing-only`, `--failed-only`, and `--deferred-only` are mutually exclusive. The
   runner must print or persist the selected record set summary before writes begin. For
   native Wix entities, a local crosswalk hit must skip the create idempotently even if the
   record was selected earlier.
   If the execution manifest enables CMS mirror download, fetch CMS rows before this step
   and pass them to the deterministic state-preparation layer; the generated import may use
   only local crosswalk state after preparation succeeds.
6. Capture compact reports, audit-log references, errors, retries, skipped records, and
   checkpoint information from the shared runtime outputs. If a dependency phase has
   systemic failures, stop before downstream writes that depend on it. Example:
   product-category failures must block product writes unless the accepted execution plan
   explicitly says category assignment is best-effort.
   Safe-mode outputs must be summarized by counts and paths only: include skipped
   safe-mode blocked record counts, the project-relative
   `state/safe-mode/blocked-records.ndjson` path when present, and the
   `state/safe-mode/email-replacements.ndjson` row count/path when replacement rows were
   written. Do not print original email addresses or phone numbers from the recovery
   ledger.
7. Save durable execution artifacts, including the authoritative completion artifact and
   deterministic user-facing completion summary.

## Required final report contents

When execution finishes, the deterministic completion outputs must explicitly include:

- the destination site's **dashboard URL**
- the destination site's **editor URL** only when editor work was actually performed or
  the next required step is explicitly in the editor
- the current **preview URL** only when public route/site verification is relevant to the
  completed work
- whether store routes such as product/cart/checkout were verified or still unverified
- URL preservation counts: base paths captured, URL ledger rows written, redirect plans
  recorded, unresolved public URLs, and manual-review URL rows
- safe-mode counts: email fields replaced, phone fields replaced, records skipped with
  `SAFE_MODE_SUSPICIOUS_EMAIL`, and the project-relative safe-mode ledger paths. Do not
  include original email addresses or phone numbers.
- dry-run counts when `dryRun: true`: requests built, Wix calls skipped, would create,
  would update, would delete, would require live lookup, and local validation failures.
  Do not report dry-run rows as created, updated, imported, installed, published, or
  verified in Wix.
- an explicit statement that URL redirects/site routing were not applied in the current
  import phase when `urlPreservation.applyRedirects` is `false`
- a plain-language distinction between:
  - **catalog/data imported successfully**
  - **website/homepage built successfully**
- **notification-mute state (spec 0012) — in every terminal report** (completed, halted
  to needs-user, or aborted; an aborted run is exactly the case where the owner is least
  likely to remember the mute happened). Derive "was muted" **only from recorded state**
  — the `mute-site-notifications` item in `setup/setup-verification.json` and the import
  run's preflight log entries — never from strategy/config inference. When a successful
  mute is recorded, state in plain language that **all site notifications are currently
  muted** and stay muted until re-enabled; that the owner can **simply ask the agent to
  unmute them** (on request the agent calls `unmuteSiteNotifications` and confirms
  `muted: false` via `getSiteMuteState`); and how to re-enable manually. For existing
  sites this line is mandatory and prominent. If no successful mute is recorded, do not
  claim the site is muted — for a run where mute was in effect, that is a blocker that
  should already have stopped the import at the preflight.

Do not report a successful catalog import as if it automatically means a finished
website. If the homepage/root preview is still blank or no site shell has been built,
say that explicitly in the final report.

State the **delivery mode** explicitly (see `replatform` → "Delivery mode"):

- **`management` mode (default):** report that the deliverable is a **Wix-managed headless
  backend** — data migrated and manageable from the dashboard, with **no customer-facing
  website built (by design)**. Do not frame the missing storefront as an incomplete result.
  You may note that a storefront can be generated on request (website mode, via
  `wix-headless`), but do not build one unless the user asks.
- **`website` mode:** report the **storefront URL** produced by `wix-headless` and confirm
  the released site serves the migrated catalog (not demo data).

## Completion artifact authority

At the end of the run, artifact authority should be explicit:

1. `execution-log.md` is authoritative for chronology and operator/debug context only
2. `execution/completion-report.json` is authoritative for final outcome
3. `execution/review/completion-summary.md` is the deterministic user-facing rendering of
   `execution/completion-report.json`

This skill should not treat `execution-log.md` as the canonical source for final
imported/skipped/failed counts when `completion-report.json` exists.

## Localhost media before live import

When source media URLs are local/private, ask the user to choose one path before live
media writes:

- Expose the source with a public HTTPS tunnel such as ngrok:

  ```bash
  brew install ngrok
  ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
  ngrok http 8090
  export WP_BASE_URL=https://<id>.ngrok-free.app
  ```

- Or skip/defer media import and record the effect on hero images, galleries, downloadable
  files, and other media-dependent references.

Non-media entities may continue if the execution plan clearly excludes or defers media.

## Artifact to create or update

- `migrations/<project>/execution-log.md`
- `migrations/<project>/execution/live-import-summary.json`
- `migrations/<project>/execution/completion-report.json`
- `migrations/<project>/execution/review/completion-summary.md`
- `migrations/<project>/execution/recovery-log.json` for every resumed, partial,
  missing-only, failed-only, or deferred-only run
- audit/report artifacts emitted by the shared import runtime

## Recovery artifact contract

Targeted recovery is a first-class import mode, not a side script. Every selective,
resumed, partial, missing-only, failed-only, or deferred-only run must append one entry to
`execution/recovery-log.json` and update `execution/live-import-summary.json` through the
same shared summary writer used by the main import path.

Each recovery entry must include:

- recovery id
- timestamp
- selection filters
- reason
- records selected
- records attempted
- imported
- already present
- failed
- deferred
- crosswalk changes
- summary changes
- operator-visible outcome
- links to detailed logs

Do not overwrite earlier recovery entries. Failed or deferred records from an earlier
attempt must remain visible after a later successful recovery; the newer recovery entry and
summary delta explain what changed.

## Completion report contract

This skill should expect the shared import runtime to emit a machine-readable completion
artifact and should preserve it as the primary post-run result.

`execution/completion-report.json` should be the authoritative source for:

- final run status:
  `complete | complete_with_warnings | complete_with_recovered_records |
  complete_with_deferred_records | incomplete_with_failures |
  incomplete_with_mismatches | aborted`
- final completeness counts by entity and subtype: extracted, in-scope, attempted,
  imported, already present by crosswalk, deferred, failed, skipped out of scope, and
  unexpected skipped
- grouped skipped/deferred outcomes
- grouped failure outcomes
- mismatch rows where source, import, and crosswalk counts do not reconcile
- URL preservation summary counts and artifact paths when URL preservation is enabled
- dashboard/editor/preview destinations
- artifact references for logs and diagnostics

`execution/live-import-summary.json` should carry the same `entityCompleteness` accounting
contract during/after the live import so short imports are visible before any manual count
comparison. For every mapped entity/subtype row, the runtime must reconcile:

```text
imported + alreadyPresentByCrosswalk + deferred + failed == inScope
```

Any non-zero deferred, failed, or unexpected skipped count for an in-scope mapped entity
class must appear in the completion headline. Any mismatch must set the final status to
`incomplete_with_mismatches` unless the run was already `aborted`.

When more than one status applies, choose the highest-severity status in this order:

1. `aborted`
2. `incomplete_with_mismatches`
3. `incomplete_with_failures`
4. `complete_with_deferred_records`
5. `complete_with_recovered_records`
6. `complete_with_warnings`
7. `complete`

The user-facing completion summary should be rendered deterministically from that artifact.
The completion report should be produced from the runtime's in-memory counters for the
current `runId`, with audit-log aggregation used only as a verification/fallback path.
When audit events are read, filter by `runId`; never infer final counts by counting every
line in an append-only `import-audit.ndjson` that may include dry-runs, retries, and
previous recovery passes.

## Minimum execution log contents

- run timestamp
- command or entrypoint used
- extracted source location / manifest used
- entities processed
- records read, transformed, written, skipped, failed
- retry behavior
- blocking errors
- follow-up remediation
- member activation status, when members are in scope (activation automation created and
  enabled post-window? label waves applied — which label, how many contacts per wave?) —
  see rp-target-wix members guidance; never mass-send set-password emails
- dashboard URL
- editor URL when relevant
- preview URL when relevant
- post-import route verification status (for example product/cart/checkout reachable or
  not yet installed)
- URL preservation artifact paths and counts, including redirect plans and unresolved
  public URLs
- explicit state classification: `catalog imported`, `site shell built`, or both
- references to machine-readable report and audit artifacts

The execution log may mention outcome summaries, but it is not the authoritative source
for final imported/skipped/failed counts.

## Guardrails

- **Import writes go through the executed artifact, not the agent.** Never perform import
  writes via `CallWixSiteAPI`/MCP as a substitute for running the script. MCP is
  verification-only here (see the section above). (Setup execution is out of scope for this
  rule — see `rp-execute-setup`.)
- Prefer `execution/execution-manifest.json` over markdown when
  deciding what to run.
- Prefer `execution/completion-report.json` over `execution-log.md` when reporting final
  outcome to the user.
- Prefer `execution/recovery-log.json` over migration-local recovery scripts when
  explaining targeted recovery outcomes.
- Prefer dry-runs or sample batches before full import.
- Stop on systemic mapping or write failures rather than amplifying bad writes.
- Preserve enough logging to support replay and debugging.

<!-- chapter:end slug=rp-execute-import -->

---

<!-- chapter:begin slug=rp-execute-setup position=11 -->

## 11. rp-execute-setup

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-execute-setup/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-execute-setup/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-execute-setup.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (1), referenced from this skill's directory:
  - `scripts/mint-token.sh` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-execute-setup/scripts/mint-token.sh

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-execute-setup
description: >-
  Verifies and provisions Wix-side setup required before import. Use after codegen when
  machine-readable setup artifacts must be validated or executed against the target site.
---

# rp-execute-setup

Verify that required Wix-side setup exists and is ready for import.

## Purpose

This skill validates the prerequisites discovered by `rp-setup-discovery`. It can also drive the setup work when the environment and permissions allow it.

## Required inputs

- `migrations/<project>/setup/setup-plan.json`
- `migrations/<project>/setup/setup-requirements.json`
- `migrations/<project>/execution/execution-manifest.json`
- `migrations/<project>/config/wix.env` or equivalent environment values
- access to the target Wix environment or to exported evidence from that environment

Prefer the machine-readable setup artifacts above. Markdown setup summaries are secondary
renderings for humans, not the primary execution contract.

## Config

Prefer project-local config over ad hoc shell state. `config/wix.env` should exist before
setup verification/provisioning and contain:

```bash
WIX_SITE_STRATEGY=
WIX_SITE_ID=
WIX_AUTH_TOKEN=
```

`WIX_SITE_STRATEGY` is always required. `WIX_SITE_ID` is required before setup
verification/provisioning begins. If the strategy is `new` and no site has been created
yet, halt to needs-user and route back to the site-creation step rather than asking for an
existing site ID. `WIX_AUTH_TOKEN` is the canonical Wix credential key for this project
flow; for CLI-scaffolded headless sites it holds the site write credential (a CLI token
sent as a Bearer token). **Never print secret values.** Mint the token without printing it
to the transcript by running `scripts/mint-token.sh` from the migration project root:

```bash
bash migrations/<project>/scripts/mint-token.sh
```

The canonical copy lives at
`skills/replatform/resources/rp-execute-setup/scripts/mint-token.sh`.
`rp-import-codegen` copies it into `migrations/<project>/scripts/` at scaffolding time —
do not write it from scratch. The script reads `WIX_SITE_ID` from `config/wix.env`, calls
`npx @wix/cli@latest token --site "$WIX_SITE_ID"`, captures stdout (token shape:
`OauthNG.JWS.<base64>.<base64>.<sig>`), writes it directly into `config/wix.env` as
`WIX_AUTH_TOKEN`, and prints only a char-count confirmation. Do NOT run the token command
raw in a Bash tool call — it prints the credential to transcript.

If `WIX_SITE_ID` is missing for a RePlatform `new site` + `headless` flow, the recovery
path is the **Wix CLI headless scaffold** step defined in `replatform` → "Headless site
creation" (`npm create @wix/new@latest headless`). The account-level Projects API is
deprecated for this workflow. **Note:** the scaffold does not install Wix Stores — this
skill (setup) is where Wix Stores and other required apps get installed (via the
`wix-manage` skill / app-install path), then verified, before import.

Treat `migrations/<project>/config/*.env` as secret-bearing once they may contain real
values. Do not verify them with whole-file reads that echo contents into tool output.
Only check existence plus `present` / `blank` / `missing` status for required keys.

## Workflow

1. Resolve the active project.
2. Read the machine-readable setup artifacts.
3. Verify each required app, collection, schema, field, and permission through the shared
   setup runtime contract.
   If setup requirements came from domain knowledge, preserve the originating `targetRef`
   in verification output so later execution warnings can be traced back to the selected
   entity guidance.
4. Record pass, fail, or blocked status for each item.
5. If execution is allowed, perform missing setup steps through the shared setup runtime
   and re-verify.
6. Save the verification results and setup execution artifacts.

## Execute the setup artifacts — do not re-derive setup from prose

Setup execution should follow the approved machine artifacts and the shared setup runtime.
It should not rebuild setup decisions from markdown or ad hoc reasoning when
`setup/setup-plan.json`, `setup/setup-requirements.json`, and `execution/execution-manifest.json`
already exist.

This skill owns execution of those artifacts. It does not own redefining the setup plan
or creating a separate setup approval checkpoint.

Dry-run setup uses the same setup plan and setup executor as live setup. When `DRY_RUN`
is enabled by config or `--dry-run`, every setup step must first be reduced to a
structured intent describing the REST request, SDK operation, MCP tool call, or CLI
command that live setup would use. The shared setup runtime then captures that intent,
marks the step `planned_dry_run`, and must not invoke Wix MCP tools, Wix CLI commands,
SDK calls, or fetch calls that access or mutate Wix account/site state.

Do not override `DRY_RUN=true` with `--no-dry-run` for setup verification or provisioning
unless the user has explicitly approved leaving dry-run for setup. Prefer to avoid that
override entirely when a dry-run or plan/report can answer the question. The one allowed
live action while a project otherwise remains in dry-run mode is the separate new-site
creation step handled upstream by `replatform`; this skill must not treat that exception
as permission to run live setup writes or live setup verification probes.

Dry-run setup must not update `setup/setup-verification.json` in a way that claims a Wix
capability, app installation, collection, or site exists. Write a separate dry-run setup
report or clearly dry-run-scoped observations instead.

## Provisioning — exhaust programmatic options before declaring anything "manual"

Default to provisioning via API. Do **not** label a requirement "manual" or "owner
action" until you have confirmed no API can do it.

The preferred contract is a **shared setup runtime** owned by `rp-target-wix`, executed
against the machine setup artifacts produced upstream. This skill may use available Wix
API/MCP surfaces as the transport beneath that runtime, but the contract at the
RePlatform level is:

- setup execution consumes machine setup artifacts
- setup execution follows shared runtime behavior
- setup provisioning is not re-planned live by the agent

The approval gate is unchanged: no setup write before the user accepts the execution
plan.

If a helpful Wix tool surface is available in the runtime, it may be used beneath the
shared setup runtime for verification or transport. If no such surface is available, do
not treat that alone as a blocker; continue with the shared setup/runtime contract and
verified `rp-target-wix` behavior, marking unverified items where needed.

Only proceed in a docs-only/read-only posture when the MCP is genuinely unavailable and
the step can still produce useful non-destructive output.

Concrete mechanisms:

- **Mute site notifications FIRST (spec 0012).** When the setup artifacts carry the
  `mute-site-notifications` requirement (always for `WIX_SITE_STRATEGY=new`; opt-in for
  existing sites), execute it **before every other setup write** — immediately after the
  target site is available and `WIX_AUTH_TOKEN` is minted, and before app installs,
  collection creation, or any other provisioning — so setup writes themselves cannot
  fire notifications. Use the `muteSiteNotifications` primitive from
  `rp-target-wix/lib/wix-writers.js` (VERIFIED 2026-08-04) with a project-identifying
  reason (`RePlatform migration — <project>`); verify via `getSiteMuteState` →
  `muted: true` (verification method `status-read`; an idempotent re-mute is only a
  documented fallback, method `idempotent-recall`). Record the result in
  `setup/setup-verification.json` on the requirement at call time — status
  (`pass`/`fail`/`blocked`), timestamp, and verification method — like any other
  verified item; downstream reports read this recorded state, never infer it. **AUTH
  TRAP:** the endpoints accept user tokens only — the CLI-minted `OauthNG` site token
  works; an account API key gets an empty-body 403. **A failed mute is a blocker, not a
  warning:** the run halts to needs-user and never proceeds to import writes — no
  degraded mode, no continue-anyway. Never call `unmuteSiteNotifications` from this
  skill — unmute is an explicit owner request handled at the orchestrator level.
  Record the provision outcome through the standard `rp-telemetry` recorder like any
  other notable boundary (an `error` event with `error_code` on failure; the
  existing-site opt-in surfaces via the approval gate's `user_decision` event) — no new
  telemetry surface.
- **Installing / enabling Wix apps (Blog, Members, etc.) IS automatable.** Use the
  App Installation API:
  1. Pre-check with `POST /apps-installer-service/v1/app-instance/is-permitted-to-install`
     (read-only) to see whether the identity may install the app.
  2. If permitted, install with `POST /apps-installer-service/v1/app-instance/install`.
     Body (all fields required — confirmed by live 400s): `{ appInstance: { appDefId,
     enabled: true }, tenant: { tenantType: "SITE", id: <siteId> }, installType:
     "INSTALL_TYPE_SITE", appsInstallOptions: {} }`. (The `is-permitted-to-install`
     pre-check uses a *different*, oneof-based body and is informational only — if its
     validation fights you, skip it and rely on `/install`.)
  3. List current state with `GET /apps-installer-service/v1/app-instances`.
  - **Ground `appDefId` from the official "Apps Created by Wix" table**
    (`/docs/api-reference/articles/work-with-wix-apis/platform/about-apps-created-by-wix`),
    NOT from a docs *example* — e.g. the install-app example uses
    `1380b703-…`, which is **Wix eCommerce**, not Blog. Installing the wrong app on a
    live site is a real hazard; verify the ID maps to the app you intend.
  - **After installing Wix Stores, verify the catalog is V3 — before any Stores write.**
    Installing Stores does **not** guarantee Catalog V3: on a site scaffolded from the
    `blank` headless template, the install comes up **`V1_CATALOG`**, which the V3 Stores
    primitives cannot write to (verified the hard way on a live migration, 2026-07-30).
    Catalog version is fixed at provisioning — there is no in-place V1 → V3 switch. Check it
    with the read-only Catalog Versioning API:

    ```bash
    curl -s -H "Authorization: Bearer $WIX_AUTH_TOKEN" -H "wix-site-id: $WIX_SITE_ID" \
      https://www.wixapis.com/stores/v3/provision/version
    ```

    `catalogVersion` is `V3_CATALOG` (proceed), `STORES_NOT_INSTALLED` (install, re-check),
    or `V1_CATALOG` — record it as a **blocker** in `setup/setup-verification.json`, write
    nothing to Stores, and halt to needs-user. The prevention lives upstream of this skill
    (`replatform` → "Headless site creation": scaffold with `--site-template commerce`); the
    recovery is a user-approved replacement site, **never** a silently created one, and
    never a throwaway probe site.
- **Wix Data / CMS collections (the `WDE0110: Wix Code not enabled` case).** Enable Wix
  Data by **installing the Wix Data app `appDefId e593b0bd-b783-45b8-97c2-873d42aacaf4`**
  via the App Installation API (same `/install` body shape as any other app; it also
  auto-installs a dependency app `1a711f05-2040-47df-a9f0-4f9cddb4c3c6`). Once installed,
  plain REST `POST /wix-data/v2/collections` creates **NATIVE** collections with no
  `WDE0110` — no code editor toggle, no custom app needed. **Verified live 2026-06-10**
  on a fresh free site (install → 200; collection create → 200 `collectionType: NATIVE`).
  - This is the preferred path. The older data-collections-extension app (authoring a
    custom app that declares collections) is now a **fallback** — only needed if
    you must declare collection schemas at install time, and it still can't express
    `REFERENCE` fields (add those after install via `create-field`).
  - Note: the standalone "Wix CMS" app (`appDefId 675bbcef-…`) is **not** installable
    (`is-permitted-to-install` → `false`) — do **not** use it; use `e593b0bd-…`.
- **Optional import crosswalk CMS mirror.** Native Wix entities use local crosswalk state
  under `migrations/<project>/state/crosswalk/` for idempotency. Provision a native
  **`ImportCrosswalk`** collection only when the approved setup artifacts explicitly
  request a CMS mirror for existing-site seeding or site-local reference. After enabling
  Wix Data, create it with `POST /wix-data/v2/collections` and fields such as
  `entityType` (TEXT), `sourceId` (TEXT), `sourceStableKey` (TEXT), `targetId` (TEXT),
  `targetType` (TEXT), and `updatedAt` (DATETIME/TEXT). Do not provision this collection
  as the default native-entity idempotency mechanism. If upstream artifacts still call the
  optional mirror `MigrationRefs`, normalize them here rather than creating both
  collections.
- **Genuinely manual (no API exists):** upgrading the storage plan, generating
  external-system credentials (e.g. a WordPress Application Password), and
  account-level billing. These are the only categories that may be reported as manual —
  and only after confirming no API covers them.

## Artifact to create or update

- `migrations/<project>/setup/setup-verification.json`
- `migrations/<project>/setup/review/setup-verification.md`
- audit/report artifacts emitted by the shared setup runtime
- dry-run request captures in `migrations/<project>/state/attempts/wix-request-captures.ndjson`
  when setup is run with dry-run enabled

Stores live setup checks that require a probe record must use the shared rp-target-wix
verification CLI, not ad hoc snippets. For subscription support, run:

```bash
node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
  --artifact migrations/<project>/setup/stores-subscription-verification.json \
  --proposal-artifact migrations/<project>/setup/contract-ledger-proposal.json
```

Keep the JSON verification artifact and contract-ledger proposal alongside setup
verification. The proposal is not itself shared product knowledge; the orchestrator must
promote accepted proposal data into `rp-target-wix/domains/**/entities/*.json` in the same
session or record a deferral reason. If cleanup fails, do not mark the probe as clean;
preserve the warning and run the emitted `stores delete-probe` recovery command after
permissions or target state are fixed.

## Verification output format

For each requirement capture:

- requirement name
- expected state
- observed state
- status: passed, failed, blocked
- remediation needed

The machine-readable verification artifact should also preserve:

- stable requirement ID
- checkpoint/provisioning step ID when applicable
- verification evidence reference
- automation mode: `automatable | manual | blocked | unverified`

## Optional media reachability verification

When the migration includes media import by source URL, check whether discovered media
URLs are publicly reachable by Wix. If the source URL is `localhost`, `127.0.0.1`, or a
private-only host, mark **media import** as `blocked` or `deferred`, but do not block
unrelated non-media entities. This is optional setup and, as far as we know today, affects
only Wix Media import.

Record the user's chosen path in `setup/setup-verification.json` and render it into the
review markdown:

- **Tunnel media URLs:** ask the user to expose the source through a public HTTPS tunnel,
  then use that URL for `WP_BASE_URL` / `SOURCE_URL` or rewrite media URLs to that base.
- **Skip/defer media:** proceed only if the execution plan clearly says media and any
  media-dependent references (hero images, galleries, downloads) will be skipped or
  deferred.

Ngrok quick setup for macOS:

```bash
brew install ngrok
ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
ngrok http 8090
export WP_BASE_URL=https://<id>.ngrok-free.app
```

## Runtime policy

Split this skill's work by side effect:

- **Verification is read-only** — checking what's installed, what's missing, and what's
  genuinely manual. It runs **before** the execution-plan acceptance gate and feeds the
  plan.
- **Provisioning writes** — installing apps, enabling Wix Data (via the data-collections
  enabler), creating collections, adding fields — happen **only after** the user accepts
  the execution plan. **No site write before acceptance.** Once accepted, the "Migrate"
  consent covers the individual writes, so don't re-prompt per app/collection. Halt to
  needs-user only for genuinely manual items (storage-plan upgrade) or a
  missing/invalid credential.

The execution behavior itself should come from the shared setup runtime and approved
setup artifacts, not from improvised per-run logic in this skill.

## Guardrails

- Never report setup as complete without evidence.
- Prefer machine-readable setup verification artifacts over prose-only reporting.
- Before marking an item blocked or manual, confirm no API can perform it (see
  Provisioning above). Reserve "manual" for storage/billing/external-credential steps.
- If credentials or permissions are genuinely missing, mark the item blocked and state
  the exact API that was refused and why.
- Do not start import execution from this skill.
- Do not reinterpret setup requirements from markdown when machine artifacts exist.

<!-- chapter:end slug=rp-execute-setup -->

---

<!-- chapter:begin slug=rp-import-codegen position=12 -->

## 12. rp-import-codegen

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-import-codegen/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-import-codegen/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-import-codegen.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-import-codegen
description: >-
  Generates migration readers, transforms, setup plans, and thin Wix write specs from schema and mapping
  artifacts. Use when producing runnable extract/import code under the migration project.
---

# rp-import-codegen

Generate source readers, transforms, setup/import entrypoints, and thin Wix write specs from approved migration artifacts.

## Purpose

This skill turns the schema, mapping, and setup decisions into implementation files under the active migration project.

## Required inputs

- `migrations/<project>/source-schema.json`
- `migrations/<project>/mapping/mapping-plan.json`
- `migrations/<project>/setup/setup-plan.json`
- `migrations/<project>/setup/setup-requirements.json` when setup affects write paths

Prefer the machine-readable artifacts above. Markdown review files are secondary renderings
for humans, not the primary codegen contract.

## Source read contract

Generating a correct reader requires platform-specific knowledge — auth model,
pagination, rate limits, and REST quirks. That knowledge lives in the matching **source
adapter** skill, not here, so this skill never names a platform. Resolve the adapter from
the `platform` field in `source-schema.json` via the naming convention `rp-source-<platform>`
(e.g. `platform: "wordpress"` → `rp-source-wordpress`) and read its "Read contract"
section. The operational facts should already be recorded in `source-profile.md`; use the
adapter to fill any gaps rather than guessing. `rp-execute-import` runs the reader you
generate and stays platform-agnostic — so the platform specifics must be baked into this
generated code, not deferred to execution.

Adding a new source platform therefore requires no change to this skill: a new
`rp-source-<platform>` adapter is enough.

### CSV sources (`platform: "csv"`)

When `source-schema.json.platform === "csv"`, generate a **file reader** instead of an HTTP
reader and read `rp-source-csv` → "Read contract". The differences that matter:

- **Vendor `lib/csv-parse.js` into `src/lib/`** and import it, exactly as a WordPress reader
  vendors `wp-http.js`. Do not re-emit parsing: the sampler and the reader must agree on what
  the file contains. There is no auth, no pagination, and no rate limiting to generate.
- **Grouping is the reader's core job.** Replay `sourceMeta.sourceFiles[].layout`: with
  `continuation: "blank-key"` a blank key extends the current group; with a `sectioned`
  pattern the discriminator column routes parent vs child rows and `parentRefColumn` resolves
  a child to its parent (it may hold `id:123` or a SKU, and the child is not guaranteed to
  follow its parent).
- **Iterate the file set** by role, treating `sourceFiles[].partOf` entries as continuations
  of the same logical stream and honoring each part's own column order.
- **Materialize `column-values` entities**: collect the distinct values of the source column
  and their ancestors into their own entity file, emitted depth-ascending so a parent
  category exists before its child, plus the linking relation. Do not invent Wix ids at
  extract time — the `ImportCrosswalk` resolves them at import time.
- **Apply `sourceMeta.dialect.emptyPolicy`** through the shared `coerceEmpty` helper so a
  required Wix field is never fed an empty string the source did not have.
- CSV values are plain text; write them as-is with no entity-decoding step.

## Target write contract

Symmetrically, do **not** re-derive the Wix write surface here. It is identical for
every migration and is pre-verified in the **`rp-target-wix`** internal resource (see
`CONVENTIONS.md`), which ships shared Wix runtime code: verified request builders,
executors, and reusable execution logic for retries, throttling, checkpoints, and
reporting. **Vendor copies of the shared runtime modules from `rp-target-wix` into the
project** (like the source transport) and generate thin project-specific write specs and
transforms that **call those shared primitives/runtime functions**. Never hand-emit Wix
endpoints/bodies inline —
that path repeatedly shipped wrong shapes (lowercase Ricos plugin enums, `{tag:{…}}`
tag bodies, `media.wixMedia.image.id` featured images) that only failed at execution.

**Validate by real call, not by doc example.** MCP doc checks confirm an endpoint
*exists*; they do not confirm the request *shape works* — public examples have been
wrong (e.g. Ricos plugin enums shown lowercase that 400). Trust a Wix request shape only once a real call (or
`tests/target-wix/contract-test.js` in live mode, run from the repo root) has succeeded. Treat the
adapter's `// VERIFIED:` shapes as the source of truth over any docs example.

Adding a new source platform requires no change here, and the Wix surface change for
*all* migrations is a one-place edit in `rp-target-wix` (caught by its contract test),
not a per-project regeneration.

## Workflow

1. Read the discovery, mapping, and setup artifacts, plus the source adapter's read
   contract and the `rp-target-wix` write contract.
   If a mapping entity includes `targetRef`, validate its selected writer and reliability
   against `rp-target-wix/scripts/domain-knowledge.js summarize-entities`; do not remap
   source entities independently.
2. Generate machine-readable execution artifacts:
   - `execution/execution-manifest.json`
   - `execution/llm-handoff.json`
   - when `SAFE_MODE=true` or `DRY_RUN=true`, `execution/review/code-safety-review.md`
   - optional supporting execution subplans only when the runtime truly needs them
3. Generate setup code that can verify/provision Wix prerequisites by executing the
   machine setup artifacts through the shared setup runtime.
4. Generate source reader code that can enumerate and fetch source entities. If the adapter
   ships a shared transport module (auth, pagination, throttling, retries), vendor a copy of
   it into the project (e.g. `migrations/<project>/src/lib/`) and import from it instead of
   re-emitting that plumbing — the reader should hold only per-project orchestration.
   The reader must extract source records to durable files on disk; it must not require the
   whole source dataset to live in memory before import begins.
5. Generate transform code that maps source records into Wix-shaped objects — thin glue over
   `wix-build.js`, which already owns slug sanitizing and the money/price/variant rules; see
   "Never hand-write slug sanitizing" below.
   **Wix Stores products: Catalog V3 only. Catalog V1 is not supported by this workflow —
   there is nothing to generate for it.** The only destination a migration ever writes to is
   a `V3_CATALOG` site (guaranteed at provisioning and gated before any write — see
   `replatform` → "Headless site creation" and `rp-execute-setup`), so generated code may
   assume V3 unconditionally: no version detection, no branch, no V1 shape, no V1 fallback
   for a failing V3 write. A V1 site is a **blocker the run halts on**, never a case codegen
   handles. Concretely: never emit Catalog V1-style top-level `price`, `sku`, or variant
   inventory fields. For simple products, emit one variant under
   `variantsInfo.variants[]` with variant-level `price`, `sku`, and physical properties.
   When the source product is subscription-based and the source payload exposes explicit
   recurring cadence in structured product data, emit native
   `product.subscriptionDetails` with at least `allowOneTimePurchases` and one
   `subscriptions[]` entry carrying `title`, `description`, `frequency`, `interval`, and
   `autoRenewal`. Do not emit placeholder-only metadata or skip those products on create
   when that cadence can be inferred deterministically; live create coverage for this
   shape was verified on July 26, 2026.
   Product HTML descriptions land in `plainDescription`, which Wix converts to rich content
   server-side — it is HTML, not a plain-text flattening, so there is no fidelity loss to
   accept. Do NOT call the Ricos conversion endpoint on the product path: it costs one HTTP
   round-trip per product ahead of a bulk create, and that burst is what the endpoint throttles
   with a 403. Two traps: `plainDescription` is silently ignored when `description` is also set
   (set exactly one), and it is capped at 16,000 characters — longer bodies need truncation or
   an info section, recorded in `mapping-gaps.json`. Ricos conversion remains correct for blog
   posts, where `richContent` really is a Ricos document.
   Media scope should be relationship-driven by default: generate imports only for media
   referenced by mapped entities, not for the entire unattached source library. When the
   target adapter says an entity can ingest external URLs directly (for example Wix Stores
   product media), generate that entity-native path instead of routing those files through
   the slower generic Media Manager import flow.
6. Generate thin project-specific write specs and import orchestration code that pass
   those Wix-shaped objects into the shared `rp-target-wix` runtime. Generated code should
   describe *what* to write and in what order, not *how* to implement retries, throttling,
   checkpointing, or audit logging.
   Prefer the selected entity's `preferredWrite`; if it is not `verified-live`, surface an
   execution warning before consent. Native mappings must have a known `writerId`, a
   direct REST plan that calls `notifyMissingWriter`, or an explicit unsupported/gap
   fallback.
7. Generate or wire the deterministic execution-state preparation step before any import
   write. The generated runner should call the shared `execution-state` preparation
   contract, or the execution instructions should run
   `skills/replatform/scripts/execution-state-prepare.js` before the generated import
   entrypoint.
8. Generate the runnable setup/extraction/import entrypoints (see below) — the artifacts
   `rp-execute-setup` and `rp-execute-import` actually run. This is required, not optional.
   Then run the **sample-preview gate** (see below) before the execution-plan approval.
9. Generate `execution/review/import-plan.md` plus
   `execution/review/import-plan.freshness.json` using
   `skills/replatform/scripts/artifact-freshness.js write ...`. The freshness metadata
   must include hashes for `source-schema.json`, `mapping/mapping-plan.json`,
   `setup/setup-verification.json`, generated import code, and the target contract ledger
   revision. If codegen follows a newly promoted write contract, regenerated code and this
   metadata must reflect that promotion.
10. Document any manual code follow-up still required.
11. Generate any post-import remediation helpers that are required to reach the accepted
   mapping fidelity when the source capture cannot express the relationship inline during
   the first create pass.

## Post-codegen code-safety review checkpoint

When either `SAFE_MODE=true` or `DRY_RUN=true` for the active project, codegen must
produce a mandatory review artifact before the execution approval gate:

- `migrations/<project>/execution/review/code-safety-review.md`

This review is performed by the agent, not delegated to the user. It verifies the
generated code itself. It must check:

- every generated write path that can carry email/phone data passes `safeModeOptions`
  into the shared Wix runtime or direct REST wrapper
- every shared/native writer reached by generated code actually consumes those
  `safeModeOptions` and routes the request body through the shared sanitizer rather than
  silently ignoring the parameter
- no generated writer path silently bypasses safe mode because of a missing function
  parameter or a direct call that skips the shared sanitizer
- dry-run uses the same generated write code path as live import, with Wix calls skipped
  only at the shared `wix.send` boundary
- dry-run reporting does not label would-send placeholders as live-created/imported site
  objects
- mapping-declared `safeModeReplacements[]` are reflected in the generated write specs and
  runner wiring, and any entity that still carries outbound contact data without matching
  replacement-path coverage is treated as a review failure rather than left for the user
  to reason about manually

If the review finds a gap, execution approval must remain pending until the generated
code is corrected and the review artifact is regenerated with a passing verdict. The
user's role at this checkpoint is final go/no-go approval after the agent has already
completed the review and surfaced the findings.

## Codegen boundary

This skill owns the **project-specific layer** only.

It should generate:

- setup plan renderings and setup runner wiring
- source readers
- transforms
- per-entity write specs
- import ordering and dependency wiring
- project-local config loading
- execution review artifacts
- post-codegen code-safety review artifacts when safe mode or dry-run is enabled

It must not regenerate for each migration:

- raw Wix auth/client plumbing
- generic retry loops
- throttling behavior
- audit log shape
- compact execution report shape
- checkpoint store mechanics
- generic bulk-write orchestration

Those behaviors belong in `rp-target-wix`.

## Final handoff expectations

The generated plan and downstream execution path must make the final state legible to the
user. Treat these as required handoff details, not optional niceties:

- Surface the **dashboard URL** for the destination site.
- Surface the **editor URL** only when editor work was actually performed or the next
  required step is explicitly in the editor.
- Surface the **preview URL** only when public route/site verification is relevant to the
  completed work.
- Distinguish clearly between:
  - **catalog/data imported**
  - **website/homepage built**
- Do not imply that a successful Stores import means the site's homepage or full website
  experience exists. A migration can finish with valid catalog/product/cart/checkout
  routes while the homepage is still blank or unbuilt.
- If Shopify quick mode relies on public collection feeds such as
  `/collections/{handle}/products.json` to recover category membership, bake that into the
  generated extractor/importer or emit an explicit remediation helper and call it out in
  `execution/review/import-plan.md`.

## Runnable setup/extraction/import entrypoints — the artifacts are the execution path (required)

`rp-execute-setup` and `rp-execute-import` run the migration by **executing these
artifacts**, never by the agent hand-issuing Wix MCP calls. So codegen must emit real,
runnable entrypoints:

- **`src/setup/run-setup.js`** or equivalent setup entrypoint — reads the machine setup
  artifacts and executes them through the shared setup runtime.
- **`src/extract/run-extract.js`** or equivalent reader entrypoint — reads the source APIs and
  writes durable extracted files under the project. Extraction is a separate step from
  destination writes, so large migrations can be resumed or re-imported without re-reading
  the whole source.
- **`src/import/run-import.js`** or equivalent import entrypoint — reads the extracted files,
  applies transforms, and writes to Wix in dependency order through the vendored shared
  write runtime. It must:
  - load project-local config files, then process env, for all expected env-style values
    (never hardcoded; fail fast if absent — do not fall back to any agent/MCP auth),
  - run the **notification-mute preflight** before the first entity write whenever mute
    is in effect (see "Notification-mute preflight" below),
  - consume extracted source files from disk rather than materializing the entire source in
    memory,
  - apply idempotent dedupe keyed by source ID, using either a client-controlled source-id
    field on the target or the authoritative local
    `state/crosswalk/crosswalk.ndjson` durable `sourceId -> targetId` crosswalk for native Wix
    entities with server-assigned IDs, reading existing target state under the rules in
    "Reading existing target state" below — an empty read is not an empty site,
  - honor `DRY_RUN=true` and `--dry-run` for the safe-validation pass; `--dry-run` takes
    precedence over config and enables the shared Wix runtime's dry-run mode.
  - `--sample` may be supported as a narrower dry-run-style validation mode, but it must
    not replace `--dry-run` as the primary safe-validation control.
  - honor deterministic selective resume flags: `--entity <entity>`,
    `--source-type <subtype>`, `--missing-only`, `--failed-only`, and `--deferred-only`.
    `--missing-only`, `--failed-only`, and `--deferred-only` are mutually exclusive. These
    flags must drive the same import path as a full run after selecting a stable record
    set; do not generate one-off recovery drivers.
  - emit a stable `runId` at process start and include it in every audit event, dry-run
    request capture, placeholder crosswalk row, and summary artifact.
  - emit `execution/live-import-summary.json` and `execution/completion-report.json`
    through the shared completion-report runtime, with entity/subtype completeness counters
    for extracted, in-scope, attempted, imported, already present by crosswalk, deferred,
    failed, and skipped-out-of-scope records.
  - stop before dependent phases when required upstream entities fail. For example, do not
    create products after product-category failures unless the execution plan explicitly
    marks category assignments as non-blocking.
  - pass `safeModeOptions` to every relevant writer path, including direct REST fallback
    paths and unverified native writers that can carry contact values.
- **The dry-run is the same import code path with Wix calls skipped at the shared Wix
  boundary** — not a separate driver. Generated code must construct the same Wix client,
  call the same writer helpers, run `SAFE_MODE` sanitization, and pass request metadata
  (`phase`, `operation`, `entity`, `sourceId`, `verification`, and when needed
  `responseShape`) into `wix.send` so the runtime can capture would-send requests and
  return live-compatible dry-run placeholders.

Extraction format requirements:

- write extracted data to project-local files, not process memory
- chunk by entity and page/batch so a large source does not become one giant file
- write a manifest that lets the import step discover which entity files exist and in what
  order to consume them
- make the extracted files deterministic enough for resume, replay, and debugging

### Notification-mute preflight (spec 0012, required)

When mute is in effect — **always** for `WIX_SITE_STRATEGY=new` (unconditional,
regardless of `WIX_MUTE_NOTIFICATIONS`), and for existing sites only when
`WIX_MUTE_NOTIFICATIONS=on` — the generated import script must contain a mandatory
preflight step, **before the first entity write**, that asserts the site is muted:

- Primary: `getSiteMuteState(wix)` (rp-target-wix, VERIFIED 2026-08-04) returns
  `muted: true`.
- Fallback (only if the status read is unavailable): an idempotent
  `muteSiteNotifications(wix, { reason })` call — passing the **same
  project-identifying reason as setup** (`RePlatform migration — <project>`), because a
  re-mute overwrites the recorded reason (last caller wins).

If the preflight call fails or reads `muted: false` and the re-mute fails, the script
**aborts before any write** with a clear error routing back to setup — exit non-zero, no
degraded mode, no `--skip-mute-preflight`-style flag, no warning-and-continue. For
new-site projects the preflight is emitted unconditionally and never appears as a
skippable option; for existing-site projects with `WIX_MUTE_NOTIFICATIONS=off` (the
default), no preflight is emitted. The preflight logs its re-verification and outcome
into the run's execution/progress log (keyed by `runId`) — terminal reports read this
recorded state, never strategy/config inference. In dry-run mode the preflight follows
the shared dry-run contract like any other Wix call: the intent is captured and the
state read is skipped (`stateKnown: false`), not asserted as muted. The generated script
must never call `unmuteSiteNotifications` — unmute is an explicit owner request outside
the import path. A preflight mute-verification failure is recorded through the standard
`rp-telemetry` recorder as an `error` event (with `error_code`) like any other run
event — no new telemetry surface.

### Use the target's BULK write path (required)

**A generated importer must write through the target's bulk endpoint whenever one exists.**
Per-record creates are acceptable only when the target has no bulk equivalent, or for a
deliberate single-record contract probe. This is not an optimization to add later: at 1000
products, per-record creates are 1000 round trips where bulk is ~11, and the latency
difference is the difference between a minute and half an hour.

Derive the batch shape from the endpoint's **own limits, all of them at once**. Bulk
endpoints routinely cap several dimensions simultaneously and exceeding **any one** rejects
the entire request — Wix bulk product create caps products (100), variants (1000), options
(100), modifiers (100) and infoSections (100) per request, so with 2 options per product the
options cap binds at 50 products, not 100. Batch with `ndjson.readBatchesByLimits` and the
limits/cost helpers the target adapter exports (`BULK_PRODUCT_LIMITS`,
`storesProductBulkCost`); never batch on record count alone.

Three properties of bulk responses that generated code must handle explicitly, because each
one silently corrupts a report if ignored:

- **Bulk is not atomic.** A `200` can contain per-item failures. Walk every
  `results[]` entry; never infer success from the HTTP status.
- **Correlate by the response's own index field** (`itemMetadata.originalIndex` for Wix), not
  by response position, and verify that every input is accounted for. A mis-correlated
  result crosswalks the wrong target id onto a source record.
- **Count the "undetailed failures" bucket.** Servers drop failure detail past a threshold;
  those are still failures and must appear in the completion report.

Dedupe **before** building the batch — a skipped record must never reach the API — and
record the crosswalk per successful item, not per batch, so an interrupted run resumes
correctly.

### Record streams are NDJSON, single documents are JSON (required)

**Every file that holds a stream of records must be newline-delimited JSON (`.ndjson`), one
record per line — never a `{ "records": [ … ] }` array.** Vendor
`rp-target-wix/lib/ndjson.js` into the project (like `wix-writers.js`) and use it; do not
hand-roll line splitting, which gets chunk boundaries and CRLF wrong.

This applies to:

- `data/source-extract/<entity>.ndjson` — the extractor's output
- the `sourceId -> targetId` crosswalk
- audit logs (already NDJSON)

It exists because every downstream stage does the same three things with these files, and an
array is the wrong shape for all of them:

- **scan** — `countRecords` counts lines; nothing is parsed and nothing is held in memory. A
  JSON array must be fully parsed to be counted.
- **batch** — a bulk endpoint's page is `readBatches(file, 100)`. Use `readBatchesBy` when the
  target caps more than one dimension: Wix bulk product create allows **≤100 products AND
  ≤1000 variants per request**, which is `{ maxCount: 100, maxCost: 1000, cost: p =>
  p.variantsInfo.variants.length }`.
- **cursor / resume** — `readSlice(file, { offset, limit })` skips what is already done
  without rebuilding it into objects.

Two more properties that matter in practice: a producer can append records as it finds them
instead of buffering the whole entity, and an interrupted write leaves a **valid readable
prefix** — a truncated JSON array is unparseable, so a crash mid-extract loses everything.

**Do not line-delimit single documents.** A manifest, `mapping-plan.json`,
`decisions.json`, `execution-manifest.json`, `preview-result.json` or
`completion-report.json` is one object; it stays `.json`. NDJSON buys nothing there and makes
it unreadable.

Generated importers must **stream** these files (`for await (const batch of
readBatches(...))`), not `readAllRecords` them. `readAllRecords` is an escape hatch for
genuinely small streams (a six-record category list) and is named to make its misuse on a
large stream obvious.

Projects generated before this rule can be moved forward with
`convertLegacyJsonFile(jsonPath, ndjsonPath)` rather than re-extracting.

### Reading existing target state (required)

An idempotent importer has to know what is already on the site before it writes. Every bug in
this section is the same bug: **a read that returns nothing looks exactly like a site that
contains nothing**, and nothing-on-the-site is the branch that writes. None of them throw, so
none of them show up in a dry-run.

**The adapter's `query*` executors already return the array.** `queryStoresCategories`,
`queryStoresProducts`, `queryContacts`, `queryCoupons` and `queryOrders` unwrap the response
before returning it, so the value **is** `categories` / `products` / etc. Generated code must
use it directly:

```js
const existing = await W.queryStoresCategories(wix);          // an array
const existing = (await W.queryStoresCategories(wix)).categories;  // WRONG → undefined → []
```

The second form is what shipped, and reading `.categories` off an array yields `undefined`,
which the usual `|| []` turns into an empty array. It silently disabled a category dedupe
index, silently disabled a product name-match safety net, and made a setup verification
report **0 categories on a site that had 25** — all without one error line.

**Never cursor-page through those executors.** Unwrapping discards `pagingMetadata`, so the
cursor a loop needs is already gone; a loop built on them cannot advance past page one, and
reading `.pagingMetadata` off the returned array is the same `undefined` as above. Prefer the
adapter's sweep primitives, which own the loop and the failure semantics:

- `queryAllStoresCategories(wix)`, `queryAllStoresProducts(wix)`, `queryAllDataItems(...)`

When a sweep is needed for an entity that has no `queryAll*` primitive yet, generate the loop
against the **raw** response and add the primitive to `rp-target-wix` rather than leaving the
loop in project code:

```js
let cursor = null;
do {
  const body = cursor ? { cursorPaging: { limit: 100, cursor } } : { cursorPaging: { limit: 100 } };
  const response = await wix.send(W.buildQueryStoresProductsRequest(body));   // raw, not the executor
  for (const p of response.products || []) { /* index it */ }
  cursor = (response.pagingMetadata && response.pagingMetadata.cursors && response.pagingMetadata.cursors.next) || null;
} while (cursor);
```

**An incomplete sweep must throw, not fall through.** If any page fails, or the loop hits its
page ceiling with a cursor still outstanding, the generated code must abort the import with a
message naming the sweep. It must **not** continue with the partial index, and must not treat
"the sweep failed" as "nothing exists" — that is precisely the state in which a re-run
re-creates the entire catalog it already imported. The single most expensive failure in this
whole pipeline is a duplicate import, and it arrives through an empty net.

**A match must be ADOPTED into the crosswalk, not skipped.** When a safety net (name match,
slug match, source-id field) finds that the target entity already exists, record it in the
crosswalk with its **target id and revision** and count it as reused. A bare `continue` that
skips the write without recording the id looks correct — nothing is duplicated — but every
later phase that resolves ids *from the crosswalk* then silently drops the record. Concretely:
the product was already on the site, so it was skipped, so it had no crosswalk row, so the
category-link phase could not resolve its id and it ended up in no category at all. Index the
net as `name -> { id, revision }`, not as a `Set` of names, so the id is available to adopt.

An ambiguous match is the one case that must not be adopted: if the source key is not unique
(e.g. four source products share a title), the net cannot tell which existing entity
corresponds to which source record. Import rather than guess, and report the ambiguity in the
completion report.

### Never hand-write slug sanitizing (required)

A source handle is **not** already a valid Wix slug. Wix rejects anything outside `[a-z0-9-]`,
and because slug validation happens before the batch is applied, **one bad slug fails the
entire bulk request** — 100 products lost for one character. Shopify mints underscores from
decimal titles ("pH 5.5" → `ph-5_5`), so this is routine input, not an edge case.

It is already solved: `wix-build.js` exports `toWixSlug` and applies it automatically through the
`coerce: 'slug'` rule on `product.slug` in `wix-target-spec.js`. Generated transforms that call
the build layer (see the `src/import/transforms/` note under "File targets") get it for free and
must not re-derive it — a per-project copy is how the underscore bug reached a live site in the
first place.

Two properties to preserve when a generated transform sets a slug explicitly:

- **Sanitize in the build layer, not the writer**, and keep both values. URL preservation needs
  the original `sourceSlug` alongside the `plannedTargetSlug` actually derived from it (see the
  URL preservation rules under "Codegen rules"), which a silent rewrite inside the writer would
  falsify. `normalizeStoresProductV3` therefore passes a slug through untouched.
- **`toWixSlug` throws when a value sanitizes to empty** (an all-non-latin title, for example).
  That is a signal to supply a deterministic fallback — the record's source id — not to omit the
  slug and let Wix derive one, which breaks URL preservation with no trace.

## Sample-preview gate

Between the extractor being generated and the execution-plan approval, show the user what
their data actually became. The mapping review checkpoint validates *intent*; this validates
*structure* — how source rows turned into entities — before any full run.

Required whenever the source cannot be read back from a live API — in particular every
`platform: "csv"` run, where a misread layout silently produces the wrong entity split.

1. Generate the extractor first (`src/extract/run-extract.js`).
2. Run it in sample mode (`--sample`) to materialize a small `data/source-extract/` slice.
3. Write two artifacts under `migrations/<project>/preview/`:
   - `preview-summary.md` — a short human-readable structure preview: how rows became grouped
     entities (e.g. one product with its variants and images), per-entity record counts, and
     which columns landed in which Wix fields.
   - `preview-result.json` — `{ "status": "pending", "decidedAt": null, "decidedBy": null,
     "entityCounts": {...}, "warnings": [] }`.
4. Pause and ask the user to validate the structure. On accept, set `status: "accepted"` with
   `decidedAt`/`decidedBy`; on reject, set `status: "rejected"` and set
   `approvals.mapping.status` back to `pending` so the router returns to `rp-mapper`.
5. Record the artifacts on the codegen checkpoint (`checkpoints.codegen.artifactRefs` +
   `lastCompletedStep: "codegen.sample-preview"`).

This is a **codegen sub-gate, not an orchestration phase**: it adds no state to
`orchestration-state.js`. While `preview-result.json` says `pending`, the router keeps routing
back to this skill instead of advancing to the execution-plan gate — that routing behavior is
the enforcement, so the artifact must be written honestly. The gate rides on the existing
extract→import split and does not change how import writes to Wix.

## Project-local config files

Generated code should treat `migrations/<project>/config/` as the canonical home for all
values that are otherwise expected as environment variables. Use simple `.env` syntax and
load these files before reading config:

- `config/wix.env` always exists. Default `DRY_RUN=false`, except when the user has
  explicitly asked to start, create, prepare, or run the migration in dry-run mode; in
  that case scaffold or preserve `DRY_RUN=true`:

  ```bash
  WIX_SITE_STRATEGY=
  WIX_SITE_ID=
  WIX_AUTH_TOKEN=
  WIX_MUTE_NOTIFICATIONS=
  DRY_RUN=false
  SAFE_MODE=true
  SAFE_MODE_PHONE_NUMBER=+972 50 0000000
  ```

- `config/source.<platform>.env` exists after the source platform is known. For WordPress:

  ```bash
  WP_BASE_URL=
  WP_USERNAME=
  WP_APPLICATION_PASSWORD=
  WP_MEDIA_URL_REWRITE_FROM=
  WP_MEDIA_URL_REWRITE_TO=
  WC_CONSUMER_KEY=
  WC_CONSUMER_SECRET=
  ```

  For CSV sources this is `config/source.csv.env`, which is **not** secret-bearing — all keys
  are optional hints (`CSV_INPUT_ROOT`, `CSV_DELIMITER`, `CSV_ENCODING`, `CSV_VENDOR`,
  `CSV_MEDIA_URL_REWRITE_FROM`, `CSV_MEDIA_URL_REWRITE_TO`). Generated readers must resolve
  input paths against `CSV_INPUT_ROOT` (falling back to the project directory) rather than
  baking absolute paths into generated code.

Codegen rules:

- Generate a small dependency-free config loader in the runnable entrypoint or `src/lib/`.
- Load `config/wix.env` and the selected source config before constructing source/Wix
  clients.
- Default `DRY_RUN` to disabled. Treat `true`, `1`, `yes`, and `on` as enabled and
  `false`, `0`, `no`, and `off` as disabled. `--dry-run` must override config and enable
  dry-run. `--no-dry-run` may be supported to override `DRY_RUN=true`.
- When scaffolding a project that is in dry-run mode, generated review artifacts must say
  that leaving dry-run later requires explicit user approval for any step other than
  new-site creation, and that such overrides should be avoided when a dry-run or report
  is sufficient.
- Preserve an explicit `DRY_RUN=true` from project config when regenerating code or
  config. Do not reset it to `false` during later codegen passes.
- Default safe mode to enabled when `SAFE_MODE` is missing or blank. Honor
  `SAFE_MODE=false` when the user set it before mapping: do not require
  `safeModeReplacements[]`, do not replace contact values, and do not write safe-mode email
  recovery rows.
- When safe mode is enabled, require `SAFE_MODE_PHONE_NUMBER`, default missing/blank values
  to `+972 50 0000000`, and make the generated config explicit.
- Real process environment variables may override file values.
- Blank values in config files must not overwrite non-empty process env values.
- If a required key is still missing after loading file + env, fail fast with the key
  name, not a downstream 401.
- In dry-run, missing or blank `WIX_AUTH_TOKEN` and `WIX_SITE_ID` are `would_block_live`
  findings, not blockers, unless a generated local artifact requires the site ID as a
  stable namespace. Do not mint a Wix CLI token solely for dry-run.
- `WIX_MUTE_NOTIFICATIONS` (spec 0012) resolves by strategy when blank: `new` → `on`,
  `existing` → `off`; record the resolved value explicitly (mirrored from
  `orchestration/decisions.json`). **`WIX_MUTE_NOTIFICATIONS=off` together with
  `WIX_SITE_STRATEGY=new` fails codegen validation** — halt with the config conflict
  rather than generating artifacts (the same rule fails in `rp-setup-discovery`; fail
  fast at whichever runs first). Note the preflight emission rule below ignores the
  config for new sites anyway — it is unconditional.
- Treat `WIX_SITE_STRATEGY` as required. `WIX_SITE_ID` becomes required no later than the
  point where generated code needs to construct Wix clients or destination-specific
  artifacts. For `WIX_SITE_STRATEGY=new`, codegen should fail with a clear message to
  create/select the new Wix site first rather than assuming an existing site flow.
- For RePlatform `new site` + `headless`, that unblock message must point back to the **Wix
  CLI headless scaffold** in `replatform` → "Headless site creation"
  (`npm create @wix/new@latest headless`). The account-level Projects API is deprecated for
  this workflow.
- `WIX_AUTH_TOKEN` is the canonical Wix auth key in project-local config and holds the
  **site write credential** for import. With CLI-scaffolded headless sites this is a
  short-lived CLI token sent as a **Bearer** token — the generated Wix client must send
  `Authorization: Bearer <token>` (plus `wix-site-id`). Do not assume a raw, non-expiring API
  key; mint the token at write time.
- Never log secret values. It is okay to log that a key is present/missing.
- Dry-run must write request captures under `state/attempts/wix-request-captures.ndjson`
  and dry-run placeholder target IDs only under `state/crosswalk/dry-run-crosswalk.ndjson`
  or in memory. It must never append simulated target IDs to
  `state/crosswalk/crosswalk.ndjson`.
- Do not generate debug output that dumps config file contents, environment snapshots, or
  request headers carrying credentials.

## WooCommerce subscription product codegen

For WooCommerce Stores products, subscription products are a native Wix Stores product
subtype when the target metadata marks `product.subscriptionDetails` writable for
Catalog V3 create. Codegen must read the `stores/product` `fieldContracts[]` metadata and
vendor/use the matching `rp-target-wix/lib/wix-writers.js` helpers:

- `STORES_SUBSCRIPTION_CONTRACT`
- `normalizeStoresProductSubscriptions`
- `validateStoresProductSubscriptionDetails`

Generate deterministic native subscription mapping only from recognized structured
WooCommerce subscription fields. Accepted first-pass source keys include:

- `_subscription_period_interval`
- `_subscription_period`
- `_subscription_length`
- `_subscription_trial_period`
- `_subscription_trial_length`
- `_subscription_sign_up_fee`
- `_subscription_price`

Equivalent public REST fields may be used when discovery normalized them into the source
schema, but do not infer cadence from product titles, descriptions, prose, shortcode
blobs, or unrecognized plugin metadata.

Mapping rules:

- Treat source `type: "subscription"` or recognized subscription metadata as the product
  subtype signal.
- Parse billing interval as an integer `>= 1`.
- Parse billing period from `day`, `week`, `month`, or `year`, then emit Wix frequency
  `DAY`, `WEEK`, `MONTH`, or `YEAR`.
- Use subscription price when present, otherwise the normal product price, and still emit
  the Catalog V3 variant-level money object required by the shared writer.
- Parse optional length/trial/signup-fee fields only when they are clean structured
  values. If a field is present but malformed and needed for the selected mapping, defer
  the record rather than guessing.
- Build subscription descriptions deterministically from source subscription labels or
  product names, then pass them through `normalizeStoresProductSubscriptions` so the Wix
  `description <= 60` contract is satisfied before write.
- Run `validateStoresProductSubscriptionDetails` in the generated dry-run path and before
  live create for every product carrying `subscriptionDetails`.

Do not generate a generic subscription skip gate. If a subscription product lacks
structured cadence or required values, emit an explicit deferred record such as
`unsupported_subscription_shape`, `missing_subscription_cadence`, or
`invalid_subscription_interval`, and include that reason in the execution summary. A
deferred subscription is a counted import outcome, not a silent skip.

**Token minting — always route through `scripts/mint-token.sh`.** Every migration project
must include `scripts/mint-token.sh`. The script reads `WIX_SITE_ID` from `config/wix.env`,
runs `npx @wix/cli@latest token --site "$WIX_SITE_ID"`, captures the token (shape:
`OauthNG.JWS.<base64>.<base64>.<sig>` — single line, no JSON wrapper), and writes it
directly to `config/wix.env` as `WIX_AUTH_TOKEN` without printing the value. Run via Bash:

```bash
bash migrations/<project>/scripts/mint-token.sh
```

**Do NOT** run `npx @wix/cli@latest token` raw in a Bash tool call — it prints the
credential to stdout which lands in the transcript. Always use `mint-token.sh`.

**During scaffolding, copy `scripts/mint-token.sh` from the canonical skills location** —
do not generate it from scratch:

```bash
cp skills/replatform/resources/rp-execute-setup/scripts/mint-token.sh \
   migrations/<project>/scripts/mint-token.sh
```

The preferred
pre-import flow is:

1. verify `wix whoami`,
2. run `bash scripts/mint-token.sh` (writes `WIX_AUTH_TOKEN` silently),
3. run the import — the generated client reads `WIX_AUTH_TOKEN` and sends it as
   `Authorization: Bearer <token>` with `wix-site-id`.

## Localhost media sources

If the source profile shows `localhost`, `127.0.0.1`, or another private-only source URL,
generated media import code must not assume Wix can fetch those URLs. Wix Media import is
URL-based (`rp-target-wix` import-from-URL primitive), so live media import needs a public
URL reachable by Wix servers. This is optional and, as far as we know today, only affects
media import. Entity-native background ingestion from external URLs follows the same
reachability requirement: if the target API ingests a source URL itself, that URL must
still be publicly reachable by Wix.

Codegen/runtime should support one of these explicit paths:

- Use a public HTTPS tunnel/source URL for live media import. For ngrok on macOS:

  ```bash
  brew install ngrok
  ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
  ngrok http 8090
  export WP_BASE_URL=https://<id>.ngrok-free.app
  ```

- If the source REST responses still contain local media URLs, generate a configurable
  rewrite from the local base URL to the public tunnel base URL. For WordPress, use
  `WP_MEDIA_URL_REWRITE_FROM` and `WP_MEDIA_URL_REWRITE_TO`; when those are blank, it is
  acceptable to rewrite localhost/private origins to public `WP_BASE_URL`.
- Or generate/allow a media-skip/defer mode and document that media-dependent references
  such as hero images, galleries, and downloadable files will be absent until media is
  imported.

Surface the selected path in `execution/review/import-plan.md` and the execution plan before any live
write. Do not let a dry-run with localhost media URLs imply live Wix Media import is ready.

## File targets

Write code under the project-local source tree:

- `migrations/<project>/execution/`
- `migrations/<project>/src/setup/`
- `migrations/<project>/src/extract/`
- `migrations/<project>/src/import/`
- `migrations/<project>/src/lib/` — vendored shared modules (the source adapter's transport
  module, `rp-target-wix/lib/ndjson.js`, `rp-target-wix/lib/wix-writers.js`,
  `rp-target-wix/lib/wix-target-spec.js`, `rp-target-wix/lib/wix-build.js`), copied here so
  the project runs standalone with no external deps
- `migrations/<project>/src/extract/readers/`
- `migrations/<project>/src/import/transforms/` — **thin glue only.** Vendor and call
  `wix-build.js`; do **not** re-derive the canonical→Wix payload layer here. Money objects, slug
  sanitization, the empty `physicalProperties` trap, choice-by-name variant references, the
  compare-at-must-exceed-price rule and the regular/sale price resolution are all settled in
  `wix-target-spec.js` + `wix-build.js` and regression-locked by
  `tests/mapping/wix-build-oracle-test.js` against 220 payloads from two live-verified imports.
  That layer is vendor-independent, so a per-project reimplementation only re-introduces bugs
  that were already found and fixed. This directory should hold at most the mapping from THIS
  project's reader field names onto the canonical vocabulary — and once the reader emits canonical
  records directly, nothing at all.
- `migrations/<project>/src/import/write-specs/`
- `migrations/<project>/src/setup/run-setup.js` — setup execution entrypoint
- `migrations/<project>/src/extract/run-extract.js` — extraction entrypoint. It must accept
  `--sample` (extract a small slice only); that is what the sample-preview gate runs.
- `migrations/<project>/src/import/run-import.js` — the runnable import entrypoint
  (required; see "Runnable setup/extraction/import entrypoints" above). `--dry-run`
  drives the safe-validation pass through the same import code path.
- `migrations/<project>/data/source-extract/` — extracted `<entity>.ndjson` record streams plus
  a `manifest.json` (the manifest is a single document, so it stays JSON)
- `migrations/<project>/state/crosswalk/crosswalk.ndjson` — authoritative local
  `sourceId -> targetId` crosswalk for native Wix entities
- `migrations/<project>/state/attempts/write-attempts.ndjson` — append-only write attempt
  journal for resume/reconciliation
- `migrations/<project>/state/url-preservation/base-paths.json` — entity-level public
  route intent from mapping `urlPolicy`
- `migrations/<project>/state/url-preservation/url-ledger.ndjson` — append-only URL
  preservation upsert log, one latest row per `sourceStableKey + sourceRelativeUrl`
- `migrations/<project>/state/url-preservation/redirects.ndjson` — planned redirect rows
  when source and target relative URLs are both known and differ
- `migrations/<project>/state/url-preservation/unresolved.ndjson` — public source URLs
  waiting on target route configuration, target lookup, or manual review
- `migrations/<project>/logs/` — audit/error logs emitted by the shared runtime
- `migrations/<project>/execution/review/import-plan.md`
- `migrations/<project>/execution/review/code-safety-review.md` when `SAFE_MODE=true` or
  `DRY_RUN=true`
- `migrations/<project>/execution/recovery-log.json` — append-only standard recovery
  actions emitted by every resumed, partial, missing-only, failed-only, or deferred-only
  run

Recommended machine-readable artifacts:

- `migrations/<project>/execution/setup-plan.json`
- `migrations/<project>/execution/execution-manifest.json`
- `migrations/<project>/execution/llm-handoff.json`
- `migrations/<project>/execution/live-import-summary.json`
- `migrations/<project>/execution/completion-report.json`

## Generated write spec contract

Generated writer code should be thin. It should primarily define deterministic per-entity
write specs, then pass Wix-shaped objects into the shared runtime.

Each write spec should define, as applicable:

- `entity`
- `mode`: `create | update | upsert | bulk_create | bulk_upsert`
- `create`
- `update`
- `lookup`
- `bulk`
- `batchSize`
- `concurrency`
- `retryPolicy`
- `throttlePolicy`
- `auditKeys`
- `crosswalkAuthority: "local"`
- `cmsMirror: "none" | "download" | "upload" | "download-and-upload"`
- `crosswalkStrategy`
- `reconciliationStrategy`
- `dependencyRefs`
- `verificationLevel`: `verified | unverified`
- `safeMode.replacePaths[]`: resolved request-body paths copied from mapper
  `safeModeReplacements[]`, with `{ kind: "email" | "phone", path }` entries. Paths use
  the shared safe-mode grammar (`field`, `field[]`, `field.items[]`) and are relative to
  the request body passed to the shared writer builder. The mapper is required to surface
  these replacements in both machine artifacts and review markdown; codegen should treat
  missing `safeModeReplacements[]` for an entity with outbound contact fields as a mapper
  contract failure, not as a cue to infer or invent replacements silently.

The transform layer should output Wix-shaped objects only. Request-envelope building and
retry/reporting logic belong in the shared runtime.

When safe mode is enabled, generated import code must derive one `safeModeOptions` object
per source record and pass it to every Wix writer call. The options must include origin
`entityType`, deterministic origin `entityId`, `safeModePhoneNumber`, and the write spec's
resolved replacement paths. `entityId` must be the source ID or deterministic
`sourceStableKey`, never the target Wix ID. If no deterministic origin identity exists,
fail before writing.

Generated code must not query Wix by original email or phone values while safe mode is
enabled. Any destination lookup keyed by contact data must use the same mock value that the
writer will send, with local crosswalk state remaining the primary idempotency authority.

## Execution artifact contract

Codegen should emit machine-readable execution artifacts alongside runnable code.

- `execution/setup-plan.json`: execution-ready rendering of setup work
- `execution/execution-manifest.json`: authoritative ordered task graph covering setup,
  extraction, transform/write execution, artifact refs, checkpoint ids, and plan version
- `execution/llm-handoff.json`: whether unresolved codegen/execution decisions remain

This manifest is the primary downstream contract for `rp-execute-setup` and
`rp-execute-import`.

For every import task that handles public routed entities, include:

```json
{
  "urlPreservation": {
    "enabled": true,
    "basePathsPath": "state/url-preservation/base-paths.json",
    "ledgerPath": "state/url-preservation/url-ledger.ndjson",
    "redirectsPath": "state/url-preservation/redirects.ndjson",
    "unresolvedPath": "state/url-preservation/unresolved.ndjson",
    "applyRedirects": false
  }
}
```

Do not set `applyRedirects` to `true`; the current phase records redirect plans but does
not configure Wix redirects or site routes.

## Import completeness accounting contract

Generated import runners must treat completeness accounting as a first-class runtime
output. For each mapped entity class and meaningful subtype, the runner must produce an
`entityCompleteness` row with these counters:

- `extracted`
- `inScope`
- `attempted`
- `imported`
- `alreadyPresentByCrosswalk`
- `deferred`
- `failed`
- `skippedOutOfScope`
- `unexpectedSkipped`

Subtype is required whenever source records with the same entity map through materially
different write paths or deferral rules, for example simple vs subscription Stores
products. `deferred` means intentionally not attempted with a recorded reason. `failed`
means attempted and not successfully written. `unexpectedSkipped` is for in-scope mapped
records that were skipped outside an accepted out-of-scope rule.

The generated runner must call or vendor `skills/replatform/lib/completion-report.js` and
write both:

- `execution/live-import-summary.json` during/after live import for machine inspection
- `execution/completion-report.json` as the authoritative final outcome artifact

For each row, reconciliation is:

```text
imported + alreadyPresentByCrosswalk + deferred + failed == inScope
```

Any reconciliation mismatch, non-zero `failed`, non-zero `deferred`, or non-zero
`unexpectedSkipped` count must appear in the report headline and in the deterministic
human completion summary. Do not report a run as cleanly complete when source, import, and
crosswalk counts do not reconcile.

## Verifying Wix APIs

**The primary control is the Target write contract above: call `rp-target-wix`'s
verified primitives when they exist.** That adapter is where each stable shape is
verified-once (by a real call) and where a Wix surface change is fixed in one place.
When Wix has a native entity but `rp-target-wix` does not yet have a dedicated writer,
codegen must generate a Wix REST call for that native entity via the adapter's generic
direct REST helper, log the missing writer, and call the RePlatform notification hook.
Do **not** route to CMS merely because the writer is missing.

- If a Wix tool surface such as Wix MCP is available, prefer it to locate the endpoint
  and request/response shape. If the shape is common enough, add a dedicated primitive to
  `resources/rp-target-wix/lib/wix-writers.js`; otherwise generate a project-local native
  REST call through `sendDirectRest` and mark it `UNVERIFIED`.
- **Confirm the shape with a real call, not a doc example** — public examples have been
  wrong (e.g. lowercase Ricos plugin enums that 400). Cover the new primitive in
  `tests/target-wix/contract-test.js` so drift stays visible.
- **Fallback when no Wix tool surface is available:** rely on published Wix REST/SDK docs
  and conservative names. Mark the generated native call `// UNVERIFIED:` until a real
  call confirms it — never ship an unchecked Wix call to a user's live site without
  surfacing it in the execution plan.

## Runtime policy

Verify each Wix endpoint and field at codegen time. The `// UNVERIFIED:` marker is a
fallback only for environments where no direct verification aid is available, not a way
to ship unchecked calls that fail later on the user's live site. Anything unverified must
be surfaced explicitly in downstream artifacts before execution.

## Missing writer policy

CMS fallback is for source concepts that do **not** have a suitable native Wix entity, or
for native entities explicitly rejected because they cannot preserve fidelity or would
cause unsafe side effects. CMS is **not** a fallback for a missing writer, and it is not a
special default for coupons just because coupon scoping/restrictions need mapping.

When the mapping targets a native Wix entity and no dedicated `rp-target-wix` writer
exists:

1. Generate project-local code that calls the native Wix REST endpoint through
   `sendDirectRest`.
2. Add a clear log line before the first use of that generated REST path.
3. Call `notifyMissingWriter({ sourceEntity, wixEntity, method, path, reason })`. The
   current implementation may be a no-op; the generated code must still call it.
4. Mark the path `UNVERIFIED` in `execution/review/import-plan.md` and the execution-plan report until a
   live/sandbox call promotes it.
5. Maintain the same idempotency rules as dedicated writers: crosswalk by source ID in
   local state and never dedupe by slug.

## Codegen rules

- Keep reader and writer responsibilities separate.
- Keep setup, extraction, and import responsibilities separate.
- Do not generate a read-all-into-memory importer for the general case. The reader extracts
  to disk first; the importer consumes extracted files from disk.
- Make transforms deterministic and testable.
- Make generated write specs deterministic and declarative.
- Preserve **source IDs** for traceability, but do not assume native Wix target IDs can
  be preserved or client-assigned.
- Generate a stable selection layer before executing writes. Use the shared
  `import-recovery.js` runtime helpers where available, or vendor equivalent helpers into
  the project. Selection must:
  - apply `--entity` before entity write execution,
  - apply `--source-type` against deterministic source subtype fields such as
    WooCommerce `type: "subscription"`,
  - exclude local-crosswalk hits in `--missing-only` mode and count them as
    `alreadyPresent`,
  - select latest failed attempt rows for `--failed-only`,
  - select latest deferred/needs-verification attempt rows for `--deferred-only`,
  - emit an execution selection summary before the first write.
- Native Wix creates must perform a local crosswalk lookup immediately before every create,
  even after pre-selection. If a crosswalk row is found, skip idempotently and count the
  record as already present instead of writing a duplicate.
- Every selective/resumed/partial run must append `execution/recovery-log.json` with a
  standard recovery entry containing: recovery id, timestamp, selection filters, reason,
  records selected, records attempted, imported, already present, failed, deferred,
  crosswalk changes, summary changes, operator-visible outcome, and links to detailed logs.
  Recovery artifacts are append-only; never overwrite or collapse prior recovery actions.
- Update `execution/live-import-summary.json` through a shared summary writer, not by
  hand-editing counters in recovery-specific code. The recovery-log entry must include
  the summary delta so final state can be reconstructed from standard execution artifacts.
- For public routed entities, consume mapping `urlPolicy` and initialize local URL
  preservation state before writes. Generate deterministic helpers, or vendor the shared
  `url-preservation-state.js` behavior, to:
  - write `base-paths.json` from entity-level route policies
  - derive `sourceRelativeUrl` from `sourceUrlField` or `sourceBasePath + sourceSlugField`
  - preserve the original `sourceSlug` before any normalization
  - record `plannedTargetSlug` before create/update
  - record `actualTargetSlug` and `actualTargetRelativeUrl` only after the Wix response or
    a safe target lookup proves them
  - append replacement rows to `url-ledger.ndjson` instead of editing rows in place
  - write `redirects.ndjson` only when both relative URLs are known and differ
  - write `unresolved.ndjson` with `pending_target_route`, `target_url_missing`,
    `source_url_missing`, or `manual_review` when a concrete preserved URL or redirect
    cannot be derived safely
  - never silently overwrite a source slug with a normalized slug
- URL ledger resume must replay the latest valid `url-ledger.ndjson` row by
  `sourceStableKey + sourceRelativeUrl`, derive expected rows from extracted source
  records and `urlPolicy`, merge target IDs from the local crosswalk, and query Wix only
  when the write spec declares the lookup safe.
- For native Wix entities, generate and use the shared local-state runtime
  (`local-state.js`) whenever the target API does not expose a client-controlled
  source-id field. Load `state/crosswalk/crosswalk.ndjson` at startup, rebuild missing or
  corrupt index caches from it, append a `started` row to
  `state/attempts/write-attempts.ndjson` before each native write, write the confirmed
  crosswalk row after success, then mark the attempt `imported`. **Slug-based dedupe does
  NOT work** — Wix rewrites slugs; never rely on it.
- Only fetch CMS `ImportCrosswalk` rows as a pre-execution seed for existing-site,
  legacy, recovery, or delta flows, and only when valid local crosswalk state does not
  already exist. Dedupe downloaded mirror rows by `sourceStableKey` and newest valid
  `updatedAt`. Never use CMS as the ordinary resume source after local state exists.
- **Conditional-entity gating must match the verification artifact exactly.** When an
  entity's import is gated on a `setup/setup-verification.json` item (e.g. an unverified
  native path that setup must promote), the generated lookup must key on the same field
  the generated setup runner writes (requirement id vs step id — pick one and use it in
  both). A mismatched key silently reports "not verified" (fails safe into deferral, but
  would wrongly defer a passing entity).
- The optional CMS mirror collection name is **`ImportCrosswalk`**. If upstream artifacts
  still say `MigrationRefs`, normalize them to `ImportCrosswalk` in the generated code and
  note the normalization in `execution/review/import-plan.md`.
- **Attach every related entity, don't just create it.** Creating a tag/category is not the
  same as linking it. Blog posts attach tags via `tagIds` (GUIDs) and categories via
  `categoryIds` on the draft-post create — collect the resolved ids and pass them, or the
  taxonomy exists on the site but `postCount` stays 0 (a builder bug we hit before).
- **Taxonomy creates are not idempotent**: treat `409 ALREADY_EXISTS` as success
  AND resolve the existing entity's id (e.g. `listBlogTags`) so it can still be attached —
  don't drop it.
- **Blog rich text is chunked for you.** `convertHtmlToRichContent` transparently splits HTML
  over the 30k Ricos cap and merges the node arrays; pass full HTML, don't pre-truncate
  or skip large posts. This is the **blog** path only — a blog post's `richContent` really is a
  Ricos document. Stores products must NOT use it: their HTML goes in `plainDescription` and Wix
  converts it server-side (see the product-description rule above).
- Use the shared runtime for batching, retries, throttling, checkpoints, and audit/report
  emission rather than generating those mechanics ad hoc.
- Use the deterministic execution-state preparation contract before native writes. Do not
  let generated import code query CMS directly for resume; CMS mirror rows must be copied
  into local crosswalk state first.
- Do not hardcode secrets.

## Headless storefront codegen rules

When the delivery mode includes a headless storefront (i.e. a `wix-headless` Astro project
is generated alongside the import), apply these rules in addition to the general codegen
rules above.

**Cursor-paginated catalog queries (required).** Generated shop and category pages must
never call `queryProducts().limit(N).find()` once and stop. Wix Stores returns at most 100
items per page — a store with more than 100 products silently truncates the listing.
Always generate a cursor loop in the shared catalog helper and call it from every page
that lists products:

```ts
async function listAllProducts(): Promise<Product[]> {
  const all: Product[] = [];
  let result = await products.queryProducts().limit(100).find();
  all.push(...result.items);
  while (result.hasNext()) {
    result = await result.next();
    all.push(...result.items);
  }
  return all;
}
```

Apply the same pattern to category-filtered product queries (`productsInCategory`). This
is a data-correctness requirement, not a UX preference.

**Product count display (required).** Every generated shop and category page must display
the total product count (e.g. `מציג X מוצרים` for RTL Hebrew sites, or equivalent).
The count is `products.length` after the cursor loop completes and must appear above the
product grid.

**`client:load` for primary CTA islands (required).** Generated product detail pages must
mount add-to-cart and cart-view React islands with `client:load`, not `client:only`.
`client:only` omits the component from the server-rendered HTML entirely — it is
invisible to crawlers and does not render until after full React hydration.
`client:load` hydrates immediately on page load. `client:only` must never be the default
for a primary call-to-action.

**URL redirect file for WooCommerce sources (required).** When the source platform is
WooCommerce and the delivery includes a storefront, generate `frontend/public/_redirects`
mapping old WordPress URL patterns to the new Astro routes. At minimum:

```
/product/:slug         /products/:slug        301
/product-category/:slug  /category/:slug      301
/shop/                 /shop                  301
/?p=*                  /                      301
```

Also generate `frontend/src/pages/product/[slug].astro` (singular path) as a redirect
shim to `/products/[slug]` (plural path). This prevents 404s on all inbound links from
the old site. Derive the source URL pattern from the source profile captured during
discovery; `/product/` is the standard WooCommerce single-product path.

The `_redirects` file and redirect shim are required migration outputs, not optional
polish.

## When codegen must halt to LLM

Emit `execution/llm-handoff.json` with `needsLlm: true` when any of the following occur:

- a native Wix path exists but only an unverified write surface is available
- setup artifacts are insufficient to generate deterministic setup execution
- transform semantics remain unresolved
- a multi-pass relationship strategy cannot be derived deterministically
- the runtime contract required from `rp-target-wix` is missing for the mapped target

## Output

Summarize which files were generated, which entities they cover, and any remaining implementation gaps.

<!-- chapter:end slug=rp-import-codegen -->

---

<!-- chapter:begin slug=rp-mapper position=13 -->

## 13. rp-mapper

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-mapper/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-mapper/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-mapper.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (3), referenced from this skill's directory:
  - `lib/mapping-resolve.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-mapper/lib/mapping-resolve.js
  - `scripts/resolve-mapping.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-mapper/scripts/resolve-mapping.js
  - `wix-business-solutions-taxonomy.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-mapper
description: >-
  Maps discovered source entities and fields to Wix targets and documents lossiness. Use
  when creating machine-readable mapping artifacts and review markdown after discovery.
---

# rp-mapper

Create a mapping plan from the discovered source schema into Wix entities and Wix data structures.

## Purpose

This skill translates source entities and fields into Wix targets. It should define what each source record becomes in Wix, how fields transform, and where custom schemas or extended fields are required.

## Required inputs

- `migrations/<project>/source-schema.json`
- `migrations/<project>/source-profile.md` when available
- `migrations/<project>/orchestration/checkpoints.json`
- `migrations/<project>/orchestration/decisions.json`
- any existing Wix target-model constraints supplied by the user

Read **only** these canonical artifacts by default. Do not ingest the raw discovery dump
(`migrations/<project>/data/...`) wholesale — it is platform-specific and large. Instead, when an
entity's mapping is ambiguous, follow that entity's `rawFile` pointer in `source-schema.json` to open
just that one raw file for verification. Skip entities whose `inUse` is `false` (advertised by the
source but holding no records).

### Adapter-supplied hints in `sourceMeta`

A source adapter may pre-fill part of the mapping when it recognizes the source format. Two
`sourceMeta` keys carry that, and both are optional — a schema without them maps exactly as
before:

- **`sourceMeta.mappingHints[]`** (`{ column, wixTarget, matchedAlias }`) — an **advisory**
  pre-fill, e.g. from a recognized vendor's CSV profile. Seed the corresponding
  `fieldMappings[]` entries from it so the user reviews and corrects instead of authoring from
  scratch, and record `decisionProvenance: source_platform_rule`. It is never authoritative:
  the source's own field list still governs, and every hint still passes through the mapping
  review checkpoint.
- **`sourceMeta.drift.unmappedColumns[]`** — real source columns the adapter's profile did not
  recognize. Each one must be explicitly mapped or explicitly recorded as skipped; leaving
  them unaddressed is how a stale profile turns into silently dropped data.

In addition to the project artifacts above, consult the relevant bundled adapter resource
when the mapping depends on platform behavior that should not be guessed:

- source-side read/entity semantics from `replatform/resources/rp-source-<platform>/SKILL.md`
- Wix target constraints and domain behavior from `replatform/resources/rp-target-wix/SKILL.md`
- Wix target entity suitability from
  `replatform/resources/rp-target-wix/scripts/domain-knowledge.js`

Treat those adapter resources as the authoritative home for platform knowledge. Do not
copy source/Wix behavioral rules into the mapping plan as if they originated there; cite
and apply them there.

## Deterministic first — resolve before you reason

**Do not author field mappings by hand when a vendor overlay already knows them.** Run the
deterministic resolver first and let it do the mechanical join; your judgement is needed only for
what it cannot decide.

```
node scripts/resolve-mapping.js --fileset <migrations-root>/<project>/data/csv-discovery/fileset.json \
  --out <migrations-root>/<project>/mapping
```

It joins three inputs that are all already data — the header set discovery actually observed, the
vendor overlay's `columnMap`, and `rp-target-wix`'s `lib/wix-target-spec.js` — and writes
`mapping/mapping-resolution.json` plus `mapping/mapping-residue.json`. Exit code `2` means blocked:
a required Wix input is unfilled, or an overlay names a canonical field that does not exist.

What it decides, so you don't:

- **which column feeds which canonical field**, and via which alias (recorded for review)
- **which columns nothing claims** → `residue.unmappedColumns`
- **which mapped columns have no Wix home**, with the declared reason → `residue.unsupportedTargets`
- **which required Wix inputs nothing feeds** → `residue.unfilledRequired` (a blocker)
- **which overlay entries reference an absent column** → `overlayDrift` (the profile is stale)
- **which entities arrive by derivation** rather than by a column → `derivedEntities`, including
  `requiresFaithfulnessLedgerEntry` when the source taxonomy is hierarchical

**Your job is `residue`, and only `residue`.** Each entry needs an explicit decision — map it to a
canonical field, or record it as intentionally skipped with a reason. Leaving an entry undecided is
how a stale overlay turns into silently dropped data.

Two rules keep this honest:

- **The header row read at discovery time is authoritative, not the overlay.** An overlay can only
  claim columns that actually exist; a column it does not know is surfaced, never dropped.
- **When you resolve a residue entry that the overlay *should* have known, fix the overlay** —
  `rp-source-csv/vendors/<vendor>.json` plus a header fixture in `tests/fixtures/csv/headers/`. That
  is a data edit with no skill-logic change, and it means the next run resolves it deterministically
  instead of asking a model again. A mapping decision made twice is a missing overlay entry.

### Do not regenerate the payload builder

`rp-target-wix/lib/wix-build.js` turns a canonical record into a Wix Stores V3 create body
deterministically, and `lib/wix-target-spec.js` declares every field, constant and trap it relies
on. This layer is **vendor-independent** — Shopify, WooCommerce, Magento and BigCommerce all
converge on it — so codegen must **vendor and call it**, exactly as the reader vendors `csv-parse.js`.
Do not emit a per-project `to-wix.js` re-deriving money objects, slug sanitization, choice-by-name
variant references, or the empty `physicalProperties` trap. Those are settled, tested, and
regression-locked against 220 payloads from two live-verified imports
(`tests/mapping/wix-build-oracle-test.js`).

The spec also settles the one price decision that recurs on every vendor: see
`STORES_V3_TARGET.priceResolution`. A (regular, sale) pair is **resolved** into Wix's
`actualPrice`/`compareAtPrice`, not mapped field-for-field — getting it backwards silently
overcharges every discounted product.

## Workflow

1. Read the source discovery artifacts.
2. **Run the deterministic resolver** (above) and read its residue.
3. Identify the target Wix entities for each source entity using the domain knowledge
   reader first. Start from `sourceMeta.candidateTargetRefs[]` when discovery supplied it;
   otherwise run `domain-knowledge.js resolve-source`; use semantic reasoning only when no
   candidate exists and record `unverified` confidence.
4. Decide the resolver residue only, then define any remaining transformations and defaults.
5. Mark gaps where native Wix entities are insufficient — the resolver's `unsupportedTargets`
   already names them with reasons; carry those into the faithfulness ledger rather than restating.
6. Identify requirements for custom collections, extended fields, references, media handling, and rich content normalization.
7. Save the mapping plan.
8. Write a concise user-review summary after the plan is complete.
9. Pause for a mapping review checkpoint before downstream setup/codegen work begins.

## Artifacts to create or update

- `migrations/<project>/mapping/run.json`
- `migrations/<project>/mapping/mapping-plan.json`
- `migrations/<project>/mapping/entity-decisions/<entity>.json`
- `migrations/<project>/mapping/llm-handoff.json`
- `migrations/<project>/mapping/review/mapping-gaps.json`
- `migrations/<project>/mapping/review/mapping-plan.md`
- `migrations/<project>/mapping/review/mapping-summary.md`
- `migrations/<project>/orchestration/checkpoints.json`
- `migrations/<project>/orchestration/approvals.json`

## Minimum contents of the mapping plan

Include for each source entity:

- source semantics in this project, especially when the entity name is generic
  (`comment`, `item`, `entry`, `record`, `media`, `user`, etc.). State what the entity
  actually contains here, based on the discovered data, not just the route name.
- target Wix entity or collection
- selected `targetRef`, `targetDomain`, `targetEntity`, `targetClassification`,
  `importReliability`, `preferredWrite`, and `knowledgeEvidence` when the decision uses a
  bundled domain entity record
- primary key and deduplication strategy
- field mapping table
- transformation rules
- validation rules
- URL preservation policy for public routed entities
- unresolved questions
- setup implications for Wix-side configuration
- media policy when the entity carries media: whether only referenced media is in scope,
  whether the target accepts external URLs directly, and whether media must exist in Wix
  before create/update
- `safeModeReplacements[]` when safe mode is enabled, listing every mapped email or phone
  field that enters an outbound Wix payload. Write the same array in both
  `mapping/mapping-plan.json` for the entity decision and
  `mapping/entity-decisions/<entity>.json`.
- a human-visible safe-mode note in the review markdown whenever any entity has
  `safeModeReplacements[]`, so email/phone replacement coverage is visible without opening
  the JSON artifacts

When a source entity is generic or overloaded, the mapping plan must name the concrete
subtypes or usage contexts it observed. Examples:

- `comment`: blog post comments, product reviews, page comments
- `item`: order line items, catalog items, CMS rows
- `media`: blog hero images, product gallery assets, downloadable files

Do not leave a generic entity label unexplained if the discovered data shows multiple
real-world meanings.

## Safe-mode contact replacement metadata

Safe mode is enabled by default through `config/wix.env` unless the user explicitly sets
`SAFE_MODE=false` before mapping. When enabled, mapping artifacts must emit semantic
contact-channel metadata so generated imports can replace outbound email and phone values
through deterministic shared writer code.

For every mapped entity, mark fields for safe-mode replacement when source evidence,
target-Wix domain knowledge, target field semantics, user-directed mappings, CMS/custom
fields, extended fields, form submissions, metadata, or plugin fields indicate an email or
phone contact channel.

Each `safeModeReplacements[]` entry must include:

```json
{
  "kind": "email",
  "sourcePath": "billing.email",
  "targetPath": "billingInfo.email",
  "required": true,
  "reason": "source and target are email fields"
}
```

`targetPath` is relative to the Wix-shaped object or request body defined by the mapping
decision. Use the shared safe-mode path grammar: object fields (`billingInfo.email`,
`contact.email.email`) and array wildcards (`lineItems[].buyerInfo.email`,
`contact.additionalEmails[].email`). When the same source value is copied to multiple
Wix fields, list each target path.

Load target-side hints from selected `rp-target-wix` entity records'
`safeModeContactFields[]` and merge them with source-side evidence and user mapping
decisions. If `SAFE_MODE=false` before mapping, do not require this metadata for execution.

This metadata must be surfaced in **both** machine and human mapping artifacts:

- write `safeModeReplacements[]` into `mapping/mapping-plan.json`
- write the same `safeModeReplacements[]` into `mapping/entity-decisions/<entity>.json`
- render a per-entity safe-mode line in `mapping/review/mapping-plan.md`
- when any replacements exist, include a short `Safe mode replacements` section in
  `mapping/review/mapping-summary.md` listing the affected entities and the mapped
  email/phone paths

Do not leave safe-mode contact replacement coverage implicit in field tables or only in
JSON. A reviewer should be able to confirm from the review markdown which outbound
email/phone fields will be replaced in safe mode.

## Identity and deduplication rules

Be explicit about the difference between a **source ID** and a **Wix target ID**.

- Preserve the **source ID** in the migration artifacts, generated code, local crosswalk
  state, and any optional CMS mirror collections needed for site-local traceability.
- Do **not** assume native Wix entity IDs can be client-assigned or preserved. For most
  Wix APIs, the target ID is server-assigned.
- When a target is a native Wix entity whose ID cannot be controlled by the client,
  the mapping plan must define a **local crosswalk strategy**:
  `crosswalkAuthority: "local"`, a per-entity `crosswalkStrategy`, and a
  `reconciliationStrategy` used for dedupe, resume, and relationship resolution.
- CMS `ImportCrosswalk` is optional. Use `cmsMirror: "none" | "download" | "upload" |
  "download-and-upload"` to request it explicitly. It is a copy/seed adapter for
  existing-site or handoff reference flows, not the required resume authority.
- Only say an ID is "preserved" when the destination actually has a client-controlled
  field that stores the source ID. Otherwise say the source ID is **tracked** or
  **crosswalked**.

## URL preservation policy

For every in-scope source entity that appears on the public website, include an explicit
`urlPolicy` in `mapping/mapping-plan.json` and the relevant
`mapping/entity-decisions/<entity>.json` artifact.

Minimum `urlPolicy` fields:

- `public`: `true` for public routed entities; `false` for entities intentionally outside
  URL preservation.
- `sourceBasePath`: the observed source base path, such as `/shop/products`; use `null`
  only when the source URL cannot be derived and record that risk.
- `sourceSlugField`: source field holding the slug, when present.
- `sourceUrlField`: source field holding the full/permalink URL, when present.
- `targetBasePath`: known Wix destination base path, or `null` when website-builder route
  selection is deferred.
- `targetSlugField`: planned target slug field, when applicable.
- `preserveBasePath`: whether the future website-builder phase should try to preserve the
  source base path.
- `preserveSlug`: whether generated code should preserve the source slug unless target
  validation or collision handling forces a change.
- `redirectMode`: `record-if-different`, `manual-review`, or `none`.

If the same source entity type has multiple public route shapes, record each observed
route class separately instead of guessing one base path. Slugs are identity and SEO data,
but slug preservation alone is not URL preservation; route/base-path intent must be
captured as data for the future website-builder phase.

Mapping review artifacts must include a short `URL preservation` section listing:

- public entity types with source base paths
- whether base paths should be preserved
- entities whose target route is deferred to the website-builder phase
- known slug normalization, collision, route, or redirect risks

## Verifying Wix APIs

Confirm exact Wix entity/collection/field names before mapping a source field onto
them — never invent a Wix API or field name.

When the mapping depends on Wix runtime behavior rather than just field names, verify and
follow the relevant `rp-target-wix` contract as well. Examples include create ordering,
native tag handling, category assignment requirements, member prerequisites, and whether a
product/media field should be sent as an external URL for Wix-side ingestion.

For entity suitability, do not grep or paste whole domain files into the plan. Use:

```bash
node skills/replatform/resources/rp-target-wix/scripts/domain-knowledge.js summarize-entities --refs <domain/entity,...>
```

Load full entity records only for selected candidates that materially affect the current
project.

- Verify enum **values**, not just names: every `Field.type` you assign must be a real
  member of the Create Data Collection `Type` enum. (Common trap: there is no `SLUG`
  type — a slug maps to a `TEXT` field. Never assign a guessed enum value and flag it
  `unverified`; resolve it or omit it.)
- If a Wix tool surface such as Wix MCP is available in the runtime, use it as a fast
  verification aid for entity, field, enum, app, and setup names.
- If no Wix tool surface is available, rely on `rp-target-wix`'s verified contracts plus
  published Wix REST/SDK documentation and conservative, known-good names. Mark anything
  you could not verify directly as `unverified` in the mapping plan so it is surfaced
  before execution.

## Runtime policy

Resolve ambiguous mappings using the documented default, record the decision and rationale
under the machine mapping artifacts and render them into `mapping/review/mapping-plan.md`,
and keep going. Known fidelity forks
(e.g. comments anonymize vs. skip) should already be answered by the submission intake;
apply those answers rather than re-asking. If a required input is truly missing, surface it
as a blocker rather than silently guessing.

If the source entity name is generic but the observed data disambiguates it, record that
disambiguation explicitly in the machine mapping artifacts and review plan. Do not force later stages to infer what
"comments", "items", or similarly broad labels meant in this specific migration.

## Faithfulness ledger (detect lossiness here, early)

The mapping stage is where lossiness and coverage gaps are *discovered*, so it is where
they must be *recorded* — not at execute time, which is too late to do anything but
report. Maintain a **faithfulness ledger** in `mapping/review/mapping-gaps.json` and render it
into `mapping/review/mapping-plan.md`, listing everything that
will not migrate cleanly, including:

- fields/relationships flattened or dropped (e.g. hierarchy → flat),
- entities skipped (e.g. gated PII),
- **targets with no verified Wix primitive** — if Wix has a native entity, record that
  codegen must use an `unverified` native REST path and notify the RePlatform team about
  the missing writer. Use CMS only when no suitable native Wix entity exists, or when the
  native entity is rejected for fidelity/side-effect reasons.

**Mandatory trigger — hierarchical source taxonomy → flat Wix target.** When a source
entity carries `"hierarchical": true` (or any `parent` self-relation) in
`source-schema.json` and maps to a flat Wix target such as Blog categories, you **must**
write a faithfulness-ledger entry recording that the parent/child hierarchy is dropped on
import (the Wix Blog category target is flat — it has no parent/child relationship). This
is not optional discipline: the flag exists precisely so
the warning is data-driven. Do not map such a taxonomy without the ledger entry. (If
hierarchy must be preserved, the alternative is a CMS-collection taxonomy with a `parent`
reference field — note that trade-off in the ledger instead.)

This ledger is the source the execution-plan report draws from to surface "what we won't
do" to the user **before** consent (`rp-execute-import`). If it isn't recorded here, the
user can't be warned there.

Every selected entity whose domain knowledge includes `IMPORT_UNRELIABLE` must create a
`mapping/review/mapping-gaps.json` entry with the same fixed flag string, the selected
`targetRef`, a project-specific summary, and the chosen fallback or review action.

## Default media scope

Default to importing only media referenced by in-scope entities such as posts, products,
collections, categories, or CMS rows that are actually being migrated. Unattached
source-library media is out of scope by default unless the user explicitly asks for a
library/archive migration.

When an entity carries media, consult `rp-target-wix` for the target behavior:

- if the target accepts external URLs and ingests them in the background, prefer that
  path
- otherwise require media import to Wix first, then attach the resulting Wix media id

## Mapping summary for user review

After `mapping/mapping-plan.json` is written, create
`migrations/<project>/mapping/review/mapping-summary.md` as a
short review artifact for the user. Its purpose is to make the mapping decision easy to
review without forcing the user through the full plan.

The summary should:

- explicitly say that full details live in `mapping/review/mapping-plan.md`
- list each in-scope source entity and its planned Wix target
- call out the main gaps, lossy transformations, skipped entities, and `unverified`
  target paths from the faithfulness ledger
- summarize public URL preservation: source base paths, deferred target routes, and slug
  or redirect risks that affect approval
- mention the biggest setup implications the user should know now (for example: required
  Wix apps, required CMS collections, optional CMS crosswalk mirror, media reachability
  caveat)
- when safe mode is enabled and any replacements exist, include a short summary of which
  entities have outbound email/phone replacements and where they apply
- surface unresolved questions only when they materially affect whether the user should
  approve the mapping

Keep it concise. The user should be able to decide "yes, this is the right migration
shape" from this file alone, then consult `mapping/review/mapping-plan.md` only when they want detail.

Recommended structure:

- one-sentence purpose / pointer to `mapping/review/mapping-plan.md`
- `Source -> Wix targets`
- `URL preservation`
- `Main gaps / lossiness`
- `Important setup implications`
- `Safe mode replacements` when applicable
- `Questions or risks to confirm`

Do not restate full field tables or detailed transformation rules here unless a specific
field-level issue is central to the approval decision.

## Mapping review checkpoint

Once both mapping artifacts exist, reset `orchestration/approvals.json` so
`mapping.status=pending`, then stop and ask the user to review
`migrations/<project>/mapping/review/mapping-summary.md`. The checkpoint should make clear:

- this is a semantic review of what will be migrated where
- the full technical detail remains in `mapping/review/mapping-plan.md`
- downstream setup discovery and code generation will wait for acceptance

Do not proceed to `rp-setup-discovery` or `rp-import-codegen` until the user accepts this
mapping review checkpoint, unless the user explicitly asks to continue provisionally.

## Guardrails

- Do not collapse multiple source concepts into one Wix field without documenting lossiness.
- Call out data that cannot be migrated faithfully — record it in the faithfulness ledger above.
- Do not describe a source entity only by a generic label when the observed data is more
  specific; name the concrete subtype(s) present in the project.
- Keep business rules explicit so `rp-import-codegen` can implement them deterministically.
- Do not guess Wix field, enum, or app names when you cannot verify them. Mark them
  `unverified` and surface the risk before execution.

<!-- chapter:end slug=rp-mapper -->

---

<!-- chapter:begin slug=rp-setup-discovery position=14 -->

## 14. rp-setup-discovery

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-setup-discovery.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-setup-discovery
description: >-
  Derives Wix environment prerequisites (apps, collections, schemas) from an approved
  mapping plan. Use after mapping review and before import code generation.
---

# rp-setup-discovery

Determine the Wix-side setup required before import can run safely.

## Purpose

This skill analyzes the approved mapping artifacts and derives environment prerequisites
such as installed apps, SMC collections, extended-fields schemas, references,
permissions, and any other target-system configuration dependencies.

## Required inputs

- `migrations/<project>/mapping/mapping-plan.json`
- `migrations/<project>/mapping/review/mapping-gaps.json`
- `migrations/<project>/mapping/review/mapping-summary.md`
- `migrations/<project>/orchestration/approvals.json`
- any Wix environment constraints or destination account details provided by the user

## Workflow

1. Confirm the mapping review checkpoint has been accepted, then read the mapping plan and
   mapping summary and identify every non-native requirement.
2. For mapped entities with `targetRef`, load compact domain/entity summaries through
   `rp-target-wix/scripts/domain-knowledge.js summarize-entities` and include
   `wixAppsRequired`, `setupRequirements`, target classification, and write verification
   level in setup analysis.
3. Determine which Wix apps must be installed or enabled.
4. Determine which SMC collections must exist.
5. Determine which extended-fields schemas, references, enums, or validation rules must exist.
6. Capture dependency ordering where setup steps depend on each other.
7. Write a setup artifact that downstream execution can verify.

## Artifact to create or update

- `migrations/<project>/setup/run.json`
- `migrations/<project>/setup/setup-plan.json`
- `migrations/<project>/setup/setup-requirements.json`
- `migrations/<project>/setup/setup-blockers.json`
- `migrations/<project>/setup/llm-handoff.json`
- `migrations/<project>/setup/review/setup-summary.md`
- `migrations/<project>/orchestration/checkpoints.json`

Treat `mapping/review/mapping-summary.md` as the user-facing statement of intent and
`mapping/mapping-plan.json` as
the detailed contract. If they materially disagree, halt and send the workflow back to
`rp-mapper` to correct them before deriving setup requirements.

## Minimum contents

- required Wix apps
- local crosswalk authority and optional CMS mirror requirements
- required collections and schemas
- required field definitions
- permissions or access prerequisites
- setup order
- manual steps vs. steps that can be automated
- verification criteria for each requirement

## Optional source reachability setup for media

If the mapping imports media by URL and the source profile shows `localhost`,
`127.0.0.1`, or another private-only source URL, add an optional setup note for media
reachability. This is **not** a blocker for non-media entities and should not be framed as
a required Wix-side app/setup item. As far as we know today, it only affects Wix Media
import because Wix's import-from-URL API fetches files from Wix servers.

Offer two options in `setup/review/setup-summary.md` and preserve the machine-readable
effect in `setup/setup-blockers.json` or `setup/setup-requirements.json`:

- Expose the local source through a public HTTPS tunnel before live media import.
- Skip/defer media import for the first live run and continue non-media entities.

For ngrok, include:

```bash
brew install ngrok
ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
ngrok http 8090
export WP_BASE_URL=https://<id>.ngrok-free.app
```

Tell the user to use the HTTPS forwarding URL as `WP_BASE_URL` / `SOURCE_URL`, or ensure
generated code rewrites source media URLs from the local base URL to the tunnel base URL.

## Verifying Wix APIs

Confirm that the apps, collections, field types, and references you require actually
exist in Wix with the names/types you state — never invent them.

- Verify at the moment you write the requirement; do not defer it. Verify **enum
  values**, not just names: confirm each `Field.type` against the Create Data Collection
  schema. (Common trap: there is no `SLUG` type — a Wix slug is a `TEXT` field.)
- If a Wix tool surface such as Wix MCP is available, use it as a fast verification aid.
- If no Wix tool surface is available, rely on `rp-target-wix`'s verified contracts plus
  published Wix REST/SDK documentation and conservative, known-good names, and mark the
  requirement `unverified` so a human confirms it before `rp-execute-setup` runs.

## Standing requirement: mute site notifications (spec 0012)

Emit a **`mute-site-notifications`** requirement into `setup/setup-plan.json` and
`setup/setup-requirements.json` whenever mute is in effect:

- `WIX_SITE_STRATEGY=new` — **always**, as a standing rule keyed on the strategy value
  alone. Not derived from mapping decisions, domain knowledge, or agent judgment, and not
  conditional on `WIX_MUTE_NOTIFICATIONS` (for new sites the mute is unconditional).
- `WIX_SITE_STRATEGY=existing` — only when the owner explicitly opted in with
  `WIX_MUTE_NOTIFICATIONS=on`. Under the existing-site default (`off`), emit no
  requirement.

Requirement contents: severity **blocker**, automation mode `automatable`, ordered
**first among setup writes** (before app installs, collection creation, and any other
provisioning), executed via the `muteSiteNotifications` primitive in
`rp-target-wix/lib/wix-writers.js` (VERIFIED 2026-08-04) with a project-identifying
`reason` (`RePlatform migration — <project>`). Verification criterion:
`getSiteMuteState` returns `muted: true`. Auth note for the requirement: the call must
use the CLI-minted site token (`WIX_AUTH_TOKEN`) — an account API key gets 403.

**Config validation (fail fast):** `WIX_MUTE_NOTIFICATIONS=off` together with
`WIX_SITE_STRATEGY=new` is a validation error — halt with the config conflict recorded
rather than emitting artifacts. (`rp-import-codegen` re-validates the same rule.)

## Crosswalk authority and CMS mirror

Local files under `migrations/<project>/state/crosswalk/` are the durable
`sourceId -> targetId` authority for native Wix entities. Do not make CMS setup a minimum
requirement for native-entity idempotency.

Create/provision CMS **`ImportCrosswalk`** only when the approved mapping plan explicitly
requests a CMS mirror (`cmsMirror: "download" | "upload" | "download-and-upload"`). The
mirror is compact site-local reference data and a pre-execution seed source for
existing-site flows when valid local crosswalk state does not already exist. Add a CMS
quota warning whenever a mirror is requested.

If older artifacts call the optional mirror `MigrationRefs`, normalize it to
`ImportCrosswalk` and call out the rename in the artifact so downstream setup/codegen
share one contract.

## Classifying manual vs. automatable

Do not assert "manual" / "cannot self-provision" by assumption — check the API surface.

- Enabling a Wix app (Blog, Members, etc.) **is** automatable via the App Installation
  API; classify it automatable and cite the method. (Common trap: do not mark Blog or
  Members "cannot self-provision" — both are installable via the API.)
- When the migration carries a product catalog, emit a **`stores-catalog-v3` requirement**
  alongside the Wix Stores app install: expected state `catalogVersion: V3_CATALOG`, verified
  read-only via `GET https://www.wixapis.com/stores/v3/provision/version`, severity
  **blocker**, automation mode `automatable` (it is provisioned by scaffolding the site from
  the `commerce` template — see `replatform` → "Headless site creation"). A Stores install
  alone does not imply V3; a `blank`-scaffolded site comes up `V1_CATALOG` and cannot be
  converted, so this must be a first-class requirement rather than an assumption.
- Creating CMS collections is automatable; enabling Wix Data itself is automatable by
  installing the **Wix Data app `appDefId e593b0bd-b783-45b8-97c2-873d42aacaf4`** via the
  App Installation API, after which `POST /wix-data/v2/collections` creates NATIVE
  collections with no `WDE0110` (verified live 2026-06-10; see `rp-execute-setup`).
- Only storage-plan upgrades, external-system credentials, and account billing are
  genuinely manual. Mark something manual only after confirming no API covers it.

## Runtime policy

This phase is deterministic planning work. It does not create a second approval gate.
`setup/review/setup-summary.md` is supporting execution-planning context, not a separate
user approval checkpoint.

Verify names and enums rather than emitting `unverified` by default. Classify
automatability by checking the API surface (see above), not by assumption. If a direct
verification aid is unavailable, use the documented fallback and mark the requirement
`unverified` so downstream steps surface it before execution.

## Guardrails

- Separate required setup from optional optimizations.
- Avoid embedding import logic here; this file is about prerequisites.
- Be explicit about which requirements come from which mapping decision.
- Do not guess setup names or enum values when you cannot verify them. Mark them
  `unverified` and surface the risk before execution.

<!-- chapter:end slug=rp-setup-discovery -->

---

<!-- chapter:begin slug=rp-source-csv position=15 -->

## 15. rp-source-csv

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-source-csv/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-source-csv.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (11), referenced from this skill's directory:
  - `lib/canonical-record.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/lib/canonical-record.js
  - `lib/csv-fileset.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js
  - `lib/csv-fingerprint.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/lib/csv-fingerprint.js
  - `lib/csv-layout.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/lib/csv-layout.js
  - `lib/csv-parse.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/lib/csv-parse.js
  - `scripts/csv-discovery.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js
  - `source-schema.example.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/source-schema.example.json
  - `vendors/bigcommerce.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/vendors/bigcommerce.json
  - `vendors/magento.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/vendors/magento.json
  - `vendors/shopify.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/vendors/shopify.json
  - `vendors/woocommerce.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-csv/vendors/woocommerce.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-source-csv
description: >-
  CSV file source adapter: parse core, vendor fingerprinting (Shopify, WooCommerce, Magento,
  BigCommerce), layout inference, file-set handling, and read contract for codegen. Use when
  the migration starts from one or more CSV/export files instead of a live source URL.
---

# rp-source-csv

CSV **source adapter**. Owns every CSV-specific detail the platform-agnostic skills must not
hardcode: how to parse a file, how to identify which vendor produced it, how its rows group
into entities, how a set of files resolves into entity streams, and what a generated reader
must get right.

## When this skill is used

This is not a stage in the migration flow — it is a reference consulted by two stages:

- **`rp-discovery`** consults the *Capture* section to sample the files and produce the
  canonical `source-profile.md` + `source-schema.json`.
- **`rp-import-codegen`** consults the *Read contract* section to generate a reader that
  extracts CSV data correctly (grouping, derived entities, empty-vs-absent) into durable
  project-local files for the later import step.

`rp-execute-import` never consults this skill — by the time execution runs, the CSV-specific
knowledge is already baked into the generated reader.

## Platform identity

- Source platform: `csv` — a **file-provided** flow, not a URL probe.
- Set `"platform": "csv"` in the emitted `source-schema.json`, and record the detected
  vendor per file in `sourceMeta.vendor` / `sourceMeta.sourceFiles[]`.
- Record `sourcePlatform=csv`, `sourceMode=files_only`, and **every** input file in
  `fileInputPaths` in `orchestration/decisions.json`. Preflight requires no source env keys
  for `csv`; the `files_only` branch enforces a non-empty `fileInputPaths` instead.

## Three layers

1. **A generic core** — `lib/csv-parse.js` (parse, dialect detection, type sniffing) and
   `lib/csv-layout.js` (layout inference, derived entities). Knows nothing about commerce and
   handles any CSV, including hand-rolled ones.
2. **Per-vendor overlays** — declarative JSON profiles under `vendors/` that specialize the
   core with a vendor's fingerprint anchors, known layout, column→Wix pre-map, and quirks.
   v1 ships **shopify, woocommerce, magento, bigcommerce**.
3. **A fingerprint detector** — `lib/csv-fingerprint.js` picks the overlay from the header
   set, or honors a user-stated vendor, and falls back to `custom` below the threshold.

**The overlay is advisory, never authoritative.** The authoritative record of which columns a
file contains is always the header row read at discovery time. The overlay only (a)
*identifies* the vendor and (b) *pre-fills* mappings the LLM would otherwise propose.

## Capture (discovery-time)

Sampling the files to learn their shape — **not** a bulk load.

Before capture, verify `migrations/<project>/config/source.csv.env`. Create it if missing,
using empty values for the user to fill:

```bash
CSV_INPUT_ROOT=            # optional; directory the input paths are recorded relative to
CSV_DELIMITER=             # optional; blank = auto-detect , ; \t |
CSV_ENCODING=              # optional; blank = auto-detect (UTF-8 / BOM)
CSV_VENDOR=                # optional; user-stated vendor overrides fingerprint detection
CSV_MEDIA_URL_REWRITE_FROM=
CSV_MEDIA_URL_REWRITE_TO=
```

Unlike `source.wordpress.env`, **`source.csv.env` is not secret-bearing** — it holds
delimiter/encoding/vendor/rewrite hints only, no credentials. Regular file handling applies.
There is **no acquisition-mode fork and no credentials request** for a CSV run.

1. Run the deterministic capture script **from this skill's directory** (the folder
   containing this `SKILL.md`; see `CONVENTIONS.md`):

   ```
   node scripts/csv-discovery.js --file <path> [--file <path> ...] \
     --out-dir <migrations-root>/<project>/data/csv-discovery
   ```

   `--file` is repeatable; pass the **whole file set in one run** so roles and split files
   are resolved together. For long runs, pass `--progress-log <path>` and poll it per
   `CONVENTIONS.md#progress-log-polling`.

   Useful options: `--vendor` (override detection), `--delimiter` / `--encoding` (override
   detection), `--role <path>:<role>` (name a file's entity when the script cannot),
   `--allow-header-superset` (union split files whose headers differ), `--scan-rows`.

2. Per run the script: reads each header → detects the dialect → fingerprints the vendor per
   file → assigns a role per file and concatenates split files → resolves the layout (overlay
   or generic classifier) → derives `column-values` entities → diffs the real header against
   the matched profile → samples head and tail rows → infers per-column
   type/cardinality/requiredness/examples → writes the raw dump. Because headers are read
   fresh every run, discovered fields are never limited to what the profiles know.

3. It writes into `--out-dir`:
   - `README.md` — the index: files, vendor detection with evidence, layout, entities,
     profile drift, warnings.
   - `fileset.json` — machine-readable `sourceFiles[]`, streams, vendor, dialect, layout,
     drift, mapping hints, entities and relations. **This is what `rp-discovery` synthesizes
     `source-schema.json` from.**
   - `raw-capture.json` — per-column detail plus head/tail samples.
   - `<role>--<entity>.md` — one per entity.

4. **Honor the halt.** When the capture reports `halt: true` (ambiguous layout, unknown file
   role, conflicting split-file headers, mixed vendors for one role), stop and ask the user
   the question named in the warning rather than synthesizing a schema from a guess.

5. **Record counts are scan-based.** The script streams each file in full for its row count
   and retains the first `--scan-rows` rows (default 5000) for inference. When
   `raw-capture.json` reports `scan.truncated`, say so in `source-profile.md` instead of
   presenting a sampled count as exact.

The raw capture is evidence, not a hand-off artifact. `rp-discovery` synthesizes it into the
canonical artifacts and records traceability pointers (`rawDiscovery`, per-entity `rawFile`).

## Layer 1 — the generic core

`lib/csv-parse.js` is the CSV analogue of `rp-source-wordpress`'s `wp-http.js`: dependency-free
and reused by **both** the capture script and the generated reader so the sampler and the
importer parse identically. It handles what breaks a naive `split(',')`:

- **delimiter detection** (`,` `;` `\t` `|`), scored by field-count consistency **and** field
  count, since a semicolon file whose text fields each contain a comma splits perfectly
  consistently on `,` too. `CSV_DELIMITER` always wins.
- **RFC-4180 quoting**: embedded delimiters, embedded newlines, `""` escapes. A quote opens a
  field only at field position 0; `a"b` is three literal characters.
- **encoding / BOM**: the mark is stripped before the header is read. A UTF-16 BOM fails fast
  naming `CSV_ENCODING` rather than producing mojibake — full transcoding is out of scope.
- **line endings**: `\n`, `\r\n`, and lone `\r`, including a `\r\n` split across a chunk
  boundary. A newline inside quotes is data and is preserved verbatim.
- **empty vs absent** (below).
- **streaming**: rows are yielded without loading the file.

### Empty vs absent is detected, not assumed

Whether an empty cell means "absent" is a property of the **writer's quoting policy**, not of
the datum: exporters that quote every field (Excel, Magento) can only ever produce a quoted
empty, and exporters that quote nothing unnecessary can only ever produce an unquoted one.

So the parser returns **strings only, never `null`**, with an optional parallel `quoted[]`
array, and `detectEmptyPolicy` decides once per file:

- `present-if-quoted` — the file contains **both** forms, so it genuinely distinguishes them.
- `always-empty` — it contains only one form, so the distinction carries no information.

The policy is recorded in `sourceMeta.dialect.emptyPolicy` and applied through
`coerceEmpty(value, isQuoted, policy)`. Generated readers must use it, so a required Wix field
is never fed an empty string the source did not actually have. This also keeps split files
(shape C) consistent when two parts were written by different tools.

## Layer 2 — vendor overlays

Each supported vendor is a declarative JSON profile under `vendors/`. Overlays are **data, not
code**, because they are numerous, additive, and pure lookup: adding or updating a vendor is a
data-file edit plus a header fixture, with no change to any skill logic.

```jsonc
{
  "vendor": "shopify",
  "profileVersion": "2026-07",              // provenance; bump when the format changes
  "sourceOfTruth": "https://help.shopify.com/.../csv",
  "anchors": {
    "required": ["Handle", "Title"],        // a FEW STABLE columns — never the full schema
    "strong":   ["Body (HTML)", "Variant SKU", "Variant Price", "Option1 Name", "Vendor"],
    "negative": ["Attribute 1 value(s)"]    // presence disproves this vendor
  },
  "layout": {
    "pattern": "grouped-by-key",
    "groupKey": "Handle",
    "continuation": "blank-key",
    "parentEntity": "product",
    "childEntity": "variant",
    "columnGroups": [                       // which columns belong to which entity
      { "entity": "variant", "prefixes": ["Variant ", "Option1 "], "columns": ["Cost per item"] },
      { "entity": "image",   "prefixes": ["Image "] }
    ]
  },
  "derived": [                              // entities synthesized from a column's values
    { "entity": "category", "fromColumn": "Product Category",
      "hierarchySeparator": ">", "hierarchical": true, "linkPolicy": "leaf" }
  ],
  "fileRoles": [{ "role": "product", "filenameHints": ["products_export", "products"] }],
  "columnMap": [{ "wixTarget": "variant.sku", "aliases": ["Variant SKU", "SKU", "variant_sku"] }],
  "quirks": ["Blank-Handle rows continue the previous product", "..."]
}
```

Column matching is **normalized + alias-based**: lowercase, NFKC, strip punctuation and
whitespace. `Body (HTML)` → `bodyhtml`, and `Option1 Name` ≡ `Option 1 Name`. This absorbs
casing changes and renames without a code change.

**`columnGroups` is the one thing the data cannot supply.** Shopify's `Image Src` and
`Variant SKU` both vary within a product group, so only a declaration can say that one is a
third collection and the other is the variant. Generic derivation still covers every column
the overlay does not name — including new columns a vendor adds later.

An overlay feeds two stages:

- **Discovery** — `layout` + `quirks` resolve grouping deterministically, avoiding the generic
  ambiguity halt. Layout traps are stable and hard to infer, which is what makes them worth
  encoding.
- **Mapping** — `columnMap` pre-fills `sourceMeta.mappingHints[]` so `rp-mapper` reviews and
  corrects instead of authoring from scratch.

A `custom` file has no overlay: it runs core-only, and the mapper leans more on the LLM and the
user at the same review checkpoint. A fully stale overlay degrades to exactly that experience —
the floor, not a failure.

## Layer 3 — fingerprint detection

`lib/csv-fingerprint.js` identifies the vendor from the header row:

```
requiredRatio = |required ∩ header| / |required|      (normalized EXACT match)
strongRatio   = |strong   ∩ header| / |strong|
score = requiredRatio === 1 ? 0.50 + 0.50·strongRatio
                            : 0.60 · requiredRatio · (0.50 + 0.50·strongRatio)
negative anchors present -> score *= 0.35
match iff score >= 0.70 AND matchedStrong >= 2 AND (winner − runnerUp) >= 0.15
```

- **Anchors never alias-match.** Aliases are permissive for *mapping*; permissiveness is poison
  for *identification* — Shopify's `variant.sku` aliases include a bare `SKU`, which every
  WooCommerce and Magento export also has.
- **Ratios, not counts.** Anchor ratios are bounded 0..1, so a wide CSV cannot inflate a match.
  Do **not** reintroduce a header-count normalization: dividing by header count penalizes wide
  files and inflates narrow ones, which is what lets a 6-column near-miss flip to a false match.
- Ties, sub-threshold scores, and files with fewer than two strong anchors → `custom`.
- A **near miss** (a drifted vendor export that lost a required anchor) is reported as
  `nearMiss` so it becomes a user question, never silence.
- `CSV_VENDOR` / `--vendor` **overrides** detection, but detection still runs: a disagreement
  is recorded as `conflict: { stated, detected }` and surfaced.

## Layout — how rows group

`lib/csv-layout.js` resolves the layout: `flat`, `grouped-by-key` (blank-key or repeat-key
continuation), or `sectioned`. It runs for **custom** files and as a **fallback** when a matched
overlay does not pin the layout — including when the overlay's declared `groupKey` is missing
from the header, which is recorded as `overlay-groupkey-missing` rather than crashing.

### A pinned layout is verified, not believed

An overlay's `continuation` is a **claim about the rows**, not a fact about the vendor, and it is
checked against the sample before it is used. Shopify is the reason: most exports blank the
`Handle` on continuation rows, but some repeat it on every row. Pinning `blank-key` against a
repeating file fails *silently* — no key is ever blank, so no row ever continues a group, and a
50-product file is captured as 82 single-row products with 32 blank titles.

The check fires only when the two modes would group **differently**: with one row per group both
readings produce the same boundaries, and reporting drift there would be noise.

| Pinned | Rejected when | Then |
| --- | --- | --- |
| `blank-key` | the key column is never blank | continuation corrected to `repeat-key` |
| `repeat-key` | the key column is blank on some row | continuation corrected to `blank-key` |

On rejection the generic classifier is consulted about the **disputed field only**. Which column
groups the rows is the part vendors do not change, and the overlay's `groupKey` has just been
confirmed present in the header — so a generic candidate naming that same column corroborates the
observed mode *even when the classifier as a whole halted*, because its halt is about **picking**
a key, not about this one. When corroborated, the continuation is corrected and the rest of the
overlay (entity names, `columnGroups`) is kept, with `source: overlay-continuation-corrected` and
the classifier's confidence instead of the pin's `1`. When no candidate groups on the declared key
either, nothing is trustworthy enough to pin and the whole inferred layout is used
(`inferred-after-overlay-mismatch`).

Both outcomes append to `layoutConflicts[]` and raise a warning — never a silent correction. Do
**not** "fix" this by re-pinning `repeat-key` in the overlay: exports vary, so that only moves the
same silent failure to the other half of them.

Deterministic-first: when signals are clear it sets the pattern; when they are ambiguous it
returns `pattern: 'unknown'` with `halt: true` and the ranked candidates, and discovery stops to
ask the user.

Signals worth knowing:

- **blank-key** requires the key column's non-blank values to be unique — a key never reappears
  after an intervening key. Two candidate columns that produce the **same** group boundaries
  (Shopify's `Handle` and `Title` both go blank on continuation rows) are *equivalent labels*,
  not an ambiguity; different boundaries are a genuine ambiguity and halt.
- **repeat-key** additionally requires a supporting parent column — some other column constant
  inside each run and different between runs. Without it, a flat product list merely *sorted* by
  category is indistinguishable from a grouped one, and would be split into bogus parents and
  children.
- **sectioned** requires a column-population skew: partitioned by the discriminator, some other
  column's blank rate must differ by ≥ 0.7 between partitions. Without that test every
  `Published` or `Tax class` column is a false discriminator.
- When a blank column's blankness is a pure function of the discriminator value, the blank-key
  reading is already **explained by the sectioning** and does not compete with it — this is what
  keeps a WooCommerce export from looking ambiguous.

### Parent vs child columns

A column belongs to the parent, to the child, or to a declared collection. The rule is
**within-group variance**, not blankness:

- A column that varies inside a group in ≥80% of multi-row groups is a **child** column.
- A column blank on every continuation row and populated on the head row is a **parent** column.
- Anything else is reported as `ambiguousColumns` rather than guessed.

The naive rule — "child columns are the ones populated on continuation rows" — is **wrong** for
the most important case: a Shopify product's first variant lives on its group head row, and a
single-variant product has no continuation rows at all.

Precedence: overlay `columnGroups` assigns the entity, generic derivation covers everything the
overlay does not name, and a disagreement is recorded in `layoutConflicts[]` with the overlay
kept. An overlay can never *lose* a column — that is what "advisory, never authoritative" means
in practice.

`layoutConflicts[]` carries both kinds of disagreement, because a reviewer asks the same question
of both — *where did this capture stop believing the overlay?* Column-level entries name a
`column`; layout-level entries (`overlay-continuation-mismatch`, `overlay-groupkey-missing`,
`overlay-discriminator-missing`) carry a `kind` and a `resolution`.

A child's `recordCount` is the number of rows that populate **at least one** child column, not the
group's row count: a continuation row carrying only an extra image is an image, not a variant.

## Entity origins — rows, row groups, and column values

An entity in a CSV migration comes from one of three origins, recorded per entity so the reader
knows how to materialize it:

- **`file-rows`** — one row = one entity (a flat `categories.csv`; also a sectioned file's
  parent or child rows, with `filter: { column, includeValues | excludeValues }`).
- **`row-group`** — a group of rows = one entity (a Shopify product, its variants, its images),
  with `groupKey`, `continuation`, and `columnGroup` where relevant.
- **`column-values`** — the **distinct values of a column** = an entity set. This is how
  categories and tags arrive in every named vendor's export.

For `column-values`, capture records the source column, both separators, and the distinct-value
count; the reader emits a synthetic entity file (one record per distinct path, ancestors
included) plus the linking relation. Because the result is an ordinary entity from `rp-mapper`'s
point of view, the mapper maps it with no special handling and the existing dependency ordering
creates categories before products.

Non-obvious rules the implementation depends on:

- **Split the multi-value separator first, then the hierarchy separator.** WooCommerce's
  `Clothing > Shirts, Sale` is two categories, one nested. The other order produces a category
  literally named `Shirts, Sale`.
- A separator inside a value is escaped by quoting **inside the cell**
  (`"Home, Garden > Tools", Sale`), so the inner split is RFC-4180 aware too.
- Records are emitted **depth-ascending**, so a parent is always created before its child.
- Only the **leaf** is linked to the product (`linkPolicy: "leaf"`); whether ancestors are also
  attached is a mapping decision.
- Only **group-head rows** are read, so a Woo variation row does not double-count its product's
  categories.
- Generic detection for custom files is deliberately narrow: `>` is auto-detected but `/` is
  not (it matches every URL column), markup-bearing columns are skipped, a multi-value split
  must collapse the distinct count, and a column is only auto-derived when its **name** says it
  is a taxonomy. Everything else is proposed to the user.

Set **`hierarchical: true`** on a nested derived entity. A hierarchical source taxonomy mapped
to a flat Wix target triggers the mapper's mandatory faithfulness-ledger entry; without the flag
the flatten happens silently.

## File sets — roles and concatenation

`lib/csv-fileset.js` resolves a list of input paths into logical entity streams. Four shapes
occur in practice; **A, B and C are v1**, D is v1.1:

| Shape | Example | Handling |
|---|---|---|
| A. one file, grouped | Shopify `products_export.csv` | one stream, several entities |
| B. many files, independent entities | `products.csv` + `customers.csv` | one stream per role |
| C. many files, same entity split | `products-part1.csv` + `-part2.csv` | one stream, `partOf` recorded |
| D. many files, related entities | FK columns across files | **v1.1** — schema designed, detection deferred |

**Role assignment** walks a ladder, recording `roleSource` at each step: explicit `--role` →
vendor `fileRoles.filenameHints` → generic filename tokens (whole tokens only, last token wins,
so `product-images.csv` is media) → header anchors → `unknown` **with a halt**. A role is never
invented from the filename stem; an entity map full of `weird-export` entities is worse than one
honest question.

**Same-entity concatenation (shape C)** requires the same *normalized column-name set*:

- reordering, case, whitespace and BOM differences are tolerated — names are the identity, and
  each part keeps its own `columnOrderMap`;
- a subset/superset header does **not** silently concatenate. It is legitimate (a re-export
  after the vendor added a column) but it changes what a blank cell means for half the rows, so
  it halts with the exact diff; `--allow-header-superset` unions the parts and records
  `driftAcrossParts`;
- files sharing a role but only a small column overlap (Jaccard < 0.9) are a `role-collision`,
  not two parts of one export;
- part order is a numeric-suffix-aware natural sort, so `part10` follows `part2`;
- `partOf` and `rowOffset` are recorded so any extract row traces back to its file.

**Mixed vendors** are allowed *across* roles (Shopify products plus a hand-rolled
`categories.csv` is a real bundle) but two files of the **same** role from different vendors
halt.

## Read contract (codegen-time)

What a generated CSV reader must get right. Codegen selects it when
`source-schema.json.platform === "csv"`.

The reader is an **extractor**, not an in-memory loader: it parses with the shared parse core,
applies the resolved layout to group rows into per-entity records, and writes **durable
per-entity NDJSON record streams (`<entity>.ndjson`, one record per line) plus a
`manifest.json`** into `data/source-extract/` that `rp-execute-import` reads.

Emit records **as they are grouped**, streaming through `ndjson.js`'s writer — do not
accumulate an entity's records in an array and serialise at the end. A 100k-row export must
never require the whole entity in memory, and an interrupted extract should leave a valid
readable prefix. See `rp-import-codegen` -> "Record streams are NDJSON".

- **Emit canonical records, or convert with `lib/canonical-record.js` — never hand-write the
  rename.** `rp-target-wix/lib/wix-build.js` consumes the vendor-neutral vocabulary declared in
  `wix-target-spec.js` (`description`, `brand`, `weight`), while a reader naturally names fields the
  way its vendor thinks (`bodyHtml`, `vendor`, `weightKg`). `lib/canonical-record.js` holds that
  rename as **per-vendor data** (`READER_FIELD_MAPS`) and self-validates at require time, so a typo
  is a load error instead of a field that silently never reaches Wix. Vendor it alongside
  `csv-parse.js` and call it; do not re-derive the mapping in a project-local transform. It is
  regression-locked with the builder against 220 payloads from two live-verified imports
  (`tests/mapping/wix-build-oracle-test.js`).
- **Reuse `lib/csv-parse.js` — do not regenerate it.** Vendor a copy into the project (e.g.
  `src/lib/`) and import from it, exactly as the WordPress reader vendors `wp-http.js`. The
  reader then carries only per-project orchestration: which entities to emit, the grouping loop,
  type coercion, and transform glue. One tested parse core is what makes the sampler and the
  reader agree.
- **Grouping is the reader's core job.** Replay the resolved layout: a blank key extends the
  current group; a discriminator column routes parent vs child rows, resolving
  `parentRefColumn` (Woo's `Parent` carries `id:123` or a SKU, and does **not** guarantee that a
  variation follows its parent).
- **Iterate the file set** by role, treating `partOf` entries as continuations of the same
  logical stream and honoring each part's own column order.
- **Materialize derived entities**: collect the distinct values of a `column-values` column (and
  their ancestors when hierarchical) into their own entity file, depth-ascending, and emit the
  linking relation. Do not invent Wix ids at extract time — the import stage resolves them
  through the `ImportCrosswalk`.
- **Coerce per inferred type**, and apply `coerceEmpty` with the recorded `emptyPolicy` so
  required Wix fields are not fed empty strings.
- CSV values are plain text and are written as-is — no entity-decoding step unless a specific
  vendor is known to double-encode.
- No auth, no pagination, no rate limits: the entire WordPress transport section collapses to
  "open the file once".

## Media

Image columns hold either public URLs (Wix-reachable) or local paths / private URLs (not
reachable — Wix Media import fetches from Wix servers, the same concern as a WordPress
`localhost` source). Capture flags these as `media-reachability` warnings. Record the note in
`source-profile.md` and offer the two choices:

- rewrite via `CSV_MEDIA_URL_REWRITE_FROM` / `CSV_MEDIA_URL_REWRITE_TO`, or expose the files at
  a public HTTPS URL, or
- skip/defer media import and continue with non-media entities.

## Schema shape

`source-schema.example.json` (in this skill folder) is the template `rp-discovery` follows when
emitting `migrations/<project>/source-schema.json`. It is a shape to follow, not a strict schema
to validate against. Keep the platform-agnostic core stable; push CSV specifics
(`sourceFiles`, `vendor`, `dialect`, `drift`, `mappingHints`, `joins`) into `sourceMeta`, and
give every entity an `origin`.

## Contract tests

Contract tests live in the repo's `tests/` tree, not in this bundle — a fixture shipped to a
partner is dead weight. Run them from the repo root:

```
bash tests/run-all.sh source-csv
```

Or individually: `node tests/source-csv/csv-parse-contract-test.js` (also
`csv-fingerprint-`, `csv-layout-`, `csv-fileset-`). Fixtures are in `tests/fixtures/csv/`.

They cover the dialect/quoting/BOM/line-ending/empty-policy matrix and chunk-boundary feeding;
per-vendor header fixtures, the `custom` fallback, near misses and drifted headers; the layout
patterns plus their false-positive guards, the ambiguity halt and the verification of a pinned
continuation mode in both directions; derived-entity split order,
ancestors and the URL guard; and role assignment, split-file concatenation, header-superset
halts and part ordering.

When a vendor's format drifts, fix `vendors/<vendor>.json` and add a header fixture to
`tests/fixtures/csv/headers/` — no skill logic changes.

<!-- chapter:end slug=rp-source-csv -->

---

<!-- chapter:begin slug=rp-source-wordpress position=16 -->

## 16. rp-source-wordpress

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-source-wordpress.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (4), referenced from this skill's directory:
  - `lib/wp-http.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js
  - `lib/wp-route-classifier.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js
  - `scripts/wp-discovery.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js
  - `source-schema.example.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-source-wordpress/source-schema.example.json

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-source-wordpress
description: >-
  WordPress and WooCommerce source adapter: REST capture, auth, pagination, and read
  contract for codegen. Use when the source platform is WordPress or WooCommerce.
---

# rp-source-wordpress

WordPress / WooCommerce **source adapter**. Owns every WordPress-specific detail the
platform-agnostic skills must not hardcode: how to capture the schema, how to read the
data, auth models, pagination, and REST quirks.

## When this skill is used

This is not a stage in the migration flow — it is a reference consulted by two stages:

- **`rp-discovery`** consults the *Capture* section to sample the source and produce the
  canonical `source-profile.md` + `source-schema.json`.
- **`rp-import-codegen`** consults the *Read contract* section to generate a reader that
  bulk-extracts WordPress data correctly (auth, pagination, `wc/v3` vs `wp/v2`) into
  durable project-local files for the later import step.

`rp-execute-import` never consults this skill — by the time execution runs, the
WordPress-specific knowledge is already baked into the generated reader code. Keeping the
WordPress knowledge here is what lets the rest of the workflow stay platform-agnostic.

## Platform identity

- Source platform: WordPress (core REST `wp/v2`), optionally WooCommerce (`wc/v3`).
- Detect by hitting `<base-url>/wp-json/` — the REST index lists advertised namespaces.
- Set `"platform": "wordpress"` (and note WooCommerce presence in `sourceMeta`) in the
  emitted `source-schema.json`.

## Capture (discovery-time)

Sampling the source to learn its shape — **not** a bulk export.

The Wix domain knowledge base may be used as a classifier supplement during synthesis,
but not as a sampling mandate. When a discovered WordPress/WooCommerce route matches a
known alias, annotate the emitted entity with `sourceMeta.candidateTargetRefs[]`; do not
turn that hint into a target decision here. Keep frontend, theme, runtime, admin, and
transient routes skipped by default even if a knowledge entry exists for a related
setup/config target.

Before capture, verify `migrations/<project>/config/source.wordpress.env`. Create it if
missing, using empty values for the user to fill:

```bash
WP_BASE_URL=
WP_USERNAME=
WP_APPLICATION_PASSWORD=
WP_MEDIA_URL_REWRITE_FROM=
WP_MEDIA_URL_REWRITE_TO=
WC_CONSUMER_KEY=
WC_CONSUMER_SECRET=
```

Before requesting any WordPress / WooCommerce credentials for a URL-based migration, ask
the user which data scope they want:

- `public content only`
- `also include private/authenticated data`

This choice is mandatory and must be explicit. Do not ask for `WP_USERNAME`,
`WP_APPLICATION_PASSWORD`, or WooCommerce keys until the user chooses `also include
private/authenticated data`.

Required for a complete WordPress/WooCommerce capture:

- `WP_BASE_URL`
- `WP_USERNAME`
- `WP_APPLICATION_PASSWORD`

`WC_CONSUMER_KEY` and `WC_CONSUMER_SECRET` are optional when WooCommerce accepts the
WordPress Application Password for `wc/v3` reads; ask for them only if WooCommerce routes
return 401/403 with the WordPress Application Password.

`WP_MEDIA_URL_REWRITE_FROM` and `WP_MEDIA_URL_REWRITE_TO` are optional. Use them when the
WordPress API is reached through a public tunnel but media/file URLs inside records still
point at `localhost` or another private origin. If they are blank, generated readers may
rewrite localhost/private origins to `WP_BASE_URL` when `WP_BASE_URL` is public.

`config/source.wordpress.env` is a secret-bearing file once it may contain real values.
Do not read it with whole-file commands that print its contents into tool output. Check
only whether the file exists and whether each required key is present/blank/missing; when
describing status, name keys only and never echo values.

1. Run the deterministic capture script **from this skill's directory** (the folder
   containing this `SKILL.md`; see `CONVENTIONS.md`):

   ```
   node scripts/wp-discovery.js --base-url <url> --out-dir <migrations-root>/<project>/data/wp-discovery
   ```

   It walks the REST index, runs one `OPTIONS` + a small `GET` sample per entity, and
   writes per-entity markdown (routes, schemas, sample records, record counts,
   relationships). Pass auth options (see Read contract → Auth) for a complete capture.
   For long runs, pass `--progress-log <path>` and poll it per
   `CONVENTIONS.md#progress-log-polling`.

2. If the user chose `public content only`, run unauthenticated and keep the scope framed
   as public-only. Do not pivot into asking for credentials unless the user changes scope.

3. **Credentials are required for a complete capture.** Without auth, only published
   public content is reachable; drafts, WooCommerce (`wc/v3`), user PII, and private
   fields return 401/403, making their `recordCount`/`inUse` unreliable. The script flags
   this in its README under "Incomplete Capture (Authentication)" — do not treat an
   unauthenticated run as authoritative.

   **Author identity is a specific casualty of public-only capture.** Public
   `/wp/v2/users` lists only users who authored public posts and exposes **no `roles`
   and no emails** — admin vs editor vs contributor vs subscriber is indistinguishable.
   Roles/capabilities require `context=edit` (App Password with `list_users`) or a DB
   dump (`wp_usermeta` → `wp_capabilities`); WXR exports carry author login/email/display
   name but no roles either. Vanilla WP has no guest-byline concept (every byline is a
   user); plugin guest authors (Co-Authors Plus `guest-author` entities) are separate
   non-user records. Record author capture mode in the source profile — it determines the
   blog `authorAttributionMode` downstream (public-only → fallback/owner attribution;
   authenticated → per-author mapping: administrators → owner user-member,
   content roles → per-author members; subscribers/customers → **Wix contacts by
   default** — a contact is the no-login CRM person entity and Contacts V5 upserts in
   bulk — promote to full site members (MEM-01: 1/sec serial, re-registration) only when
   member-gated features are in scope. Every Wix member auto-creates its contact, so
   member-first ordering: never pre-create a contact for someone who will become a
   member. Note contacts cannot author blog posts or comments — bylines need members).

4. Distinguish **supported** entities (advertised by the REST index) from **used**
   entities (those with `recordCount > 0`). Entities advertised but empty should be
   flagged, not mapped as if they hold data.

5. **Backend data scope.** The capture script classifies REST routes before per-route
   `OPTIONS` or sample `GET` requests. By default it samples backend data routes and skips
   frontend, theme/editor, runtime session, admin dashboard, diagnostics, marketplace
   setup, and integration/helper APIs. It writes the skipped-route audit trail to
   `skipped-routes.json`.

   For WooCommerce, prefer canonical durable data APIs such as `wc/v3` over duplicated
   `wc/v2`/`wc/v1` routes for authenticated/private commerce reads. For `public content only`
   discovery, probe public Store API catalog routes before declaring commerce deferred:
   `wc/store/v1/products` and `wc/store/v1/products/categories` are in-scope public
   commerce data when present. Cart and checkout Store API routes remain out of scope as
   runtime session state.

6. Map known plugins to entity types where relevant (e.g. WooCommerce → store,
   Seriously Simple Podcasting `ssp/v1` → podcasts, Yoast → SEO, ACF → custom fields).

7. **Localhost sources and media URLs.** A source at `localhost`, `127.0.0.1`, or another
   private-only host is valid for discovery and source reads from the user's machine.
   However, Wix Media import fetches files from the URL using Wix servers, so media URLs
   like `http://localhost:8090/wp-content/uploads/...` are not reachable by Wix during a
   live import. This is optional setup and, as far as we know today, only affects media
   import:
   - Prefer exposing the local source through a temporary public HTTPS tunnel such as
     ngrok before live media import.
   - Or explicitly skip/defer media import and continue with non-media entities.
   - If using ngrok on macOS:
     1. Install: `brew install ngrok`
     2. Add an authtoken from the ngrok dashboard:
        `ngrok config add-authtoken "<YOUR_AUTHTOKEN>"`
     3. Expose the local source port, for example: `ngrok http 8090`
     4. Set the source base URL to the HTTPS forwarding URL:
        `export WP_BASE_URL=https://<id>.ngrok-free.app`
   Record this in `source-profile.md` when the captured source URL is localhost, and note
   whether media will use the tunnel or be skipped/deferred.

The raw capture is evidence, not a hand-off artifact. `rp-discovery` synthesizes it into
the canonical artifacts and records traceability pointers (`rawDiscovery`, per-entity
`rawFile`).

## Read contract (codegen-time)

What a generated WordPress reader must get right. Capture the operational facts below into
`source-profile.md` during discovery so codegen has them without re-deriving.

The generated reader is an **extractor**, not an in-memory bulk loader. It should fetch
WordPress/WooCommerce records page by page and write them to project-local files (for
example per-entity paged JSON files plus a manifest) so the import step can read from
disk later without re-fetching the source.

**Reuse the shared transport — do not regenerate it.** The auth, URL building, rate-limit
throttling, and `Retry-After`-aware 429/503 backoff a reader needs already exist as a
dependency-free module at `lib/wp-http.js` in **this skill directory** (the same module
the capture script imports). It
exports `fetchJson`, `buildHeaders`, `configureRateLimit`, and `parseTotalHeader`. Any
generated WordPress reader **must reuse this module rather than reimplementing transport**,
so the reader contains only per-project orchestration: which entities to pull, the
pagination loop, `_embed`/`_links` resolution, and transform glue. One tested transport
core is what makes the sampler and the reader behave identically. *How* the module is
carried into a runnable migration project is `rp-import-codegen`'s concern (its File
targets), not this adapter's. The notes below describe what the reader does *on top of*
that shared core:

- **Namespaces & auth differ per namespace:**
  - `wp/v2` (core): HTTP Basic auth with a WordPress **Application Password**
    (`--username` + `--application-password`).
  - `wc/v3` (WooCommerce): **consumer key / secret**, sent as Basic auth over HTTPS (or
    as query params on some hosts). This is a different credential from the Application
    Password — both may be needed for a full migration.
- **Pagination:** `?page=N&per_page=M` (max `per_page` is typically 100). Total pages are
  in the `X-WP-TotalPages` response header and total records in `X-WP-Total` — read
  those rather than guessing when to stop. Some public WooCommerce Store API collection
  routes paginate without `X-WP-TotalPages`; when that header is absent, generated readers
  must stop on the first page whose item count is smaller than `per_page`.
- **Embedded relations:** request `?_embed` to inline related resources, or follow the
  `_links` block (`author`, `wp:featuredmedia`, `wp:term`) to resolve relations. The
  `evidence` pointers in `source-schema.json` relations come from this `_links` block.
- **Hierarchical taxonomies:** WordPress categories (and custom hierarchical taxonomies)
  carry a `parent` field on each term (`0` = top-level). When **any** term has a non-zero
  `parent`, the source taxonomy is nested. Discovery must elevate this into structured
  schema — set `"hierarchical": true` on that entity in `source-schema.json` (see
  `source-schema.example.json` → `category`) rather than leaving `parent` buried in the raw
  dump. The Wix Blog category target is flat (no parent/child), so this flag is what triggers the
  mapper's mandatory lossiness entry; without it, the flatten happens silently.
- **Rate limits / retries:** not advertised; the capture script throttles
  (`--rate-limit-rpm`, default 120) and backs off on 429/503 honoring `Retry-After`.
  Generated readers should inherit the same discipline.
- **Rich content:** `content.rendered` / `title.rendered` are HTML; `*.raw` requires
  `context=edit` (authenticated). Note which the reader should pull.
- **HTML entities in "plain text" fields (trap, hit live 2026-07-19):** WordPress returns
  HTML-encoded entities (`&#8211;`, `&amp;`, `&#8217;`, …) not only in rendered HTML but
  also in fields consumed as plain text — product/category/tag `name`, `title.rendered`
  after tag-stripping, excerpts, even SKUs. Values sent to plain-text Wix fields (product
  name, blog title, tag label, CMS text columns) must be entity-decoded by the generated
  transforms or the encoded form appears verbatim in the Wix dashboard. HTML passed to
  rich-content conversion does NOT need pre-decoding — the converter handles entities.
- **Custom fields:** ACF / meta often appear in sample records but are absent from the
  `OPTIONS` schema — surface them as `unknowns` in discovery so the mapper can decide.
- **WooCommerce variation attribute values are URL-encoded slugs, not display names (trap, hit live 2026-07-21):**
  `variations[].attributes[].value` from `wc/v3/products/{id}/variations` is a URL-encoded
  taxonomy-term slug (e.g. `1-%d7%a7%d7%92` for `1 ק"ג`), not the human-readable display
  name that the product's `attributes[].options[]` array contains. Generated readers must
  build a per-option slug→displayName map from the parent product's `attributes[].options[]`
  (which does carry display names) and resolve slug values to display names before writing to
  Wix. Passing slugs directly causes `MISSING_VARIANT_OPTION_CHOICE` because Wix compares
  variant choice names against option choice names and finds no match.
- **WooCommerce "Any"-style variations have null attribute values (trap, hit live 2026-07-21):**
  When a WooCommerce variation is set to "Any" for an option (the variation applies to any
  value of that option), `variations[].attributes[].value` is an empty string or null, not a
  specific term. Generated transforms that filter variants by attribute value will silently
  drop "Any" variants, leaving options with no variants — Wix then auto-generates priceless
  placeholder variants, causing price-empty errors. Fix: when ALL attributes for a variation
  are null/"Any", treat the variation as a single cartesian entry covering all option choices
  at the variation's price, rather than filtering it out.
- **Route paths passed to `fetchJson` / `buildApiUrl` must NOT include `/wp-json` (trap, hit live 2026-07-21):**
  `buildApiUrl` in `lib/wp-http.js` already prepends `/wp-json` to the `routePath` argument.
  Generated route paths must start with the namespace directly (e.g. `/wc/store/v1/products`,
  `/wp/v2/posts`) — never with `/wp-json/...`. Prefixing with `/wp-json` doubles the prefix
  and produces 404s.

## Schema shape

`source-schema.example.json` (in this skill folder) is the template `rp-discovery` follows
when emitting `migrations/<project>/source-schema.json`. It is a shape to follow, not a
strict schema to validate against. Keep the platform-agnostic core stable; push WordPress
quirks (`restNamespace`, statuses, etc.) into each entity's open `sourceMeta` blob.

<!-- chapter:end slug=rp-source-wordpress -->

---

<!-- chapter:begin slug=rp-target-wix position=17 -->

## 17. rp-target-wix

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-target-wix/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-target-wix.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (44), referenced from this skill's directory:
  - `domains/blog/domain.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json
  - `domains/blog/entities/category.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json
  - `domains/blog/entities/comment.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json
  - `domains/blog/entities/post.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json
  - `domains/blog/entities/tag.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json
  - `domains/cms/domain.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json
  - `domains/cms/entities/collection.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json
  - `domains/cms/entities/data-item.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json
  - `domains/cms/entities/reference.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json
  - `domains/crm/domain.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json
  - `domains/crm/entities/contact.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json
  - `domains/crm/entities/email-subscription.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json
  - `domains/crm/entities/label.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json
  - `domains/ecom/domain.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json
  - `domains/ecom/entities/order-line-item.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json
  - `domains/ecom/entities/order-refund.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json
  - `domains/ecom/entities/order.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json
  - `domains/index.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/index.json
  - `domains/media/domain.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json
  - `domains/media/entities/media-file.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json
  - `domains/media/entities/media-folder.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json
  - `domains/members/domain.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json
  - `domains/members/entities/badge.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json
  - `domains/members/entities/member.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json
  - …and 20 more, listed in https://skillsdocs.com/api/v1/books/wix/skills/skills/rp-target-wix

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-target-wix
description: >-
  Wix target adapter with verified write primitives, shared execution runtimes, and contract tests.
  Use when vendoring Wix writers, validating API shapes, or Wix provisioning mechanics.
---

# rp-target-wix

Wix **target adapter**. Owns the Wix-side write surface and shared execution runtime that every migration shares —
verified once here so the platform-agnostic stages and per-project codegen never
re-derive (and re-break) it. This is the symmetric counterpart to `rp-source-wordpress`:
that adapter owns *reading* a source platform; this one owns *writing* to Wix.

## When this skill is used

Not a stage in the flow — a reference + shared library consulted by:

- **`rp-import-codegen`** vendors `lib/wix-writers.js` into the project (like the
  `wp-http` transport) plus the shared write/setup runtime modules, and generates thin
  project-specific write specs/transforms that **call these primitives/runtime APIs**.
  Codegen does not re-emit Wix API plumbing.
- **`rp-setup-discovery` / `rp-execute-setup`** consult the "Verified endpoints" and
  "Provisioning" notes for app-install / Wix-Data / collection mechanics.

Because the Wix surface is identical across source platforms, adding a new source
(`rp-source-shopify`, …) requires **no change here**.

## Shared runtime boundary

This adapter owns the **shared Wix layer** written once for all migrations.

It should own:

- verified request builders and endpoint primitives
- shared Wix client/auth handling
- shared retry and throttling policy
- generic write execution runtime
- generic setup execution/runtime behavior
- checkpoint helpers
- local crosswalk helpers (`state/crosswalk/crosswalk.ndjson` authority, append-only
  attempt journal, optional CMS mirror seed/upload adapters)
- URL preservation helpers (`state/url-preservation/` base paths, ledger, planned
  redirects, and unresolved route rows)
- NDJSON record-stream I/O (`lib/ndjson.js`): streaming read/write, batching by count and by
  cost, cursoring, and append-only producers. Migration data files are record streams, so the
  scan/batch/cursor primitives live here once instead of being re-derived by codegen.
- audit log emission
- compact execution report generation
- contract tests for stable request/runtime behavior

Per-migration code generation should own only:

- field maps
- transforms
- write specs
- setup-plan renderings
- entity ordering
- dependency wiring

## Write contract (the verified Wix surface)

`lib/wix-writers.js` exposes pure request builders (`build*Request`) + executors. Shapes
marked **VERIFIED** were validated by a real call against a live site, not just read from
docs. Shapes marked **UNVERIFIED** are docs-schema/MCP-derived bootstrap primitives that
must be surfaced in execution plans until a live contract call promotes them. It also
exports `sendDirectRest` and `notifyMissingWriter` for generated native REST paths when
Wix has a native entity but this adapter does not yet ship a dedicated primitive.

This resource should also expose shared execution-runtime modules above those primitives,
so generated migrations use one common path for retries, throttling, checkpointing,
reporting, and audit logging.

**Read/return contract for `query*` executors.** Every `query*` executor
(`queryStoresProducts`, `queryStoresCategories`, `queryContacts`, `queryCoupons`,
`queryOrders`) **unwraps the response to the entity array and returns one page**, discarding
`pagingMetadata`. Two failure modes follow, both of which have shipped:

- **The return value IS the array.** Reading `.products` / `.categories` off it a second time
  gives `undefined` → `[]`. A dedupe index or existing-entity safety net then comes back
  empty *without erroring*, which is indistinguishable from a fresh site — so the import
  duplicates what is already there, and a setup verification reports 0 categories on a site
  with 25.
- **These executors cannot be cursor-paged**, because the cursor is in the metadata they
  discarded. Use a `queryAll*` primitive where one exists — `queryAllStoresCategories`,
  `queryAllStoresProducts`, `queryAllDataItems` — otherwise send
  `wix.send(build<X>Request(body))` and read `pagingMetadata.cursors.next` off the raw
  response.

Every sweep used for dedupe must **throw on a partial result rather than return it**. "Empty
net" and "empty store" look identical to the caller, and the caller assumes the second.

## Domain entity suitability knowledge

Target entity suitability lives in `domains/`, not in a large prose table in this skill.
Use the deterministic reader as the normal access path:

```bash
node scripts/domain-knowledge.js list-domains
node scripts/domain-knowledge.js list-entities --domain stores
node scripts/domain-knowledge.js read-entity --ref stores/product
node scripts/domain-knowledge.js resolve-source --source-system woocommerce --source-entity product
node scripts/domain-knowledge.js resolve-source --route /wc/v3/products
node scripts/domain-knowledge.js list-flagged --flag IMPORT_UNRELIABLE
node scripts/domain-knowledge.js summarize-entities --refs stores/product,ecom/order
```

`domains/index.json` is generated from domain/entity files and checked in. After editing
domain knowledge, run:

```bash
node scripts/domain-knowledge-validate.js --write-index
```

`writerId` values in entity files must match exported functions from `lib/wix-writers.js`;
use `null` for direct REST plans, setup/manual work, or unsupported native gaps.

Entity files may also expose `fieldContracts[]` for verified nested write paths. These
are machine-readable contracts for codegen and dry-run validators, not replacements for
the human notes below. For Stores products, `domains/stores/entities/product.json`
records the verified Catalog V3 create contract for `product.subscriptionDetails`,
including required nested paths, allowed recurrence values, read-back behavior, and the
live-validated `subscriptions[].description <= 60` constraint. Generated transforms
should consume that metadata through the vendored `wix-writers.js` exports instead of
copying limits into project-local code.

Live verification helpers must not edit `domains/` directly. When a probe changes a
target write assumption, emit a proposal artifact and let the orchestrator promote it
deliberately:

```bash
node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
  --artifact migrations/<project>/setup/stores-subscription-verification.json \
  --proposal-artifact migrations/<project>/setup/contract-ledger-proposal.json
```

Use `lib/contract-ledger.js` to validate the handoff: a passed verification without a
proposal is incomplete, and a proposal that is neither promoted into `fieldContracts[]`
nor explicitly deferred is stale product knowledge. Codegen should consume
`fieldContracts[]` through `scripts/domain-knowledge.js summarize-entities`, which
includes promoted contracts.

| Capability | Endpoint | Notes / traps |
| --- | --- | --- |
| HTML → rich content | `POST /ricos/v1/ricos-document/convert/to-ricos` | VERIFIED. `options.plugins` enum is **UPPERCASE** — the docs example shows lowercase and 400s. |
| Import media from URL | `POST /site-media/v1/files/import` | VERIFIED. **Async**: response is `PENDING`; poll `GET /site-media/v1/files/{id}` for `READY` before referencing. |
| Blog category | `POST /blog/v3/categories` | VERIFIED. body `{ category: { label, slug, description } }`. |
| Blog tag | `POST /blog/v3/tags` | VERIFIED. Body is **top-level `{ label, language }`** — NOT `{ tag: { label, slug } }`; slug is derived. |
| Blog post | `POST /blog/v3/draft-posts` → `…/{id}/publish` | VERIFIED. `memberId` **required** (3rd-party). VERIFIED (2026-08-02): the **site owner's auto-created user-member** satisfies it — it was already present on our API-provisioned test site with zero Members-area interaction (single-site observation), so attribute-to-owner needs no member provisioning. **Resolve it via `listMembers` + `loginEmail` match — never derive it from the account/user GUID** (the observed id equality is n=1 on a solo account, undocumented). Also VERIFIED (2026-08-02): author is **re-assignable after publish** — `PATCH /blog/v3/draft-posts/{id}` `{ draftPost: { memberId } }` then republish updates the published post's author (post id == draft id; republish fires non-suppressed events — do author-upgrade passes inside the notification-mute window). Visible custom cover media requires **both** `heroImage.id` and `media: { displayed:true, custom:true, wixMedia:{ image:{ id }}}`. `heroImage.id` alone leaves the cover hidden. |
| Blog posts (bulk) | `POST /blog/v3/bulk/draft-posts/create` | UNVERIFIED bootstrap (public docs + wix/skills `wix-manage` recipe, 2026-07-21). Max **20 posts/call**; `bulkCreateDraftPosts` chunks larger inputs. Prefer over per-post creates for N ≥ 2 (single create runs ~25–30s/call per the recipe). Publish is still per-post — a bulk `publish` flag is unverified. Surface in the execution plan until the live contract call promotes it. |
| CMS item | `POST /wix-data/v2/items` | VERIFIED. `{ dataCollectionId, dataItem: { data } }`. Requires Wix Data enabled (else `WDE0110`). |
| Members | `GET`/`POST /members/v1/members` | VERIFIED. dedup by `loginEmail` (gated PII; use a fallback member when absent). VERIFIED (2026-08-02, single-site observation): the target-site member list can already contain **auto-created user-members** for the owner/contributing Wix users (`status: APPROVED`; resolve by `loginEmail`, never by deriving from the account GUID — the observed id equality is undocumented) — never dedupe or count these against source-site members; `DELETE /members/v1/members/{id}` works for cleaning up importer-created members. Throughput: **no bulk create; documented ≥1s spacing** (10k ≈ 3h serial floor — plan + resume via crosswalk). Create sends **no email** and does **not** fire the signup automations trigger (source-confirmed) — silent by default. **Activation policy (decided 2026-08-03): passwords are never imported, and passwordless members complete the standard forgot-password flow (confirmed).** Delivery = post-import **label-wave automation**: owner creates one dashboard automation (trigger `label added`, e.g. `migrated-2026` → owner-branded email: "Log in → Forgot password"); importer applies the label via API in batches for rate control/re-targeting; enable only after the import window (AUTO-02 pause). **Never mass-send** `send-set-password-email` (3h single-use link), and never fetch/store per-member reset links — they are credential-equivalent secrets and the no-link flow needs none (details: research/import-api-suitability/orders-contacts-members.md §3 addendum). |
| Stores product (BULK — the scale path) | `POST /stores/v3/bulk/products-with-inventory/create` | UNVERIFIED bootstrap. **Use this, not per-product creates, for any catalog above a handful** — 1000 products is 11 calls instead of 1000. Creates up to 100 products with inline options, inline brand/ribbon/infoSections, all variants AND per-variant inventory items in one request. **Five caps apply simultaneously and exceeding ANY ONE rejects the whole request:** products ≤100, `variantsInfo.variants` ≤1000 total, `options` ≤100 total, `modifiers` ≤100 total, `infoSections` ≤100 total. With 2 options per product the options cap binds at **50** products, not 100 — so batch with `lib/ndjson.js` → `readBatchesByLimits` using the exported `BULK_PRODUCT_LIMITS` + `storesProductBulkCost`, never on record count alone. **TRAP: bulk is NOT atomic** — a `200` can carry per-item failures in `results[].itemMetadata.success`; never infer success from the HTTP status. **TRAP: correlate results by `itemMetadata.originalIndex`**, not response position. **TRAP: `bulkActionMetadata.undetailedFailures`** counts failures whose detail the server dropped; ignoring it silently loses records. `returnEntity:false` (default) still returns `itemMetadata.id`, which is all a crosswalk needs — pass `true` only for a contract probe that must inspect the created entity. `bulkCreateStoresProductsWithInventory` handles normalization, correlation and the unaccounted-item check, returning `{ results, succeeded, failed, undetailedFailures, unaccounted }`. Normalization is purely local — HTML descriptions travel as `plainDescription` for Wix to convert server-side — so a batch is **one** HTTP call. It used to convert one description per product first, which made a 100-product batch 101 calls and reliably tripped the Ricos endpoint's 403 throttle. |
| Stores product | `POST /stores/v3/products`, `POST /stores/v3/products/query`, `PATCH /stores/v3/products/{id}`, `GET /stores/v3/products/{id}`, `GET /stores/v3/products/slug/{slug}` | VERIFIED (2026-07-05; media patch verified in migration-20260715-01). `buildCreateStoresProductRequest` runs `normalizeStoresProductV3`, which bakes in the create traps: PHYSICAL requires `physicalProperties:{}`; simple/V1-ish top-level `price`, `sku`, and physical properties are moved into a single V3 variant; option/variant choice names clamped to 50 (identical truncation both places); variant `optionChoiceNames.renderType` defaulted to `TEXT_CHOICES`; `compareAtPrice` dropped unless strictly > `actualPrice`; variant `price` must be a **money object** (`{ actualPrice: { amount: "14.95" } }`) — a bare string/number 400s "Expected an object" (verified 2026-07-19), so the normalizer coerces scalar prices. **Descriptions:** a string `description` is HTML and is moved to **`plainDescription`** by `normalizeStoresProductV3`; Wix converts that to rich content server-side, so the product path never calls the Ricos endpoint (`description` proper is a Ricos document *object* — HTML there is a type error). **TRAP: `plainDescription` is silently ignored when `description` is also set** — a 200 with an empty description — so the normalizer throws when both are present. **TRAP: hard cap of 16,000 characters** (the old Ricos path chunked at 28k and merged, so it was effectively unbounded); over-long bodies throw rather than truncate, and need an info section or an explicit `mapping-gaps.json` entry. For product media, prefer `media.itemsInfo.items[]` with external `url` values when the source URLs are publicly reachable: the Stores product API ingests them in the background, avoiding the slower, heavily-throttled Media Manager pre-import path. Use pre-imported Wix media ids only when the target flow requires them. **Upsert lookups:** `getStoresProduct(wix, id)` and `getStoresProductBySlug(wix, slug)` return the product or throw 404 — callers should catch and treat as `null` for "not found". **Catalog sweeps:** `queryStoresProducts` returns **one page** unwrapped to the array with the cursor discarded, so a crosswalk-recovery or name-match safety net must use **`queryAllStoresProducts(wix)`**, which follows the cursor and throws rather than returning a partial index. **Slugs:** sanitizing is owned by `lib/wix-build.js` (`toWixSlug`, applied automatically via the `coerce: 'slug'` rule on `product.slug`), not by this module and deliberately not by `normalizeStoresProductV3` — the writer passes the caller's slug through unchanged so URL preservation can record the original next to the derived `plannedTargetSlug`. It matters because Wix rejects anything outside `[a-z0-9-]` and slug validation precedes application, so one bad slug (Shopify mints `ph-5_5` from a "pH 5.5" title) 400s the entire bulk batch rather than just that product. **General patch:** `patchStoresProduct(wix, { productId, revision, ...fields })` applies `normalizeStoresProductV3`, so a string `description` moves to `plainDescription` here too; `revision` from the existing product is required. UNVERIFIED for get-by-id, get-by-slug, and general-field patch (endpoint VERIFIED; arbitrary-field patching and slug lookup not yet live-tested). **Variable products / variant matrices:** Wix Stores V3 requires server-assigned `optionChoiceId` GUIDs before a variant matrix can be built — those IDs do not exist until after the initial `createStoresProduct` call returns. A single-pass import therefore cannot create a full variant matrix in one call. The two-pass approach: (1) create the product with options declared (but no variants), (2) create each option choice via `POST /stores/v3/products/{id}/options/{optionId}/choices` to obtain server-assigned choice IDs, (3) create variants via `POST /stores/v3/products/{id}/variants`. When the two-pass flow is out of scope (e.g. public-data single-pass run), import as a single-variant product at the base price, record the fidelity loss in `mapping-gaps.json`, surface it in the execution-plan report, and ensure the generated product detail page renders `productOptions` when non-empty so the storefront is ready once options are added. |
| Stores category | `POST /categories/v1/categories`, `POST /categories/v1/categories/query` | VERIFIED (2026-07-05) for create. Body `{ category, treeReference }` — `treeReference` is **top-level** (sibling of `category`, `{appNamespace:"@wix/stores"}`); nesting it 400s "treeReference must not be empty". Empty optional strings such as `description: ""` are omitted. **Query:** same top-level `treeReference` trap applies. `queryStoresCategories(wix)` returns **one page (100 max)** unwrapped to the array — it does **not** return all categories, and the unwrap discards the cursor so it cannot be paged. Any dedupe/upsert index must use **`queryAllStoresCategories(wix)`**, which follows `pagingMetadata.cursors.next` and throws rather than returning a partial index. Observed 2026-07-29: a site with 119 categories read back as 100 through the single-page call, which would have duplicated the missing 19. UNVERIFIED for query (not yet live-tested). |
| Wix Tag (global) | `POST /tags/v1/tags` | UNVERIFIED bootstrap. Body `{ tag: { name, fqdn } }`. FQDN for Stores V3 products: `"wix.stores.catalog.v3.product"`. Max 100 tags per FQDN. Create tags before products; crosswalk tag IDs into product `tags.publicTags.tagIds[]` at create time. Tags are a native Wix entity — do not route product tags to CMS. |
| Stores inventory item | `POST /stores/v3/inventory-items` | UNVERIFIED bootstrap. `{ inventoryItem: { variantId, productId, inStock: true } }` marks a variant as in-stock at the default location (omit `locationId`). Inventory items are **not** auto-created on product create — call this for each variant after `createStoresProduct`. For quantity-tracked stock use `{ quantity, trackQuantity: true }` instead of `inStock`. `setProductVariantsInStock(wix, { productId, variantIds })` is a convenience wrapper that calls `createInventoryItem` for each variant. Requires permission scope `SCOPE.STORES.INVENTORY_ITEM_WRITE`. |
| Stores category membership | `POST /categories/v1/bulk/categories/add-item` | VERIFIED (2026-07-05). `{ item:{ catalogItemId, appId }, categoryIds[], treeReference }`; `appId` = Wix Stores `215238eb-…`, `catalogItemId` = Wix product id. |
| Contacts | `POST /contacts/v5/contacts`, `POST /contacts/v5/bulk/contacts/upsert`, `POST /contacts/v5/contacts/query`, `GET /contacts/v5/contacts/{id}`, `PATCH /contacts/v5/contacts/{id}` | **GA** (docs verified 2026-08-04 — was Developer Preview when selected on 2026-07-26). Do not fall back to V4 unless a migration explicitly chooses a legacy compatibility path. **The GA contact shape is FLAT** — no `info` wrapper, no `emails.items`/`phones.items` list wrappers: one main `email`/`phone` (contact matching + subscription status live on these) plus `additionalEmails`/`additionalPhones` arrays; `addresses[]` keeps postal fields **nested under `address`**; `company` is `{ name, jobTitle }`. Create and update both take `{ contact, allowDuplicates }`; update requires the current `revision` and has **no fieldMask**. Phone tags: `OTHER`/`MAIN`/`HOME`/`MOBILE`/`WORK`/`FAX` (no `UNTAGGED`). `buildCreateContactRequest`/`buildUpdateContactRequest` accept the flat GA `contact` (legacy `info` payloads convert strictly — non-mechanical keys like `extendedFields`/`labelKeys` throw). Live create/query/update verification is still pending a token with Contacts permissions (2026-07-26 probe got `403`) — keep V5 writers surfaced as UNVERIFIED in execution reports until a live contract test promotes them. **Custom fields (GA):** the V5 contact carries `extendedFields.namespaces.<ns>`; per the V5 contact-object docs, definitions go through the **Data Extension Schema API with FQDN `wix.contacts.*.contact`** (user-defined values under `_user_fields`). The V4 Contacts Extended Fields API (`POST /contacts/v4/extended-fields`, values under `info.extendedFields`) pairs with the V4 surface only — do not mix. CAVEAT: the Data Extension Schema intro's supported-objects table does not list contacts yet (docs inconsistency at GA cutover); verify the DES path live during setup before relying on it. **Labels → tags:** V5 exposes `tags.privateTags.tagIds[]` managed via the Tags API (same FQDN); V4 label APIs and the `labels_added` automation trigger are a V4-surface concept — resolve the tag/label story during setup before planning post-import label waves through V5 writers. |
| Coupons | `POST /stores/v2/coupons`, `POST /stores/v2/coupons/query` | UNVERIFIED bootstrap. Prefer native Wix coupons because Wix has a native coupon entity; do not special-case coupons into CMS merely for caution or because scoping must be mapped. Fallback is only for source semantics with no native representation. |
| eCom order | `POST /ecom/v1/orders`, `POST /ecom/v1/orders/query` | UNVERIFIED docs-schema bootstrap. Treat as blocked unless setup verification proves historical order creation is side-effect-free. |
| Site notifications mute | `POST /notification-preferences/v1/site-mute/mute`, `POST /notification-preferences/v1/site-mute/unmute`, `GET /notification-preferences/v1/site-mute` | VERIFIED (2026-08-04, full cycle live on a test target: mute → state read → idempotent re-mute → unmute → state restored, all 200). Spec 0012. Mutes **all** notifications of the site in context — all recipients, all channels; sendability denied regardless of recipient-level preferences. All three calls return `{ siteMuteState: { muted, reason?, mutedBy: { wixUserId } } }`; executors unwrap to `siteMuteState`. Mute body `{ reason?: string }` (≤500, clamped by the builder) — always pass a project-identifying reason (`RePlatform migration — <project>`) so the mute is auditable. **TRAP: re-mute overwrites `reason`** (last caller wins) — the import preflight's idempotent re-call must pass the same reason as setup. **AUTH TRAP: user tokens only** — the CLI-minted `OauthNG` site token (`WIX_AUTH_TOKEN` from `config/wix.env`) works; an account API key gets a uniform empty-body 403 on all three endpoints. Hard invariant (spec 0012): when mute is in effect (always for `WIX_SITE_STRATEGY=new`; explicit opt-in for existing sites), a failed mute halts the run before any import write — no degraded mode. `unmuteSiteNotifications` is **never called by the flow itself** — explicit owner request only; after an on-request unmute, confirm `muted: false` via `getSiteMuteState`. |
| Direct native REST | any Wix REST path derived by codegen | UNVERIFIED generated path for native Wix entities missing a dedicated adapter writer. Must log, call `notifyMissingWriter`, and be shown in the execution plan. |

## Safe-mode contact replacement

`lib/wix-writers.js` exports the shared safe-mode runtime:

```js
createSafeModeConfig(env)
mockEmailForEntity(entityType, entityId)
sanitizeContactFieldsForSafeMode(value, options)
sanitizeWixRequestBody(body, options)
```

Generated imports must pass `safeModeOptions` to writer builders/executors when
`SAFE_MODE` is enabled. Request builders sanitize a copied request body before returning
it and throw `SafeModeBlockedError` before any send when suspicious non-replaced email
values remain.

Safe-mode replacement paths use deterministic request-body paths with object fields,
array wildcards, and Wix wrapper arrays, for example:

```text
contact.email.email
contact.additionalEmails[].email
contact.phone.phone
order.billingInfo.email
dataItem.data.submissions[].email
```

Replacement only touches primitive leaf values — a generic path that lands on an object
(such as the GA Contacts V5 `email` object) is skipped rather than clobbered, and any raw
email left inside a skipped object still trips the suspicious-value block.

Target domain entity files may define `safeModeContactFields[]` with `{ kind, targetPath,
source, notes }` entries. Mapper/codegen must merge those target hints with source-side
contact evidence and user mappings, then pass resolved request-body paths to the shared
writers.

### Stores subscription validation

`lib/wix-writers.js` exports the Stores subscription contract and deterministic helpers:

- `STORES_SUBSCRIPTION_CONTRACT`
- `normalizeStoresProductSubscriptions`
- `validateStoresProductSubscriptionDetails`

Generated import code should call the normalizer from product transforms and the
validator during dry-run and immediately before live create. Validation failures are
record-level preflight failures/deferred outcomes; do not send known-invalid
subscription payloads to Wix to discover field errors live.

Catalog V3 subscriptions are a native Stores target, not an automatic CMS fallback or
semantic loss. Product create with `subscriptionDetails.allowOneTimePurchases` and
`subscriptions[]` entries containing `title`, `description`, `frequency`, `interval`, and
`autoRenewal` was verified live on 2026-07-26 in the nopong migration; Wix returned
server-assigned subscription option ids. Mapping/codegen should emit native recurring product
creates when the source cadence is known. General subscription patching remains unverified.

### Stores live verification helpers

Use the shared Stores verification CLI for live target checks that would otherwise become
migration-local snippets:

```bash
node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
  --artifact migrations/<project>/setup/stores-subscription-verification.json \
  --proposal-artifact migrations/<project>/setup/contract-ledger-proposal.json
node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-count \
  --artifact migrations/<project>/execution/stores-product-count-verification.json
node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-by-source-marker \
  --marker-path customFields.sourceId --marker-value <source-id> \
  --artifact migrations/<project>/execution/stores-product-marker-verification.json
node skills/replatform/resources/rp-target-wix/scripts/verify-stores.js stores delete-probe \
  --product-id <probe-product-id> \
  --artifact migrations/<project>/setup/stores-probe-cleanup.json
```

The CLI reads `WIX_AUTH_TOKEN` and `WIX_SITE_ID` from the environment or
`config/wix.env` under the current project root, creates the shared Wix client, and sends
requests through `wix.send(...)`. It writes machine-readable artifacts with the target
site id, endpoint, method, verified nested paths, discovered constraints, probe id,
cleanup status, warnings, recovery instructions, and timestamp. Failed probe cleanup is
not hidden: the artifact must remain with an explicit warning and a `delete-probe`
recovery command.

## Routed URL preservation behavior

URL preservation is a mapping and execution-state concern. This adapter owns only the
Wix-side facts that generated code may rely on when deciding whether a final target URL is
known.

Current import-only phase rules:

- Do not configure Wix redirects or site routing.
- Do not guess a destination relative URL from a slug when Wix route behavior has not been
  verified for that entity and delivery mode.
- When the destination base path or final URL is unknown, write URL ledger rows with
  `urlStatus: "pending_target_route"` or `target_url_missing` and record unresolved rows
  for the future website-builder phase.
- When a Wix create/update response or a safe declared lookup returns a final slug or
  relative path, generated code may record it as `actualTargetSlug` or
  `actualTargetRelativeUrl` and write a redirect plan if it differs from the source URL.

Known routed entity status:

| Entity | URL behavior contract |
| --- | --- |
| Blog posts | Create/publish is verified for content writes. Final public route exposure after publish is not verified here; generated code must treat the target route as `pending_target_route` unless a safe lookup in the generated write spec proves the final relative URL. |
| Blog categories | Category create is verified. Final public category route exposure is not verified here; default to `pending_target_route`. |
| Stores products | Product create and slug-bearing product operations are verified, but arbitrary route/base-path configuration is not part of this phase. Generated code may record actual target slugs only when returned or safely looked up; target relative URLs remain `pending_target_route` unless the route pattern is explicitly verified in the project. |
| Stores categories/collections | Category create is verified. Final collection/category route exposure is not verified here; default to `pending_target_route`. |
| CMS items with dynamic pages | CMS item writes are verified. Dynamic page route patterns are site-builder configuration, not native item import output; default to `pending_target_route` unless website-builder artifacts define the route. |

## Shared execution runtime contract

In addition to endpoint primitives, this adapter should define the reusable runtime API
that generated migrations call.

Conceptual contracts:

```ts
executeWriteSpec({
  spec,
  items,
  client,
  dryRun,
  runContext,
}): Promise<WriteExecutionReport>

executeSetupPlan({
  plan,
  client,
  dryRun,
  runContext,
}): Promise<SetupExecutionReport>
```

The exact filenames are implementation details, but the shared runtime should be the
single place that owns generic Wix execution mechanics.

### `WriteSpec` contract

Generated migrations should pass a declarative `WriteSpec` into the shared runtime.

Each spec should define, as applicable:

- `entity`
- `mode`: `create | update | upsert | bulk_create | bulk_upsert`
- `create`
- `update`
- `lookup`
- `bulk`
- `batchSize`
- `concurrency`
- `retryPolicy`
- `throttlePolicy`
- `auditKeys`
- `crosswalkAuthority: "local"`
- `cmsMirror`
- `crosswalkStrategy`
- `reconciliationStrategy`
- `dependencyRefs`
- `verificationLevel`: `verified | unverified`

This resource should document how each field is interpreted by the runtime.

### `WriteExecutionReport` contract

The shared runtime should return a compact, token-aware report suitable for the agent.

It should include:

- run ID
- entity
- counts: input, attempted, created, updated, skipped, failed, retried, throttled
- grouped error buckets
- references to fuller on-disk logs

The runtime should prefer grouped summaries over raw per-record detail.

These per-entity or per-phase reports are intermediate runtime outputs. They are inputs to
the final completion artifact, not replacements for it.

### Completion artifact contract

The shared runtime should support deterministic emission of a final machine-readable
completion artifact for the whole run.

Preferred output:

- `execution/completion-report.json`

This artifact should be the authoritative source for:

- final run status
- final imported/updated/skipped/failed counts
- separate dry-run counts when `dryRun: true`: requests built, Wix calls skipped,
  would-create/update/delete, would require live lookup, and local validation failures
- grouped skipped/deferred outcomes
- grouped failure outcomes
- dashboard/editor/preview destinations
- artifact references for audit/error/execution logs

The shared runtime should also support deterministic rendering inputs for:

- `execution/review/completion-summary.md`

The rendered summary should be derived from `completion-report.json`, not independently
invented by the agent.

### Audit log contract

The shared runtime should emit append-only machine-readable audit events.

Preferred format: NDJSON.

Each event should capture, at minimum:

- timestamp
- run ID
- phase: `setup | import`
- entity
- operation
- source ID when applicable
- target ID when known
- endpoint or primitive used
- attempt number
- result status
- HTTP status or platform error code when applicable
- latency

The `run ID` is required because audit files are append-only across dry-runs, retries, and
recovery passes. Completion reports should be generated from runtime counters for the
current run and may use audit aggregation only when filtered by `run ID`. The shared
`lib/audit-summary.js` helper enforces this filter for fallback/verification summaries.

### Dry-run runtime contract

The shared Wix runtime exports `createDryRunConfig`, `normalizeDryRunValue`,
`createWixClient`, and `createWixSetupExecutor`.

Dry-run is enabled by `DRY_RUN=true` or by the generated entrypoint's `--dry-run` flag.
It is disabled by default. `true`, `1`, `yes`, and `on` enable it; `false`, `0`, `no`,
and `off` disable it.

When `createWixClient({ dryRun: true })` is used:

- `authToken` and `siteId` may be blank unless the generated project requires the site ID
  for a local artifact namespace;
- `send()` must not call `fetch`, SDK, MCP, or CLI transports;
- `send()` must capture the would-send request under
  `state/attempts/wix-request-captures.ndjson` when `projectDir` or `requestCapturePath`
  is supplied;
- captured headers must omit `Authorization` and redact secret-like header/body keys;
- captured bodies must be after `SAFE_MODE` contact-value sanitization;
- response payloads must preserve the live shape expected by writer helpers, for example
  `{ contact }`, `{ product }`, `{ dataItem }`, `{ file }`, or an expected collection
  field such as `{ contacts: [] }` for skipped queries.

Dry-run placeholder target IDs are not live authority. If downstream local execution
needs them, write them only to `state/crosswalk/dry-run-crosswalk.ndjson` or keep them
in memory. Never append simulated target IDs to `state/crosswalk/crosswalk.ndjson` and
never upload them to a CMS crosswalk mirror.

When `createWixSetupExecutor({ dryRun: true })` is used, each setup step must first be
reduced to a structured intent, then captured as `planned_dry_run` without invoking MCP,
CLI, SDK, or REST transports that access or mutate Wix account/site state.

### Artifact authority at run completion

The shared runtime contract should preserve explicit artifact authority:

1. `execution-log.md` for chronology and operator/debug context only
2. `completion-report.json` for final outcome
3. `completion-summary.md` for deterministic user-facing rendering

The runtime should not force downstream consumers to reconstruct final outcome by reading
raw audit logs or chronological execution logs.

### Retry and throttling policy

This adapter should define one shared retry/throttling contract rather than letting each
migration invent its own.

It should classify, at minimum:

- retryable transport/network failures
- retryable `429` responses
- retryable `5xx` responses
- non-retryable `4xx` validation failures unless explicitly listed otherwise

When the server provides backoff hints such as `Retry-After`, the shared runtime should
honor them.

### Bulk and fallback policy

The shared runtime should know:

- when a native bulk primitive exists and is preferred
- when to fall back from bulk to per-record writes
- when an upsert requires lookup + create/update orchestration
- when an unverified native path must be surfaced before execution rather than used
  silently

## Media policy by target

Do not treat all media the same. The target domain decides whether media must exist in Wix
first or can be ingested through the entity API itself.

- **Wix Stores products:** prefer external URLs on `media.itemsInfo.items[]` and let the
  product API ingest them in the background. This is the default path when the URLs are
  publicly reachable.
- **Wix Blog cover images and other surfaces that require a Wix media id at create/update
  time:** import media first, then pass the Wix media id into the entity payload.
- **Generic Media Manager import** is a fallback/shared primitive, not the default for
  every entity carrying media.

## Media import source URL reachability

The media primitive imports by URL: Wix servers fetch the provided `sourceUrl`. Public
HTTPS URLs are expected; `localhost`, `127.0.0.1`, Docker-only hosts, and other
private-only URLs are not reachable by Wix during a live import. This is optional source
preparation and, as far as we know today, affects media import only.

If the source system is local, the migration should either:

- expose the source through a public HTTPS tunnel, then pass/rewire media URLs to that
  public base URL; or
- skip/defer media import and clearly state which media-dependent references will be
  missing until media is imported.

Ngrok quick setup for macOS:

```bash
brew install ngrok
ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
ngrok http 8090
export WP_BASE_URL=https://<id>.ngrok-free.app
```

## Validate by real call — do not trust doc examples

Codegen-time MCP doc checks confirm an endpoint *exists*; they do **not** confirm the
request *shape works*. The live wporg-news import proved doc examples can be wrong
(lowercase Ricos plugins → 400) or incomplete (featured image field, tag body). The
rule for this adapter:

- Treat a shape as verified **only after a real call succeeds** — encode the working
  shape here with a `// VERIFIED:` (or `// VERIFIED-TRAP:`) note and a date.
- A `// UNVERIFIED:` primitive is allowed as a bootstrap point for generated code, but it
  is not a silent live-write permission. The execution plan must call it out, and setup
  verification must either promote it with a sandbox/live validation or route to fallback.
- Keep `tests/target-wix/contract-test.js` current: it issues one real call per primitive
  against a sandbox site and is the single place schema drift surfaces. Tests live in the
  repo's `tests/` tree rather than in this bundle, so the published product carries no test
  harnesses. Run from the repo root:

  ```bash
  bash tests/run-all.sh target-wix
  WIX_AUTH_TOKEN=... WIX_SITE_ID=... bash tests/run-all.sh target-wix
  ```

  Without credentials the shape checks still run and the live calls are skipped.
  `tests/target-wix/ndjson-contract-test.js` covers `lib/ndjson.js` and never needs
  credentials.

  For live verification runs, pass `--progress-log <path>` and poll it per
  `CONVENTIONS.md#progress-log-polling`.

  Run on a cadence and after any Wix API change. A failing contract test — not a stranger's
  broken import — is how we learn the surface moved.

## Stores catalog is Catalog V3 only — Catalog V1 is not supported

**A migration only ever writes to a `V3_CATALOG` site.** Catalog V1 is not a supported
destination: there is no V1 write primitive here, no V1 fallback, and none should be added
(a former V1 fallback was removed — it only masked real V3 errors as spurious `428`s). A V1
site is a **blocker the run halts on**, not a variant the adapter, the mapper, or codegen
accommodates. The V3 create traps are handled in `lib/wix-writers.js`
(`normalizeStoresProductV3` + the top-level `treeReference` builder), so callers never
re-hit them.

**A Stores install is not automatically V3 — verify, don't assume.** This section used to
claim every Wix Stores install is Catalog V3. That is **false**, and believing it cost a
live migration its destination site (2026-07-30): a site scaffolded from the `blank`
headless template, with Stores installed afterwards through the App Installation API, came
up **`V1_CATALOG`**. Catalog version is fixed at provisioning — there is no in-place V1 → V3
switch — so every V3 write on that site failed and the site had to be abandoned.

Two consequences, both mandatory:

- **Provision V3 at creation.** Scaffold the site with `--site-template commerce`, never
  `blank` (and never bare `--site-template`, which *means* `blank`) — see `replatform` →
  "Headless site creation".
- **Gate before the first Stores write** on the read-only Catalog Versioning API,
  `GET https://www.wixapis.com/stores/v3/provision/version` →
  `catalogVersion: V3_CATALOG | V1_CATALOG | STORES_NOT_INSTALLED`. On `V1_CATALOG`, stop
  and halt to needs-user; do not fall back to V1 endpoints, and do not silently create a
  replacement site.

## What stays in codegen (not here)

Per-project field maps and ordering (which source field → which `data` key, the
media/author/taxonomy ref maps, upsert-by-key) live in the generated transforms/write
specs. This adapter holds the invariant Wix request shapes, transport, and shared
execution mechanics. Collection names and schemas (`PodcastEpisodes`, …) are
project-specific and come from the mapping plan.

## Provisioning pointers (see rp-execute-setup)

- Apps (Blog, Members) install via the App Installation API; ground `appDefId` from the
  official "Apps Created by Wix" table.
- **Wix Data enablement** (`WDE0110`): install the **Wix Data app `appDefId
  e593b0bd-b783-45b8-97c2-873d42aacaf4`** via the App Installation API; afterward `POST
  /wix-data/v2/collections` creates NATIVE collections with no `WDE0110` (verified live).
  Fallback: a custom app with a data-collections extension (declares collections at
  install time, but can't express REFERENCE fields).

## Scope & coverage

Wix has many apps/entities (Stores, Bookings, Events, Restaurants, Pricing Plans, CRM,
…). This adapter does **not** pre-build all of them. Coverage is **demand-driven** and
grows through reviewed releases. A migration may still target a native Wix entity before a
dedicated primitive exists; in that case codegen emits a native REST path using
`sendDirectRest`, logs the missing primitive, and calls `notifyMissingWriter` so the
RePlatform team can add the writer later.

**Native target ladder when no dedicated writer exists:**

1. **Use the dedicated `rp-target-wix` primitive** when one exists.
2. **If Wix has a native entity but no dedicated primitive, generate a native REST call**
   from Wix MCP/docs-schema, mark it `UNVERIFIED`, log it, call `notifyMissingWriter`, and
   surface it in the execution plan before any write. This is not a silent live write.
3. **Use CMS only when there is no suitable native Wix entity, or when the native entity
   is explicitly rejected for fidelity/side-effect reasons.** CMS is not a fallback for a
   missing adapter writer.
4. **Halt** if neither a native path nor an acceptable CMS/custom target exists.

The invariant: **anything not backed by a verified primitive is surfaced to the user for
consent before execution** — never written silently.

## Setup runtime expectations

This adapter should also define the shared setup-side runtime used by setup execution.

It should cover reusable mechanics for:

- app installation and verification
- Wix Data enablement verification
- CMS collection and field provisioning when supported
- setup step ordering/checkpointing
- setup audit logging
- setup execution reporting

`rp-execute-setup` should consume setup artifacts through this runtime rather than
re-implementing setup mechanics in skill-local prose or one-off scripts.

## Contract-test expectations

Contract tests should validate not only request builders, but also the most important
shared runtime invariants.

Examples:

- verified request shapes still serialize correctly
- retry policy classifies common Wix failures correctly
- throttle/backoff handling honors server hints
- compact reports preserve stable top-level fields
- completion reports preserve stable top-level fields
- audit events preserve the agreed machine-readable shape

<!-- chapter:end slug=rp-target-wix -->

---

<!-- chapter:begin slug=rp-telemetry position=18 -->

## 18. rp-telemetry

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/resources/rp-telemetry/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-telemetry/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/rp-telemetry.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (4), referenced from this skill's directory:
  - `lib/bi-sink.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js
  - `lib/telemetry-recorder.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js
  - `scripts/meter-from-audit.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js
  - `scripts/rp-telemetry.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: rp-telemetry
description: >-
  Always-active telemetry companion for RePlatform migration runs. Records what happened
  during a run — halts, errors, fidelity losses, API gaps, skill coverage gaps, user
  decisions, pipeline defects — through a validated recorder script, plus the run rollup
  (stages, timings, volumes, verification). Loaded by the orchestrator at run start and
  kept active for the whole run.
---

# rp-telemetry

Capture per-run telemetry that tells us what to improve in the skills and the underlying
Wix tooling. This skill stays active alongside the migration skills for the **entire**
run; the migration skills themselves are unchanged and know nothing about telemetry.

## The one rule that governs everything you record

**Observation, not diagnosis.** Every field records what was observed — what happened,
what was expected, what actually occurred, where, and how often. Never record a root
cause, a fix, a recommendation, or a workaround-as-solution. There is deliberately no
field for them; the schema rejects unknown fields. Root cause is derived later, at review
time, by a reviewing agent with full context — not asserted by you in the moment.

Three discipline rules apply to every free-text field (`what_happened`, `expected`,
`actual`):

- **Types, not instances.** Refer to entity *types* and *classes*, never specific client
  data. `entity_type: "product"` — never a product's name, SKU, price, or body text.
- **Secret-safe.** No credentials, tokens, URLs, config values, or file contents — the
  recorder also runs a mechanical scrub as a last line of defense, but do not rely on it.
- **Short and structural.** A sentence or two, max 400 chars. Prefer stating
  expected-vs-actual over narrating. The coded fields carry the structure.
- **No remediation narration.** "The canonical lib was fixed and re-synced" is a fix
  story, not an observation — even when true (dev-mode backports). Record what was
  observed to work ("adding fieldsets=FULL returned the field; the lookup succeeded")
  and leave the repair to the execution log.

## The recorder

All telemetry is persisted through the bundled recorder — **never hand-write or edit
`run-telemetry.json` or `telemetry/` files.** Run it from this resource directory
(see `CONVENTIONS.md`), pointing at the active migration project:

```bash
node scripts/rp-telemetry.js <command> [...] --project <abs path to migrations/<project>>
```

The recorder owns everything mechanical: schema validation (it rejects invalid enums and
unknown fields — fix and retry, never guess around it), timestamps, per-class event
folding, the run/attempt/session resume model, the privacy scrub, and the well-formedness
gate at finalize. It prints one JSON result per call; `{"ok":false,...}` lists exactly
what to fix.

| Call | When |
|---|---|
| `start '<dims-json>'` | At run begin — before any other pipeline step. Resumes an unfinalized run automatically. |
| `dims '<dims-json>'` | Whenever a dimension becomes known mid-run (platform version and extensions after discovery, `site_id` after provisioning). |
| `stage start <stage>` / `stage end <stage> --outcome <outcome>` | At every stage boundary. Outcomes: `passed`, `halted`, `failed`, `skipped`. |
| `meter [--api-ms N --model-ms N --script-ms N --input-tokens N …]` | Measured latency/token counts for a stage. Call it whenever you have real numbers; see "Metering" below. |
| `wait start [--halt <subtype> --skill <s> [--what '<text>']]` / `wait end` | The moment the run halts for the user, and the moment it resumes. This is how user latency stays out of `active_ms` — never estimate elapsed time yourself. Pass `--halt` for a needs-user halt and the recorder emits the paired `halt_needs_user` event for you. |
| `record '<event-json>'` | The moment something observable happens (taxonomy below). |
| `finalize '<rollup-json>'` | When the run reaches a terminal state. |
| `rebuild [--attempt <n>] [--push]` | Only on request, to re-assemble a past run's signal document from its archived journal after a recorder fix — never during a run. `--push` re-emits the rebuilt run to the BI sink (backfill after an outage; idempotent — the reviewer dedupes at query time). |
| `status` | To orient after a resume. |

### Run lifecycle

1. **`start`** as the first telemetry act of the run, with whatever dimensions are already
   known:

   ```bash
   node scripts/rp-telemetry.js start '{"source_platform":"wordpress",
     "source_site_url":"https://client-site.example","source_acquisition":"public_storefront",
     "delivery_mode":"management","destination_strategy":"new_site",
     "runtime_env":{"agent_runtime":"claude-code","model":"<model id>"}}' --project <dir>
   ```

   `start` on a project with an unfinalized run **resumes** it (same run, same attempt,
   one more session) and closes any open wait interval. `start` after a finalized run
   opens the next attempt. Never try to manage run identity yourself.

   `source_acquisition` is an open set of class tokens, but reuse an established one
   (`admin_api`, `public_storefront`, `public_content`, `file_export`) rather than
   minting a synonym — cross-run folding depends on stable tokens.

   **Skills provenance is auto-stamped — you don't pass it.** At `start` the recorder
   stamps two identifiers onto the run so every record can be traced back to the skills
   that produced it:

   - `skills_version` — the bundle's semver release label (from `VERSION`). Coarse and
     hand-bumped by design; it is the group-by/order-by key that binds issues to a
     *release line* ("all issues on 1.2.x", "regressed since 1.1.0").
   - `skills_commit` — the exact source commit the bundle was built from: the precise
     *vendored snapshot* within a release. Many commits ship under one hand-bumped
     `skills_version`, so a run reporting `1.0.0` is otherwise unattributable to the
     change that produced its issue. The recorder resolves it, in order of authority,
     from the `sourceCommit` stamped into `.publish-manifest.json` at publish time (the
     only source that works in a partner runtime, and the only correct one once the
     bundle is vendored into `wix/skills`), else a dev-mode `git rev-parse --short HEAD`
     of the source checkout, else `null`.

   Both may be overridden by passing `skills_version` / `skills_commit` in the `start`
   dims (a runtime with better provenance than the recorder can infer), but normally you
   leave them to auto-resolve.

2. **Stage boundaries** as the pipeline moves. Map orchestrator steps to stages like this:

   | Stage | Covers |
   |---|---|
   | `config` | Everything before discovery: project resolution, config files, up-front input collection |
   | `discovery` | Source discovery (`rp-discovery` + source adapter) |
   | `mcp_gate` | The Wix MCP prerequisite gate between discovery and mapping |
   | `mapping` | `rp-mapper` producing the mapping plan |
   | `mapping_review` | The mapping review checkpoint (user-facing) |
   | `setup_discovery` | `rp-setup-discovery` |
   | `codegen` | `rp-import-codegen` |
   | `approval_gate` | The execution-plan approval gate (user-facing) |
   | `setup_provisioning` | Site creation, app installs, collections — `rp-execute-setup` |
   | `storefront_build` | The `website`-mode `wix-headless` build + release; `skipped` in `management` mode |
   | `extract` | Source extraction to disk, before any write (`rp-execute-import`) |
   | `import` | The import writes (`rp-execute-import`) |
   | `finish` | Verification spot-checks, completion reporting, handoff |

   Stages bind to **when the work actually runs**, not to their canonical order. If the
   orchestration runs extraction early (e.g. extract + dry-run before the approval gate,
   inside the codegen phase), close the open stage, bracket the extraction in its own
   `extract` stage, then reopen — a stage may be entered more than once, and its
   `active_ms` sums the entries. Booking a real extraction into `codegen` (and leaving
   `extract` as a milliseconds-long token stage) is exactly the mis-attribution the
   `extract` stage exists to prevent.

   When the `discovery` stage ends, **always** stamp what it learned:
   `dims '{"source_platform_version":"...","source_extensions":["..."]}'` — pass `[]`
   explicitly when discovery found no extensions; a null left behind is flagged in
   `telemetry_health` as `source_extensions_null_after_discovery`.

3. **`wait start` / `wait end`** around every needs-user halt: credential requests, the
   mapping-review checkpoint, the approval gate, any halt-to-needs-user. Pass the halt
   class on the same call —
   `wait start --halt missing_input --skill rp-mapper --what "run halted at the mapping-review checkpoint awaiting approval"`
   — and the recorder emits the paired `halt_needs_user` event mechanically; a stalled
   wait with no halt event in its stage is flagged in `telemetry_health`
   (`wait_without_halt_event:<stage>`). If the session is about to end on a halt, leave
   the wait open — the resume's `start` closes it, so overnight user latency lands in
   `waiting_ms` where it belongs. If you resume real work while a wait is open (e.g.
   investigating something before the user has answered), `wait end` first and
   `wait start` again when you go back to waiting — active work must never be booked as
   waiting.

4. **`record`** events as they happen (next section). Record in the moment, not
   retrospectively — improvisation and halts are only reliably knowable when they occur.

5. **`finalize`** once, at a terminal state, with the rollup:

   ```bash
   node scripts/rp-telemetry.js finalize '{"terminal_state":"completed",
     "volumes":[{"entity_type":"product","target":"native","target_surface":"stores/v3",
       "discovered":142,"planned":142,"attempted":142,"succeeded":139,"failed":3,
       "skipped":0,"already_imported":0}],
     "verification":[{"subject":"product","method":"query_back","checked":10,"passed":10,"failed":0}],
     "operator_acceptance":"accepted"}' --project <dir>
   ```

   - `volumes` come from the execution artifacts (manifest, audit log, crosswalk):
     `planned` and `target`/`target_surface` from the **approved** plan;
     `already_imported` is crosswalk-skipped scope from a prior attempt — never fold it
     into `skipped`.
   - Include a volume row for **every** entity type discovery found in use — including
     types excluded by user decision or with no clean Wix target (`discovered: N,
     skipped: N`, `target: none` where no target exists, `planned` 0 or null). "What we
     cannot or chose not to import" must be signal-layer arithmetic, never an evidence
     dig — rows only for imported types silently erase the excluded half of the approved
     plan.
   - `verification` makes the finish step's spot-checks countable. `checked: 0` is an
     honest "written but unverified" — report it rather than papering over it.
   - Do **not** finalize a halt you expect the user to resume — leave the run unfinalized
     with the wait open. Finalize with `halted_needs_user` only when the run is genuinely
     being closed out in a stalled state. A user who declines the plan is
     `abandoned_by_user` (plus a `user_decision` event with `subtype: declined`), never
     `halted_needs_user` or `failed`.

### Metering — splitting `active_ms` into where the time actually went

`active_ms` alone cannot locate a bottleneck: it is wall-clock between two stage boundaries, fusing
**model reasoning + subprocess execution + remote API latency + defect-repair time** into one
number. `meter` splits it, and cost is derived from the token counts.

```bash
# a generated script reporting its own measured work
node scripts/rp-telemetry.js meter --api-ms 250000 --api-calls 73 --api-retries 2 --script-ms 1200 --project <dir>
# the agent runtime reporting its own usage for the stage it just finished
node scripts/rp-telemetry.js meter --model-ms 42000 --input-tokens 8000 --output-tokens 1500 --cache-read-tokens 120000 --project <dir>
```

**Derive the API half mechanically — do not hand-count it.** After a generated import runs:

```bash
node scripts/meter-from-audit.js --project <migration dir> --stage import   # --dry-run to preview
```

It reads `logs/import-audit.ndjson` and emits measured `api_ms` / `api_calls` / `api_retries`.
It exists because a bulk write logs **one audit row per item, each carrying the batch's total
latency** — summing rows multiplies one call's latency by its item count, which on a real run
reported 1,050,992 ms of API time inside a stage that only existed for 346,505 ms. The helper
collapses rows per distinct `(runId, endpoint, batch, latencyMs)`, so a batch counts once while
genuinely separate calls each count.

Fields: durations `model_ms` / `api_ms` / `script_ms`; counts `input_tokens`, `output_tokens`,
`cache_read_tokens`, `cache_write_tokens`, `api_calls`, `api_retries`. Defaults to the open stage;
`--stage <stage>` targets another. Repeated calls **accumulate**, so each script invocation and each
stage re-entry reports independently.

Rules that keep the numbers trustworthy:

- **Measure, never estimate.** Every field is a number something actually observed — a script's own
  elapsed time, an audit log's summed `latencyMs`, the runtime's reported usage. If you do not have
  a measurement, omit the field; the gap is reported as `unattributed_ms` and flagged, which is far
  more useful than a guess.
- **Do not meter the same interval twice.** Meters sum, so re-reporting a stage's API time after a
  resume double-counts it. The recorder flags `stage_over_attributed:<stage>` when attributed time
  exceeds elapsed time, because `unattributed_ms` clamps at zero and would otherwise hide it.
- **Cost is derived, never recorded.** Pass `model_pricing_snapshot` in the `start` dims
  (`{ "<model>": { "input_per_mtok": 15, "output_per_mtok": 75, "cache_read_per_mtok": 1.5 } }`) and
  the rollup computes `cost.estimated_cost_usd`. A stored dollar figure silently goes wrong when list
  prices change; tokens plus a dated snapshot stay recomputable. Without a snapshot, cost is `null`
  with `cost_basis: "no_pricing_snapshot"` — never a fabricated number.
- **`contained_recovery` is derived, not self-reported.** Any `error` or `pipeline_defect` in a stage
  marks it, because a stage that spent its time debugging is the one an agent is least likely to
  remember to flag. `timing.stages_with_recovery` lists them, so a clean run and a thrash are
  distinguishable instead of both reading as "this is what the stage costs".

The rollup's `timing.agentic_ms` vs `timing.deterministic_ms` (and `agentic_share`) is the number
that shows whether moving work into deterministic code is paying off. `agentic_share` is `null` when
nothing was metered rather than `0`, which would falsely read as a fully deterministic run.

### Collecting `operator_acceptance`

At the finish handoff, ask the operator one plain question: does the migrated result look
right to them — `accepted`, `rework_needed`, or `rejected`? Record their answer in
`finalize`. If the run never reaches finish or they do not answer, it stays `unknown`.
This is the only field separating "completed and good" from "completed and unusable" —
ask it, but never pressure or interpret; their verdict as given, coarse by design.

## Event taxonomy — when to record what

Events are folded per problem class by the recorder (same type + stage + entity type +
API surfaces + app + error code + subtype fold into one event with a `count`), so record
every occurrence class once and pass `count` when you observed many at once — e.g. a
generated script reporting 4,000 identical write failures is **one** `record` call with
`"count": 4000`. Distinct problems are distinct subtypes or error codes, not bigger
counts.

Every event needs: `event_type`, `stage` (defaults to the open stage), `skill` (the
active `rp-*` resource, e.g. `"rp-mapper"`), `severity` (`blocking` | `degraded` |
`cosmetic` | `info`), and `what_happened`. Add `entity_type`, `wix_api_surface` (e.g.
`stores/v3`), `source_api_surface` (endpoint class like `wp/v2/posts` — never a URL),
`wix_app_id`, and `error_code` whenever they apply — they are the cross-run group-by keys.

1. **`halt_needs_user`** — the run stopped at a defined needs-user state. Prefer the
   mechanical form — `wait start --halt <subtype> …` emits it for you (lifecycle step 3);
   record it yourself only for a halt with no wait interval. `subtype`: `missing_input`
   (missing/invalid required input or credential) | `manual_only` (genuinely manual step,
   no API) | `systemic_failure` (systemic failure or data-loss risk).

2. **`manual_action_required`** — the execution plan flagged something as "can't be done,
   needs manual action" (e.g. a storage-plan upgrade), whether or not the run halted on
   it. `subtype`: `plan_or_billing` | `dashboard_only` | `external_dependency` | `other`.

3. **`error`** — an API or script error, including recovered ones. Carry `error_code`
   (required — it is the discriminator; no subtype), `retry_count`, and `recovered`.
   Record **one event per resolved retry chain**, after the outcome is known — the final
   `recovered`, the total `retry_count` — never one call per attempt (per-attempt calls
   fold into one class, and the failing first attempt's fields would bury the recovery).
   When recovery required a *change* (not just a retry), `actual` must record **what
   change made it succeed**, as an observation: "retried with the description as plain
   text instead of HTML; the write succeeded" — never "the fix is X". If that change
   deviated from the documented path — including a change to a *planned command or
   value*, like simplifying an input the plan specified — also emit a paired
   `skill_coverage_gap` (`undocumented_workaround`).

4. **`fidelity_loss`** — the migration technically proceeded but lost something.
   `subtype`: `dropped_field` | `unverified_enum` | `no_target` (source entity with no
   clean Wix target) | `coerced_value`. For a loss triggered by specific records, inline
   the offending record's **sanitized shape** in `observed_shapes` — field names and
   types and which field was dropped/coerced, never values.

5. **`api_gap`** — a missing or insufficient Wix API capability. `subtype`:
   `missing_api` | `missing_capability` (API exists but cannot express the operation) |
   `internal_only` (capability exists but is not publicly exposed) | `other`. Carry
   `wix_api_surface` (required) and `error_code` — that signature is matched centrally
   across runs. **Never write to any backlog file; the event is the whole per-run
   obligation.**

6. **`skill_coverage_gap`** — **the standing self-report, and the highest-value signal
   for improving the skills.** Whenever you act beyond what the active skill explicitly
   told you to do — you guess a value, work around a missing instruction, resolve an
   ambiguity by judgment, or handle a case the skill does not cover — record it **at that
   moment**. `subtype`: `guessed_value` | `undocumented_workaround` |
   `ambiguous_instruction` | `path_not_covered`. Describe the situation and the action
   taken as an observation: "skill did not specify which enum value maps to X, so a value
   was chosen to proceed" — never "the skill should add Y". If you are unsure whether
   something counts as improvisation, it does — record it.

7. **`user_decision`** — the user's answer at every defined checkpoint or fork: the
   mapping-review checkpoint, the approval gate, and intake forks (comments
   anonymization, delivery mode, media reachability, …). `subtype`: `accepted` |
   `declined` | `deferred` | `amended`; `decision_point` (required) names the fork, e.g.
   `mapping_review`, `approval_gate`, `comments_anonymization`. Record the decision, not
   the user's reasoning verbatim. An `amended` acceptance whose change exposed a mapping
   problem additionally surfaces that problem as `fidelity_loss` or
   `skill_coverage_gap`. A `declined` at a terminal checkpoint pairs with
   `terminal_state: abandoned_by_user`.

8. **`pipeline_defect`** — our own migration machinery misbehaved: not a Wix API error,
   not a source failure, not your improvisation. `subtype`: `state_inconsistency` (two
   pipeline state artifacts disagree) | `ordering_violation` (a step ran before its
   prerequisite's output existed) | `record_defect` (a pipeline record/log is malformed
   or misleading as evidence) | `other`. Name the artifacts and their states in
   `what_happened` — names and states, never contents.

### `evidence_refs` — rare, not routine

Author every event to be **self-sufficient**: coded fields + bounded free text +
`observed_shapes` should let a reviewer triage it without opening any artifact. Add
`evidence_refs` (`{"artifact":"execution-log.md","locator":"## Import"}`) only for the
exceptional event whose signal genuinely cannot carry the full observation. Use
project-root-relative paths and prefer section headings as locators (line ranges break
when files regenerate). Never reference secret-bearing config files.

## What never to do

- Never hand-write, edit, or re-read `run-telemetry.json` or `telemetry/` files — the
  recorder appends; you only call it.
- Never record client data values, names, URLs beyond the recorded source origin, or
  secrets — in any field, including shapes and locators.
- Never record a fix, root cause, or recommendation — observations only.
- Never estimate durations — timing comes from `stage`/`wait` boundary calls.
- Never skip a rejected call: fix the listed fields and retry. Rejections are counted
  against telemetry health either way.
- Never create or update improvement/feature-request backlog files during a run — those
  are maintained centrally from many runs' telemetry, not per run.
- Never delete or trim project artifacts for telemetry reasons — capture is a
  non-destructive view; size is not a reason to drop anything.

<!-- chapter:end slug=rp-telemetry -->

---

<!-- chapter:begin slug=wix-replatform position=19 -->

## 19. replatform

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-replatform/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-replatform.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (20), referenced from this skill's directory:
  - `.publish-manifest.json` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/.publish-manifest.json
  - `lib/artifact-freshness.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/artifact-freshness.js
  - `lib/completion-report.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/completion-report.js
  - `lib/config-env.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/config-env.js
  - `lib/execution-manifest.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/execution-manifest.js
  - `lib/execution-state.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/execution-state.js
  - `lib/import-recovery.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/import-recovery.js
  - `lib/local-state.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/local-state.js
  - `lib/orchestration-preflight.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/orchestration-preflight.js
  - `lib/orchestration-router.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/orchestration-router.js
  - `lib/orchestration-state.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/orchestration-state.js
  - `lib/progress-log.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/progress-log.js
  - `lib/progress-log.sh` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/progress-log.sh
  - `lib/url-preservation-state.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/lib/url-preservation-state.js
  - `scripts/artifact-freshness.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/scripts/artifact-freshness.js
  - `scripts/execution-manifest-validate.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/scripts/execution-manifest-validate.js
  - `scripts/execution-state-prepare.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/scripts/execution-state-prepare.js
  - `scripts/orchestration-preflight.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/scripts/orchestration-preflight.js
  - `scripts/orchestration-route.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/scripts/orchestration-route.js
  - `scripts/orchestration-state.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-replatform/scripts/orchestration-state.js

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: replatform
description: >-
  Routes RePlatform source-to-Wix migrations to the next workflow step by inspecting
  migration project artifacts. Use when starting, continuing, or recovering a migration run.
---

# replatform

Guide the user or agent to the next migration step by inspecting the active migration
project (default `migrations/<project>/`; see `CONVENTIONS.md` for `REPLATFORM_MIGRATIONS_DIR`).

## Purpose

This skill is the traffic controller for RePlatform work. It should determine the active
migration project, inspect authoritative orchestration artifacts, identify the next
missing decision or deliverable, and route to the appropriate internal resource.

Each migration project is fully isolated. Treat only artifacts inside the active
`migrations/<project>/` directory as authoritative for that run. Do not infer platform,
credentials, discovery results, mappings, setup, or approvals from sibling migration
projects, even when they target the same source URL or business.

## Role

You are the RePlatform expert. Your job is to help the user migrate their business from
another platform into Wix while maintaining business continuity. Guide the migration in a
way that is careful, reliable, and easy for the user to follow.

## Runtime contract

Every run follows one resumable orchestration pipeline:

resolve project → load or create `orchestration/` artifacts → collect source inputs →
resolve destination strategy → deterministic preflight → discovery → mapping →
**mapping review checkpoint** → setup-discovery →
codegen → **code safety review checkpoint** (required when `SAFE_MODE=true` or `DRY_RUN=true`) →
**execution approval gate** → setup provisioning → import → deterministic
completion reporting

**Telemetry companion (always active).** At the start of every run — including every
resume — load the internal resource `resources/rp-telemetry/` and keep its instructions
active for the entire run. It records run telemetry through its bundled recorder
(`start` at run begin, stage/wait boundaries as the pipeline moves, events as they
occur, `finalize` at a terminal state). This is the orchestrator's only telemetry
integration point; the migration skills do not change and telemetry is never
hand-written.

The orchestrator is deterministic-first:

- use durable JSON artifacts as the authoritative state contract
- prefer deterministic code and validators wherever the contract is already known
- use the LLM for user interaction, semantic mapping decisions, and structured recovery
  cases that need judgment

The submission should collect these up front so the run does not block unexpectedly:
source site URL; the source acquisition mode when the platform offers more than one
read path (for Shopify: Admin API vs public storefront; for WordPress / WooCommerce:
public content only vs authenticated access that also includes private/gated data);
source credentials required by that acquisition mode; the Wix destination mode (`new site`
vs `existing site`);
Wix authorization (new headless sites are created + written via the **Wix CLI** — it must be
logged in with `npx @wix/cli@latest login`, and site-level writes use a CLI token minted per
run with `npx @wix/cli@latest token --site "$WIX_SITE_ID"`; a raw create-time API key is no
longer used); and explicit answers to known fidelity forks (comments:
anonymize vs. skip; member-create notifications on/off; WP pages handling).

## Delivery mode: managed backend (default) vs. storefront website

A migration has two possible deliverables. Resolve which one applies **before** site
creation, and treat **management-only as the default**:

- **`management` (DEFAULT).** Migrate the business's data into a **Wix-managed headless
  backend** — the catalog/content lives in Wix (Stores, CMS, Blog, …), manageable from the
  Wix dashboard and served through Wix APIs. **No customer-facing website / JavaScript
  frontend is built.** This is a complete, valid deliverable on its own.
  **Do not ask about a website in this mode.** If the user has not asked for a site,
  storefront, or frontend, assume `management` and proceed without a clarifying question.

- **`website` (OPT-IN).** Everything in `management`, **plus** a customer-facing storefront
  (a JavaScript frontend) built with the **`wix-headless`** skill. Select this mode **only
  when the user explicitly asks** for a website, storefront, site frontend, "a site people
  can visit", or names a frontend framework. Never infer it from the mere fact that the
  source had a website — the source always did; that is not a request to rebuild one.

Record the resolved mode in `orchestration/decisions.json` and, when set, in
`config/wix.env` as `WIX_DELIVERY_MODE=management|website`. **Both modes create the destination the same way**
— a genuine Wix Managed Headless site via the Wix CLI scaffold (see "Headless site
creation"). The mode only decides whether a frontend is built:

- In `management` mode (default), scaffold the headless site, install Wix Stores (and any
  other required apps), and import the catalog. **Do not build or release a frontend.**
- In `website` mode, run the full `wix-headless` flow (it performs the same CLI scaffold,
  installs apps, and additionally builds + releases the Astro storefront), then import the
  catalog into that site (see "Website mode: build the storefront with `wix-headless`").

Site creation is identical across modes because a headless site is only produced correctly
by the CLI scaffold; `management` simply stops before the frontend build.

## Notification muting: the `mute-notifications` config (spec 0012)

Migration writes can fire Wix site notifications (emails, dashboard alerts, pushes) for
every created entity. A single config governs whether the target site is muted before
migration writes, following the `WIX_DELIVERY_MODE` pattern: record it in
`orchestration/decisions.json` and mirror it in `config/wix.env` as
`WIX_MUTE_NOTIFICATIONS=on|off`. The default resolves by site strategy and the resolved
value is **always recorded explicitly**, never left implicit:

- **`WIX_SITE_STRATEGY=new` → `on`, effectively forced.** Do not ask. `off` + `new` is a
  validation error (`rp-setup-discovery` and `rp-import-codegen` both fail it), and the
  enforcement does not consult the config for new sites — it is unconditional.
- **`WIX_SITE_STRATEGY=existing` → `off`.** Do not ask up front. Muting a live site also
  silences notifications for real visitor activity during the migration window, so it is
  an explicit opt-in (`on`) the owner can request; when opted in, the mute and its
  consequences are disclosed prominently at the execution-plan approval gate
  (`rp-execute-import`).

**Hard invariant — no mute, no import.** When mute is in effect (always for new sites;
opt-in for existing), a failed mute call at any point (setup provisioning, import-script
preflight) halts the run to needs-user with the failure recorded. Never continue to
import writes with a warning, and never offer a continue-anyway option. Enforcement is
double-locked downstream: a standing setup requirement (`rp-setup-discovery` →
`rp-execute-setup`, ordered before all other setup writes) plus a preflight assertion in
the generated import script (`rp-import-codegen`).

**Terminal-report disclosure — every terminal state.** Nothing in the flow unmutes
automatically, so any site the flow muted is still muted at completion, halt, or abort.
Whatever report the run ends with must derive "was muted" from **recorded state**
(`setup/setup-verification.json` mute item + the import run's preflight log entries) —
never infer it from strategy/config — and, when a successful mute is recorded, state in
plain language:

- **all site notifications are currently muted**, and stay muted until re-enabled;
- **the owner can simply ask the agent to unmute them** whenever they want — on request,
  call `unmuteSiteNotifications` (rp-target-wix) and confirm `muted: false` via
  `getSiteMuteState`;
- alternatively they can re-enable manually (UnmuteSite —
  `POST /notification-preferences/v1/site-mute/unmute` — or the dashboard path once one
  exists).

For existing sites this disclosure is **mandatory and prominent** — it is the only
remaining safeguard against a live business staying silenced. If no successful mute is
recorded, the report must not claim the site is muted. The unmute is **never** run as
part of the flow itself — explicit owner request only.

## Tone

Use a tone that is:

- professional
- friendly
- confidence-building

Explain the process clearly, avoid sounding uncertain when the workflow is defined, and
help the user understand what is happening and what will happen next. Be direct, calm, and
practical. Do not overwhelm the user with internal detail that does not help them make the
next decision.

## User interaction contract

Keep interaction narrow and task-directed.

Allowed interactions:

- request one missing required input or credential
- ask the user to choose the active migration project when project resolution is genuinely ambiguous
- present the execution plan report and wait for explicit acceptance before any write
- halt to a defined needs-user state with the exact unblock action

Ask questions **one by one**. Do not bundle multiple unrelated questions into a single
message. Ask the next question only after the previous one is answered, unless a later
skill explicitly requires a single grouped approval artifact such as the execution plan.

### Rules that hold in every run

- **One mandatory approval gate precedes _all_ writes to the user's site** — both setup
  provisioning and the import. Before writing anything, present the **execution plan report**
  and wait for explicit user acceptance. The report covers: the **setup changes** that will
  be made (apps to install, Wix Data enablement, collections to create), **what will be
  imported and where** (entities → Wix targets + counts), and **what can't be done and
  needs manual action**. The job pauses, surfaces the plan, and resumes only on accept.
  See `rp-execute-import` → Execution plan & user acceptance.
- **Read-only work runs before the gate; writes run after.** Discovery, mapping, codegen,
  preview, and **read-only setup verification** (checking what's installed/missing) run
  before acceptance to make the plan accurate. The "Migrate" consent + credentials
  authorize the migration but are **not** a green light to start writing — only plan
  acceptance is. After acceptance, run setup provisioning, then import, without
  re-prompting per app/collection/write.
- **Dry-run remains sticky unless the user explicitly approves leaving it.** When
  `DRY_RUN=true`, treat the project as dry-run-only by default. The one allowed exception
  is **new-site creation** for `WIX_SITE_STRATEGY=new`: the Wix CLI headless scaffold may
  run live even while the migration otherwise remains in dry-run mode, because no site
  exists yet to target. Every other live override (`--no-dry-run`, setup provisioning,
  app installation, live setup verification probes, demo-catalog cleanup, import writes)
  requires explicit user approval to leave dry-run for that step or phase, and should be
  avoided when a dry-run/report alternative exists.
- **Mapping review is a separate semantic checkpoint before setup/codegen.** After
  `rp-mapper` writes `mapping/mapping-plan.json`, it must also write a concise
  `mapping/review/mapping-summary.md` for user review. Pause there and ask the user to review
  `mapping/review/mapping-summary.md` first, using `mapping/review/mapping-plan.md` for full details, and confirm that
  the source entities, Wix targets, main gaps/lossiness, and major setup implications
  match their intent. Do not proceed to `rp-setup-discovery` or `rp-import-codegen`
  until the user accepts this mapping review checkpoint.
- **Safe-mode / dry-run code review is an agent-run checkpoint after codegen and before execution approval.**
  When `SAFE_MODE=true` or `DRY_RUN=true`, `rp-import-codegen` must also write
  `execution/review/code-safety-review.md`. The agent must perform this review itself by
  inspecting the generated code and the mapping artifacts before asking the user for final
  approval. That artifact must verify the generated code, not just the plan: every
  relevant writer path passes `safeModeOptions` into the shared Wix runtime or direct REST
  wrapper where applicable; dry-run uses the same code path with Wix calls skipped only at
  the shared boundary; dry-run reports do not claim live writes happened; and the resolved
  safe-mode replacement paths match the mapping artifacts. If the review finds any gap,
  fix the code and regenerate the review artifact before surfacing it. The user approves
  whether to proceed after the review passes; the user is not responsible for performing
  the review itself.
- **Record every material decision** in the project artifacts
  (`mapping/review/mapping-plan.md`, `mapping/review/mapping-summary.md`,
  `setup/setup-verification.json`, `execution-log.md`).
- **Promote verified write contracts before continuing.** When same-session live
  verification changes a Wix target write assumption, require a machine-readable
  `contract-ledger-proposal.json`, promote the accepted proposal into shared
  `rp-target-wix` domain metadata in the same session, or record an explicit deferral
  reason. Generated code may rely on promoted ledger entries, not unreviewed local probe
  output.
- **Keep execution review artifacts fresh.** `execution/review/import-plan.md` must have
  freshness metadata covering the source schema, mapping plan, setup verification,
  generated import code revision/hash, and target contract ledger revision. Before live
  import and final reporting, check this metadata; if stale, regenerate the import plan or
  write `execution/review/import-plan-delta.md` and make completion reporting reference
  the latest accepted plan/delta.
- **Be non-destructive and idempotent:** never delete or overwrite existing user content;
  dedupe by source ID; resume rather than restart. Do not assume native Wix entity IDs can
  be preserved or client-assigned. When the target API assigns IDs server-side, the
  workflow must maintain a durable local `sourceId -> targetId` crosswalk under
  `migrations/<project>/state/crosswalk/` for resume and relationship resolution. CMS
  `ImportCrosswalk` is optional site-local mirror/seed data for existing-site flows, not
  the runtime source of truth.
- **Preserve public URL intent as local state:** for every migrated public routed entity,
  mapping must capture route/base-path and slug policy, and import execution must write
  local URL preservation artifacts under `migrations/<project>/state/url-preservation/`.
  The current import phase records base paths, URL ledger rows, unresolved route rows, and
  redirect plans, but does not apply Wix redirects or configure site routing.
- **Halt to needs-user only for:** a missing/invalid required input or credential; a
  genuinely manual step with no API (e.g. storage-plan upgrade); or a systemic failure /
  data-loss risk. When halting, write the reason to the artifacts and surface it — never
  silently proceed and never silently stop.

## Step 1: Resolve the active project

Resolve `<migrations-root>` first: use `REPLATFORM_MIGRATIONS_DIR` when set (absolute or
relative to cwd); otherwise default to `migrations/` under the host project's cwd. See
`CONVENTIONS.md`.

Determine `<migrations-root>/<project>/` using this order:

1. Explicit project name provided by the user.
2. Current working context already referencing `<migrations-root>/<project>/`.
3. If exactly one project exists under `<migrations-root>/`, use it.
4. If multiple projects exist and none is clearly active, ask the user to choose; do not infer.

## Step 2: Inspect project artifacts

Look for these artifacts first:

- `config/wix.env`
- `orchestration/run.json`
- `orchestration/checkpoints.json`
- `orchestration/decisions.json`
- `orchestration/approvals.json`
- `config/source.<platform>.env` once the source platform is known
- `source-profile.md`
- `source-schema.json`
- `discovery/run.json`
- `discovery/entities/index.json`
- `mapping/mapping-plan.json`
- `mapping/review/mapping-summary.md`
- `setup/setup-plan.json`
- `setup/setup-requirements.json`
- `setup/setup-verification.json`
- `execution/execution-manifest.json`
- `execution/review/code-safety-review.md`
- `execution/completion-report.json`
- `execution/review/import-plan.md`
- generated code under `src/setup/`, `src/extract/`, `src/import/`
- `execution-log.md`

Reuse existing files if they already exist. Do not create parallel versions of the same artifact unless the user asks for alternatives.

Inspect only the active project's artifacts for resume and inference. Sibling
`migrations/<other-project>/` directories are out of scope and must not influence the
current run.

Treat each artifact as a complete checkpoint only when it is well-formed (e.g.
`source-schema.json` parses and contains at least one entity; required JSON artifacts parse
and contain their required top-level fields; markdown review artifacts are non-empty and
not truncated). A malformed or partial artifact means the stage
that produces it did NOT finish — re-run that stage rather than treating the file
as present. Skills should finish writing an artifact in one pass so a half-written
file is never mistaken for a completed one.

`execution-log.md` is not an authoritative resume source. Use it for chronology and
operator/debug context only. Resume state must come from the orchestration JSON artifacts
plus the active phase artifacts.

## Step 2.1: Verify project-local config files before discovery

Before source discovery, make the migration project's config explicit. Any value that a
skill, generated script, or setup step expects as an environment variable must have a
home in a project-local config file under `migrations/<project>/config/`.

Use `.env` syntax (`KEY=value`) so humans can edit the files and generated scripts can
load them without extra dependencies.

## Secret-safe config handling

Treat these as **secret-bearing files** once they may contain real user values:

- `migrations/<project>/config/wix.env`
- `migrations/<project>/config/source.<platform>.env`
- any equivalent local env/toml/json file carrying auth tokens, passwords, API keys, or
  application credentials

Rules:

- Never print or paste the contents of those files into tool output, chat, artifacts, or
  logs.
- Do not read them with whole-file commands that echo contents verbatim (`cat`, broad
  `sed`, `head`, `tail`, broad globs) after they may be populated.
- Verify them with secret-safe checks only: file exists, required keys exist, and each key
  is `present` / `blank` / `missing`.
- If a file must be created as a template, create it with empty values and from that point
  forward treat it as secret-bearing even if some values are still blank.
- When reporting status, name keys only; never include values, partial values, or
  redaction mistakes such as printing `KEY=value` lines.

Always create/verify:

- `config/wix.env`
  - `WIX_SITE_STRATEGY=`
  - `WIX_SITE_ID=`
  - `WIX_AUTH_TOKEN=`

After the source system is identified or inferred, choose the source acquisition path
when the platform supports multiple modes. For Shopify URL-based migrations, ask whether
to use the Shopify Admin API or only publicly available storefront data. For WordPress /
WooCommerce URL-based migrations, ask whether to import only publicly available content
or also include private/authenticated data. Only the private/authenticated choice should
lead to a credentials request. Treat user-provided files/exports as a separate ingestion
flow that begins from those files, not from a site URL probe.

After the acquisition path is chosen, create/verify the adapter-specific source config.
For WordPress / WooCommerce:

- `config/source.wordpress.env`
  - `WP_BASE_URL=`
  - `WP_USERNAME=`
  - `WP_APPLICATION_PASSWORD=`
  - `WC_CONSUMER_KEY=` (optional; only when WooCommerce does not accept the WordPress
    Application Password)
  - `WC_CONSUMER_SECRET=` (optional; same condition)

### File-provided runs (CSV)

When the user provides one or more CSV/export files instead of a site URL, the run is
file-based from the start. There is **no acquisition-mode fork and no credentials request**.

- Record `sourcePlatform=csv`, `sourceMode=files_only`, and **every** input file path in
  `fileInputPaths` (an array) in `orchestration/decisions.json`. Preflight requires no source
  env keys for `csv`; it checks that `fileInputPaths` is non-empty instead. Until it is, the
  run sits in `awaiting_files`.
- Create/verify `config/source.csv.env` with optional keys only:

  ```bash
  CSV_INPUT_ROOT=
  CSV_DELIMITER=
  CSV_ENCODING=
  CSV_VENDOR=
  CSV_MEDIA_URL_REWRITE_FROM=
  CSV_MEDIA_URL_REWRITE_TO=
  ```

  Unlike `source.wordpress.env`, this file is **not secret-bearing** — it holds
  delimiter/encoding/vendor/rewrite hints only. Regular file handling applies. Every key is
  optional: blank means auto-detect, so a blank file never blocks discovery.
- Discovery then uses the `rp-source-csv` adapter, which identifies the vendor (Shopify,
  WooCommerce, Magento, BigCommerce, or `custom`) from the header row. Ask the user to name
  the vendor only if the adapter reports a low-confidence or near-miss detection.

Workflow:

1. If `config/wix.env` is missing, create it with empty keys, the requested dry-run mode,
   and safe mode enabled:

   ```bash
   WIX_SITE_STRATEGY=
   WIX_SITE_ID=
   WIX_AUTH_TOKEN=
   DRY_RUN=false
   SAFE_MODE=true
   SAFE_MODE_PHONE_NUMBER=+972 50 0000000
   ```

   Safe mode replaces outbound Wix email/phone write values with deterministic mock values
   unless the user explicitly sets `SAFE_MODE=false` before mapping.
   Dry-run is disabled by default; `DRY_RUN=true` or `--dry-run` runs the same generated
   setup/import entrypoints while skipping Wix calls at the shared Wix boundary.
   If the user asks to start, create, prepare, or run a migration "in dry-run mode",
   write `DRY_RUN=true` into `config/wix.env` during scaffolding and carry that mode into
   the execution-plan report. Do not later override it with `--no-dry-run` except for the
   allowed new-site creation step or after explicit user approval to leave dry-run.
2. Ask for the source site/app URL before asking for the platform. Try to infer the
   platform from that URL or from a lightweight probe of the source (for example, a known
   REST index, platform-specific headers, or HTML/application markers). Only ask the user
   to identify the platform if detection is inconclusive.
3. If the inferred platform offers multiple acquisition modes, ask the user to choose the
   right one before requesting credentials.
   - For Shopify URL-based migrations, the fork is: `Shopify Admin API` vs `public
     storefront data only`.
   - For WordPress / WooCommerce URL-based migrations, the fork is: `public content only`
     vs `also include private/authenticated data`.
   - For the WordPress / WooCommerce `public content only` path, do **not** ask for a
     username, application password, or WooCommerce keys before discovery/import. Proceed
     unauthenticated and make clear that the run will include only public data.
   - For the WordPress / WooCommerce `also include private/authenticated data` path, ask
     for the required credentials after the user selects that mode.
   Do not ask about manual exports at this step; exports/files are a separate flow used
   when the user provides files instead of a site URL (see "File-provided runs (CSV)").
4. Before asking for any Wix site ID, ask whether the destination should be a `new site`
   or an `existing site`, and record that as `WIX_SITE_STRATEGY`.
   - If the user chooses `existing`, ask for `WIX_SITE_ID` later when it is the next
     missing Wix detail.
   - If the user chooses `new`, do not ask for an existing site ID; route to the site
     creation step and fill `WIX_SITE_ID` only after the new site exists.
   - For `new site`, the next question should be about the kind of Wix site to create
     (for example standard Wix site vs Wix Studio vs headless, or another site-creation
     fork required by the active tooling) rather than asking for a site ID that does not
     exist yet.
  - RePlatform destinations are **Wix Managed Headless** sites. Create them with the **Wix
    CLI headless scaffold** — see "Headless site creation" below. The site id comes from the
    scaffolded `wix.config.json`, not from an API key, so do **not** ask for a site-creation
    API key here; the only prerequisite is that the Wix CLI is logged in
    (`npx @wix/cli@latest login`).
5. Once the platform and acquisition mode are known, create the matching
   `config/source.<platform>.env` with empty keys and ask for missing required values one
   at a time.
6. Treat blank required keys as `needs-user`; do not start discovery if the missing value
   would make discovery incomplete. Optional keys may remain blank when the adapter says
   they are optional.
7. Generated scripts should load project-local config first, then process environment,
   with real environment variables allowed to override file values. Blank config values
   must never overwrite non-empty environment variables.

Never print secret values back to the user. It is fine to say a required secret is
present or missing.

## Headless site creation

When `WIX_SITE_STRATEGY=new`, site creation happens before setup/import. **Both delivery
modes create the site the same way** (see "Delivery mode"); `management` just stops before
the frontend build.

**Create the site with the Wix CLI headless scaffold — not the account-level Projects API.**
The Projects API (`POST /funnel/projects/v1/create`) was the prior default but is
**deprecated for this workflow**: in testing it produced sites that were **not** genuinely
headless and it silently dropped the `apps[]` install list. The CLI scaffold
(`npm create @wix/new@latest headless`, the same path the `wix-headless` skill uses) is the
verified way to get a real Wix Managed Headless site.

**Method (delegate to the `wix-headless` scaffold):**

- **Prerequisite:** the Wix CLI must be logged in **to the account the user intends the site
  to live on**. Check `npx @wix/cli@latest whoami`; if logged out, halt to needs-user to run
  `npx @wix/cli@latest login` (interactive — it prints a URL + code; it cannot be done from a
  raw API key). This replaces the old "site-creation API key" input — headless creation is
  CLI-authenticated, tied to the logged-in Wix account.
- **Confirm the account BEFORE scaffolding — this is a mandatory gate, not a nicety.** The
  CLI silently creates the site on whatever account it is logged into, with no error if it's
  the wrong one. Show the user the `whoami` email and confirm it is the intended Wix account.
  If it is not (e.g. a personal Gmail account instead of the user's business account), halt to
  needs-user: `npx @wix/cli@latest logout` then `login` as the correct account, and re-verify.
  Do not scaffold against an unconfirmed account. **Use one account consistently for the whole
  run** — the same CLI account creates the site *and* mints the import write token; never mix
  in a separate `WIX_AUTH_TOKEN` API key that belongs to a different account (a create/import
  account split silently writes to the wrong place or 404/403s).
- **Scaffold inside the active migration project** with the Wix CLI create command (this is
  the primary, self-contained path — it needs no other skill installed). Run it from
  `migrations/<project>/` and use `--folder-name frontend` unless the project artifacts
  explicitly name a different scaffold folder. The command creates the `frontend/`
  subfolder; it does not replace the migration folder or become the migration root:

  ```bash
  npm create @wix/new@latest -- headless \
    --business-name "<Brand Name>" \
    --folder-name frontend \
    --site-template commerce --no-publish --skip-install
  ```

  This creates a real headless site and writes `frontend/wix.config.json` with `appId` and
  `siteId`. This is the one step that may run live while the rest of the migration
  remains in dry-run mode. Run the `npm create` command above directly —
  `wix-headless` no longer ships a `scripts/scaffold.sh` wrapper, and this path
  deliberately needs no other skill installed.
- **`--site-template commerce` is load-bearing when the migration carries a product
  catalog — never scaffold `blank`.** `--site-template` accepts
  `commerce|scheduler|registration|blank`, and **passing it bare, with no value, means
  `blank`** ("no business solution preconfigured"). On a blank site, installing Wix Stores
  afterwards through the App Installation API provisions **Catalog V1**, which this workflow
  does not support at all — the Stores primitives in `rp-target-wix` are V3-only. A site's
  catalog version is **fixed at provisioning** — there is no in-place V1 → V3 switch — so a
  blank scaffold silently costs the whole site and forces a rebuild. The `commerce` template
  is Commerce (Wix Stores) and provisions Stores on **Catalog V3** at creation. In `website`
  mode, treat its pages as scaffolding to restyle, not as the delivered design.
- **Exactly one destination site per migration.** Site creation happens **once**. Before
  scaffolding, read `WIX_SITE_ID` from `config/wix.env` — non-empty means the site already
  exists, so do **not** scaffold again: not to retry a failed step, not to "start clean",
  not after an error. **Never create a probe, test, or throwaway site** to verify what a
  template or an install produced — every site lands in the user's real Wix account, stays
  visible there, and has to be deleted by hand. Verify on the migration's own site with the
  read-only catalog-version check below. If the site is genuinely unusable (e.g. it came up
  `V1_CATALOG`), halt to needs-user with the site id and ask to approve a replacement rather
  than rebuilding silently; on approval, write the new id into `config/wix.env` and report
  the abandoned site id as safe to delete.
- **Record ids from `wix.config.json`:** persist `siteId` into `config/wix.env` as
  `WIX_SITE_ID`, and keep `appId` in project artifacts. Construct the dashboard URL from the
  site's `metaSiteId` (resolve it via `ListWixSites`/site query when needed).
- **Auth for later site-level writes is a CLI token, not a raw API key.** Mint it without
  printing the secret to the transcript by running `scripts/mint-token.sh` from the
  migration project root. The canonical copy lives at
  `skills/replatform/resources/rp-execute-setup/scripts/mint-token.sh`; codegen copies it
  into each migration project at scaffolding time. Run it via Bash:

  ```bash
  bash migrations/<project>/scripts/mint-token.sh
  ```

  Token shape: `OauthNG.JWS.<base64>.<base64>.<sig>` — a single line on stdout with no JSON
  wrapper. Send it as a `Bearer` token (`Authorization: Bearer <token>`, plus
  `wix-site-id: <siteId>`). The generated import client must send it as a Bearer token.
  (`WIX_AUTH_TOKEN` is no longer a create-time API key; if set, it is the site write
  credential the generated code consumes.)

  **Do not** run `npx @wix/cli@latest token` raw in a Bash tool call — it prints the
  credential to stdout which lands in the transcript. Always route through `mint-token.sh`.

**Wix Stores (and other apps) are NOT installed by the scaffold.** A freshly scaffolded
headless site has no Wix Stores — `stores/v3` returns `428 REQUIRED_APP_NOT_INSTALLED` until
Stores is installed. Install required apps as a distinct step after scaffolding, via the
same path `wix-headless` uses (its `SETUP.md` Step 3 → the `wix-manage` skill / app-install
API), authenticated with the CLI token. Verify installation (e.g. `stores/v3/products/count`
responds) before importing. Do not assume creation installed Stores.

**Then verify the catalog is V3 — before any Stores write.** Installing Stores does not
guarantee Catalog V3 (a `blank`-scaffolded site comes up V1). Gate on the read-only
Catalog Versioning API:

```bash
curl -s -H "Authorization: Bearer $WIX_AUTH_TOKEN" -H "wix-site-id: $WIX_SITE_ID" \
  https://www.wixapis.com/stores/v3/provision/version
```

`catalogVersion` is `V3_CATALOG` (proceed), `STORES_NOT_INSTALLED` (install Stores, re-check),
or `V1_CATALOG` (**stop — write nothing, and see the one-site rule above**). Record the
result in `setup/setup-verification.json`. Do not skip this check on the assumption that
every Stores install is V3.

**Wipe the default demo catalog before importing — in BOTH modes.** A fresh Wix Stores
install (and `wix-headless`'s seed step) pre-populates the store with ~12 placeholder demo
products and demo categories. Left in place they mix with the migrated catalog (a clean
100-product import otherwise reads as 112). After Stores is installed and before/right after
the import, delete the default demo products and demo categories so the store holds **only**
the migrated data (keep the system `All Products` category). This is the same cleanup the
website-mode section requires; it applies to the default `management` mode too, because both
modes now start from a freshly-provisioned Stores catalog.

**Site-scoping gotcha:** Wix Stores/Categories site-level APIs scope by the site's
**metaSiteId** in the `wix-site-id` header on some sites; if the scaffold `siteId` returns
`404`/`meta-site not found` on a Stores call, resolve and use the `metaSiteId`.

## Website mode: build the storefront with `wix-headless`

Run this only when the delivery mode is `website` (the user explicitly asked for a
storefront/website/frontend). In `management` mode, skip this section entirely — the run
ends after import.

`wix-headless` builds a JavaScript storefront **only for a site it creates itself**; it
cannot target a pre-existing site id. So in `website` mode let it own the CLI scaffold — do
**not** run the "Headless site creation" scaffold yourself first (that would produce a
second, empty site). RePlatform then imports the migrated catalog into the site
`wix-headless` created.

Sequence:

1. **Read-only migration prep first.** Run discovery → mapping (+ review checkpoint) →
   setup-discovery → codegen as usual. Do **not** create a site yet, and leave
   `WIX_SITE_ID` blank.
2. **Hand off site creation + storefront to `wix-headless`.** Invoke the `wix-headless`
   skill (Skill tool, name `wix-headless`; it starts at its own `SKILL.md`, which resolves the
   project type and routes a new-site run to `references/managed/CREATE.md`) with the store
   intent and brand. It creates a new headless site with Wix Stores installed, scaffolds an
   Astro storefront, and builds + releases it, yielding a live URL. Its demo seeding is
   idempotent and its pages read the live catalog at request time.
   **Tell it to scaffold `--site-template commerce`.** Its own create flow scaffolds the
   `blank` template on the reasoning that the model owns the design — correct for a
   build-from-a-prompt run, wrong here: a blank site's Stores install comes up **Catalog V1**
   and cannot be converted (see "Headless site creation"). Its template pages are scaffolding
   to restyle against the captured source, not the delivered design.
3. **Adopt that site as the migration destination.** Read `siteId` from the `wix-headless`
   project's `wix.config.json`, persist it into `config/wix.env` as `WIX_SITE_ID`, and keep
   `WIX_SITE_STRATEGY=new`. The Wix auth used for import must be able to write to that site.
   Then run the catalog-version gate from "Headless site creation" before any Stores write —
   a site handed over by `wix-headless` is not exempt.
4. **Import into it — after removing the demo catalog.** Because `wix-headless` seeds demo
   products/categories, first delete that demo catalog on the site, then run the generated
   import (still behind the execution-plan approval gate) so the store holds only the
   migrated data. Storefront pages query the live catalog, so the released site then serves
   the migrated catalog; re-release only if statically generated routes must regenerate.
5. **Report both layers.** The final handoff must state the storefront URL **and** keep the
   `catalog/data imported` vs `website/homepage built` distinction (see `rp-execute-import`).

If `wix-headless` is unavailable in the runtime, halt to needs-user (it is required for
`website` mode — the storefront build); do not fall back to hand-building a frontend.

## Recovery

- **Resume is the default.** If the workflow stopped mid-migration, re-running
  orchestration inspects `orchestration/` plus phase artifacts (Step 2) and routes to the first
  material gap. No work is repeated unnecessarily.
- **Never auto-delete.** Existing artifacts are preserved unless the user asks
  otherwise.
- **From scratch is explicit and whole-project.** Only when the user explicitly
  asks to start over, delete the entire `migrations/<project>/` directory and
  re-run from discovery. Do not partially wipe individual stages.

## Step 3: Choose the next step

Route according to the first material gap by consulting the matching internal resource
under `resources/`:

- Project-local config files missing or missing required values: create/update
  `config/wix.env` and, after source platform is known, `config/source.<platform>.env`;
  ask the user for missing details one at a time.
- Source acquisition decisions are known but destination strategy is not: resolve
  `deliveryMode` and `WIX_SITE_STRATEGY`, defaulting `deliveryMode=management` unless the
  user explicitly asked for a storefront or frontend.
- Source and destination decisions exist but deterministic preflight has not passed: run
  the preflight contract and persist `orchestration/preflight.json`.
- No source-system understanding: consult `resources/rp-discovery/`.
- Source schema exists but no approved mapping: consult `resources/rp-mapper/`.
- Mapping plan exists but `mapping/review/mapping-summary.md` is missing: consult
  `resources/rp-mapper/` to generate the summary and stop for user review.
- Mapping summary exists but the mapping review checkpoint has not been accepted: surface
  `mapping/review/mapping-summary.md`, ask the user to review it, and wait for acceptance before
  continuing.
- Mapping exists but Wix-side requirements are unclear: consult
  `resources/rp-setup-discovery/`.
- Mapping and setup requirements exist but import code is missing: consult
  `resources/rp-import-codegen/`.
- Safe mode or dry-run is enabled, but `execution/review/code-safety-review.md` is
  missing, stale, failed, or not yet accepted: consult `resources/rp-import-codegen/`,
  run the automatic review, fix any gaps it finds, regenerate the artifact, then surface
  the passing review for user acceptance before the execution approval gate.
- **`website` mode, code generated but no destination site yet:** hand off to `wix-headless`
  to create the site + storefront, then adopt its `siteId` (see "Website mode: build the
  storefront with `wix-headless`") before setup/import.
- Setup artifacts exist but are not verified: consult
  `resources/rp-execute-setup/`.
- Code and setup are ready, any required code-safety review has been accepted, and
  `execution/execution-manifest.json` plus execution approval exist: consult
  `resources/rp-execute-import/`.
- **`website` mode, catalog imported:** confirm the storefront is released and serving the
  migrated catalog; report its URL. In `management` mode the run is done after import — do
  not build a frontend.

## Output

Respond minimally with:

- active project path
- artifacts found
- critical gaps
- exact next recommended skill
- concrete next action

## Guardrails

- Do not guess the source schema when discovery artifacts are missing.
- Do not generate import code before a mapping plan exists.
- Do not execute import before setup verification and code review are complete.
- When notification mute is in effect (spec 0012), never proceed to import writes
  without a verified mute — a failed mute halts to needs-user, with no
  continue-anyway path. Never report a site as muted from strategy/config inference;
  only from recorded verification state. Never unmute unless the owner explicitly
  asks.

<!-- chapter:end slug=wix-replatform -->

---

<!-- chapter:begin slug=wix-vibe-headless position=20 -->

## 20. wix-vibe-headless

- **Source:** https://github.com/wix/skills/blob/main/skills/wix-vibe-headless/SKILL.md
- **Raw:** https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/SKILL.md
- **Markdown:** https://skillsdocs.com/wix/skills/wix-vibe-headless.md
- **Licence:** MIT — https://spdx.org/licenses/MIT.html

Bundled files (131), referenced from this skill's directory:
  - `install/deploy.cjs` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/install/deploy.cjs
  - `install/pin-agents-md.cjs` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/install/pin-agents-md.cjs
  - `platforms/base44.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/platforms/base44.md
  - `platforms/generic.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/platforms/generic.md
  - `platforms/README.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/platforms/README.md
  - `references/blog/app/components/PostCard.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx
  - `references/blog/app/components/PostChips.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx
  - `references/blog/app/components/PostGrid.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/components/PostGrid.jsx
  - `references/blog/app/context/TaxonomyContext.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/context/TaxonomyContext.jsx
  - `references/blog/app/hooks/usePostDetail.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/hooks/usePostDetail.js
  - `references/blog/app/pages/Blog.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/pages/Blog.jsx
  - `references/blog/app/pages/CategoryPage.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx
  - `references/blog/app/pages/PostDetail.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx
  - `references/blog/app/pages/TagPage.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx
  - `references/blog/app/rest/wix-blog.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/app/rest/wix-blog.js
  - `references/blog/INSTRUCTIONS.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md
  - `references/blog/seed/seed-blog.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/seed/seed-blog.js
  - `references/blog/seed/SEED.md` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/blog/seed/SEED.md
  - `references/bookings/app/components/BookingForm.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/bookings/app/components/BookingForm.jsx
  - `references/bookings/app/components/ServiceCard.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx
  - `references/bookings/app/components/ServiceGrid.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/bookings/app/components/ServiceGrid.jsx
  - `references/bookings/app/components/SlotPicker.jsx` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/bookings/app/components/SlotPicker.jsx
  - `references/bookings/app/hooks/useServiceDetail.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js
  - `references/bookings/app/hooks/useServices.js` — https://raw.githubusercontent.com/wix/skills/main/skills/wix-vibe-headless/references/bookings/app/hooks/useServices.js
  - …and 107 more, listed in https://skillsdocs.com/api/v1/books/wix/skills/skills/wix-vibe-headless

<!-- Verbatim upstream SKILL.md follows, YAML frontmatter included. -->

---
name: wix-vibe-headless
description: "Client-only, dependency-free REST scaffolds for connecting an already-built front end (a vibe-coded app, an HTML/JSX/Vite project, a design-tool export) to a live Wix site over the site's public WIX_CLIENT_ID — the browser talks to Wix directly, no SDK, no backend, no build step. One skill covering every Wix business solution: Stores/eCommerce storefront (products, cart, checkout), Bookings (services, slots, appointments), Blog (posts, categories, tags), Events & Tickets (browse, RSVP, ticketing), Portfolio (collections, projects, galleries), Restaurants (menu, online ordering, reservations), CMS / Wix Data (list, detail, filter, forms, CRUD), Pricing Plans (memberships, subscriptions, checkout), and Members (custom login — email+password, Google/Facebook, and custom SSO — plus account areas and member-gated content). Each vertical ships a copy-as-is REST layer plus wiring instructions. Read-only over the owner's content — never provisions, never mocks data. Triggers: connect my Wix store/shop, build a storefront over Wix, add a cart and checkout, connect Wix Bookings, take appointments/reservations, show my Wix blog, list my Wix events, sell tickets, take RSVPs, build a portfolio from Wix Portfolio, show my restaurant menu / order online / book a table, display my Wix CMS collection, wire a contact form to Wix, sell membership/subscription plans, add member login / sign up, let members log in with Google or Facebook, custom login page, account / profile page, gate content behind login, sign in with SSO/Okta, 'here is my WIX_CLIENT_ID', connect this app to my Wix site over REST. Use this for CLIENT-ONLY REST integration over an existing site; use `wix-headless` instead for SDK + Wix CLI builds, hosting, and one-prompt new-site creation."
---

# Wix Vibe Headless — client-only REST connectors

Wire an existing front end to a live Wix site **from the browser**, over the site's public
`WIX_CLIENT_ID`, using hand-rolled REST — **no `@wix/sdk`, no backend, no build step, no
dependencies**. One skill, one shared transport, and a copy-as-is REST layer per Wix
business solution. Everything is **read-only over the owner's content**: render live Wix
data or an honest empty state — **never mock, never provision, never invent** products,
posts, events, menus, plans, reviews, or counts.

## When to use this skill

- The user has (or is building) a front end — a vibe-coded app, plain HTML/JSX, a Vite/React
  project, a design-tool export — and wants it to show **live data from their existing Wix
  site** and complete real purchases/bookings, all from the client.
- They hand you a **public `WIX_CLIENT_ID`** and ask to "connect this to my Wix store /
  blog / bookings / events / …".
- They want to replace placeholder/mock data with real Wix content, or add a cart, checkout,
  booking, RSVP, ticketing, reservation, form, or subscribe flow over an app they already have.

## When NOT to use this skill

| Scenario | Use instead |
|---|---|
| Build a **new** Wix site end-to-end from one prompt (discovery → design → build → host) | `wix-headless` |
| The project should use the **Wix SDK** (`@wix/sdk`) and/or the **Wix CLI**, or be **hosted on Wix** | `wix-headless` |
| Manage/configure the site via REST (install apps, seed catalogs, set up business solutions) | `wix-manage` |
| Build a Wix **app extension** (dashboard page, widget, backend, plugin) | `wix-app` |

This skill is the deliberately **client-only, REST-only** path. It is independent from
`wix-headless` (which is SDK + CLI + hosting) — do not mix the two in one project.

## The shared model (applies to every vertical)

- **Auth = one public client id.** `WIX_CLIENT_ID` is a **buyer/visitor-facing** credential —
  it only mints anonymous visitor tokens. It is **not a secret**; hardcoding and committing it
  is fine. The user provides it (their vibe/host platform surfaces a copyable prompt with the
  id filled in). Paste it into `wix-config.js` in place of the `<YOUR-CLIENT-ID>` placeholder.
- **Money/price fields are objects, never scalars.** Every price/amount a Wix API returns —
  Stores `price.actualPrice`, Bookings service `payment.fixed.price`, Events ticket
  `registration.tickets.lowestPrice`, and the rest — has the shape `{ value, currency,
  formattedValue }`. Render **`formattedValue`** (it already includes the currency symbol), or
  fall back to building from `value` + `currency`. **Never** drop the object straight into the UI
  or treat it as a number/string — that renders `[object Object]` or a bare unformatted number.
- **Visitor token = identity.** `wix-client.js` mints an anonymous visitor token, persists the
  **refresh token to `localStorage`**, and refreshes on expiry. That token IS the identity of
  the cart / reservation / member session — **never re-mint anonymously per load** or the cart
  silently empties.
- **Member login swaps the token set on the same client.** The **members** vertical is *custom*
  login — the front owns its login UI; the member is **never redirected to a Wix-hosted login page**.
  On success it writes the member's tokens into the *same* store the visitor token used
  (`setSessionTokens`), so **every subsequent `wixApiRequest` runs as the member** and the cart/session
  carries over. "My …" surfaces (plans, orders, bookings, registrations) light up only once logged in.
- **⚠️ Once you're building on Wix, keep it on Wix — auth, member data, and other business features
  too.** If you're wiring a Wix storefront/blog/etc. over `WIX_CLIENT_ID`, prefer Wix for the *rest*
  of the app as well, rather than reaching for a host platform's own backend or login. **Especially
  for auth:** when the user wants members, log them in as a **Wix member** (`members` vertical) — don't
  bolt on the host platform's auth. Likewise, member-generated content (likes, reviews, submissions,
  "my …" lists) goes in a **Wix CMS collection** (`cms` vertical), not a host database.
  - **The split-brain trap:** the most common failure is mixing the two — e.g. storing "likes" in the
    host's DB while identifying the member from the Wix session (or logging in with Wix members but
    keying data on a host user id). The two identities never match, ownership filters silently return
    nothing (a like vanishes on refresh), and the data never sits with the rest of the Wix content.
  - **Rule of thumb:** one store and one identity per feature. For a Wix-backed feature that's the Wix
    member + the Wix collection, with ownership on Wix's server-stamped `_owner` (never a hand-stored
    or host-supplied member id). Using a host backend for genuinely host-only data is fine — just
    never straddle a single feature across both.
- **Never mock, never provision.** These scaffolds are read-only over the owner's content. The
  owner adds products/posts/services/events/menus/plans in the **Wix dashboard**. If a
  collection is empty, show the empty state — never fabricate data, reviews, ratings, or counts.
- **Purchases go through Wix.** Checkout/ticketing/plan purchase always complete via the Wix
  redirect-session / Wix-hosted form — **never hand-build a `/checkout` or purchase URL**.
- **Fail loudly.** The helpers throw on out-of-stock, empty carts, unbookable slots, expired
  holds, and payment-still-owed. A green path means it really worked — don't swallow the error.
- **Copy the shipped helpers as-is — don't rewrite their internals.** Wire your UI to the *exported*
  functions; don't "refactor" or reimplement the helper bodies. Several Wix request shapes are exact
  and easy to break (the members `createRedirectSession` body is the classic trap — a rewritten
  version returns 400 and login dies). Extend by *calling* the exports or adding a new
  `wixApiRequest` call for a genuine gap — never by editing the shipped ones.
- **Beyond the snippets, look it up — never guess.** The templates and the shipped
  `references/<vertical>/` helpers are the implementation — build from them first. When you hit a
  genuine gap (a field, an endpoint, or an error the snippets don't cover), extend the client with
  `wixApiRequest` — confirming the exact endpoint, method, and body first. **For that iteration and
  troubleshooting** — finding the right endpoint, reading a method's request/response schema, or
  diagnosing an API error — fall back to the **`wix-docs`** skill (`../wix-docs/SKILL.md` when
  co-installed): it covers `curl` doc-search, reading pages, and structured API-spec queries.
  Reference index: https://dev.wix.com/docs/api-reference.md
- **Provide the user with deep links to the Wix dashboard**: In many cases, the user will need to modify the default vertical data in the Wix dashboard. Always provide the user with these links. The relevant information for each vertical's links is in its `INSTRUCTIONS.md` file.

## How this skill is structured

`<SKILL_ROOT>` is this file's directory (strip `/SKILL.md`). Each vertical ships a **complete UI
client as files** under `references/<vertical>/app/` — `components/`, `pages/`,
`hooks/`/`context/`, and its REST helpers in `app/rest/` — plus the **shared transport** in
`references/shared/app/` (`app/rest/wix-client.js` + `wix-config.js`, identical for every vertical).
Set `WIX_CLIENT_ID` (and `WIX_METASITE_ID`) in `wix-config.js`. Deploying `references/<vertical>/app/`
and `references/shared/app/` into the app's `src/` puts every file in place — the helpers all land in
`src/rest/`, so their relative imports resolve.

**Where these files live in the app, and how they get there** (pre-installed at setup, or copied
in) **is your platform's call — follow your platform instructions for that.**

Each vertical's `INSTRUCTIONS.md` is the full playbook for that solution: when to use it,
prerequisites, the exported API, how to wire it, the hard rules, and a verification checklist.
**Open the relevant `INSTRUCTIONS.md` before wiring** — the shapes and gotchas live there.

## Routing — pick the vertical(s) from the request

Load the vertical(s) the user's app needs; a project may combine several (e.g. a restaurant
with a blog, or a store with pricing plans).

Each vertical's UI + helpers ship in `references/<vertical>/app/`; copy that dir plus
`references/shared/app/` into the app's `src/` (base44 does this at install via `deploy.cjs`).

| The user wants… | Vertical | Read |
|---|---|---|
| Online store: products, categories, cart, checkout | **storefront** | `references/storefront/INSTRUCTIONS.md` |
| Appointments: services, time slots, booking, checkout | **bookings** | `references/bookings/INSTRUCTIONS.md` |
| Blog/news: post feed, post pages, categories, tags | **blog** | `references/blog/INSTRUCTIONS.md` |
| Events: browse, event page, RSVP, ticketing | **events** | `references/events/INSTRUCTIONS.md` |
| Portfolio/showcase: collections, projects, media galleries | **portfolio** | `references/portfolio/INSTRUCTIONS.md` |
| Restaurant: menu, online ordering, table reservations | **restaurants** | `references/restaurants/INSTRUCTIONS.md` |
| CMS content: list/detail, filter/search, forms, data CRUD | **cms** | `references/cms/INSTRUCTIONS.md` |
| Plans & pricing: memberships/subscriptions, subscribe, my plans | **pricing-plans** | `references/pricing-plans/INSTRUCTIONS.md` |
| Member accounts: custom login/sign-up (email+password, Google/Facebook, SSO), account area, gated content | **members** | `references/members/INSTRUCTIONS.md` |

### When the request doesn't name a Wix Business Solution — ask, or check the site

Don't infer which Wix Business Solution to build (stores, bookings, blog, events, portfolio,
restaurants, CMS, pricing plans, members, etc..) from a vague brief. **Ask the user** one short
question — what do they offer (products? appointments? posts? events?) — or **check what the
site actually has**: call a cheap read from each likely solution's helper (`queryProducts`,
`queryServices`, `queryPosts`, `queryEvents`, …) — authenticated with a visitor token minted
from the `WIX_CLIENT_ID`, or with an admin token if you have one — and build for the solutions
that return real content. A `428` "app not installed" (blog: `401`) means
that solution isn't on the site; sample-looking content ("Sample product 3") proves the app is
installed, not what the business is about. Never default to store/bookings on silence.

## The run

1. **Get `WIX_CLIENT_ID`.** It comes from the user (the handoff prompt from their Wix/vibe
   platform carries it). If it's missing, ask for it before wiring — nothing works without it.
2. **Pick the vertical(s)** from the routing table — and when the request doesn't name any,
   **ask or check the site** (see above) instead of guessing. Open each picked vertical's
   `INSTRUCTIONS.md`.
3. **Ensure the vertical's files are in place** — copy `references/<vertical>/app/` and
   `references/shared/app/` into the app's `src/`, and set `WIX_CLIENT_ID` in `wix-config.js`. (Where
   and how they get there is your platform's call — see its instructions.)
4. **Wire the shipped client** following the vertical's INSTRUCTIONS: the components are themed by
   base44's design tokens (`src/index.css` — shadcn palette, already set by the design phase), so
   there's no re-skin step; just wire routes + header/footer through the Layout. The UI ships as
   files — you compose the home page and wire it, you don't rebuild the client.
5. **Verify** against the vertical's checklist before declaring done: token persists across
   reload, live data renders (or a real empty state), and purchases go through the Wix redirect.

> Some flows need Wix-side setup the user completes later (payments connected, the deployed
> domain allow-listed on the OAuth client for hosted-checkout return, collection permissions).
> Those are out of scope here — if a call fails for that reason, flag it and continue; don't
> fall back to mock data.

<!-- chapter:end slug=wix-vibe-headless -->
