| name | migrate-site |
| description | Orchestrate bulk classic-to-modern SharePoint page migration for an entire site. Supports plan, migrate, and refine modes. |
| model | sonnet |
Site Migration
Orchestrate end-to-end migration of all classic pages in a SharePoint site to modern pages. This skill discovers pages, extracts CIMs, migrates each page in parallel (up to 5 at a time), scores quality, and produces a final summary with refinement recommendations.
Be fully autonomous. Ask once after discovery when valid existing CIMs require a reuse decision. Otherwise, do not ask questions unless there is an actual blocker (for example, the destination site URL is unknown for a publishing site that does not have the Site Pages feature activated).
Hard Rule: Always Dispatch Subagents
Every extract-and-understand and transform-and-create invocation must run inside a dispatched subagent (Agent tool), with one subagent per page.
Model selection when dispatching subagents
Subagents do not inherit the orchestrator's model, so specify it explicitly for every per-page task:
- Claude Code: already specified in
SKILL.md
- GitHub Copilot CLI: use the fast model with
reasoning_effort: "medium" when calling the Task tool (for example, an economic/fast model such as gpt-luna or mini)
Workflow
Phase 1: Plan — Discover & Extract All Pages
- Discover pages with
list_site_pages(sourceSiteUrl):
- use
library: "both" to include both Site Pages and Pages
- use
includeModernPages: false to skip already-modern pages
- present the page list as a summary table (name, type, library)
- Determine the destination site. Same-site migration is preferred when possible, including publishing sites whose source site already has the Site Pages feature activated.
- Detect this by checking whether the source site exposes a Site Pages library via
list_site_pages(..., library: "both") or resolve_list_info(siteUrl, "Site Pages")
- If Site Pages resolves, use the same site as the destination
- Ask for a separate destination site URL only when the source is a publishing site and Site Pages is not activated
- Check for existing page-understanding JSON before dispatching extraction tasks. For each discovered page:
- derive
pageunderstanding/<sitename>/<page-name-without-aspx>.json
- treat a file as reusable only if it is valid JSON with
schemaVersion, source, and content, and source.siteUrl / source.pageName match the discovered page
- automatically reject invalid, unreadable, or source-mismatched files and report why they were rejected
- if any reusable CIMs exist, present a table with page name,
extractedAt, and migrationStatus, then ask the user once how to handle them:
- Reuse all existing CIMs and skip page understanding (recommended)
- Re-extract all pages that already have CIMs
- Choose which existing CIMs to reuse
- if the user chooses individual pages, ask one follow-up question for the page names; reuse only those and re-extract the rest
- if no reusable CIMs exist, do not ask anything and continue
- Extract only pages that are not being reused. For each classic page without a reusable/selected CIM:
- invoke
extract-and-understand
- extract and save the CIM only; do not auto-handoff to
transform-and-create
- Mark CIMs as planned. After each successful extraction, and for each existing CIM selected for reuse, update the file with:
"migrationStatus": "planned"
"plannedAt": "<ISO timestamp>"
- reuse skips only
extract-and-understand; the page still goes through transform, create/update, and comparison
- if extraction fails, set
"migrationStatus": "error", "error": "<message>", and "lastAttemptAt": "<ISO timestamp>", then continue
- Report the plan summary with totals, reused vs freshly extracted CIMs, and any extraction failures, then proceed directly to Phase 2.
Phase 2: Migrate & Score — Parallel Page Migration
Process all planned pages in parallel, up to 5 concurrent tasks. Each task handles one page end-to-end.
Per-page task
- Invoke
transform-and-create. Read the CIM and create or update the modern page. Do not ask the user about layout, web part choices, or page naming.
- Update the CIM on create/update success with:
"migrationStatus": "migrated"
"migratedAt": "<ISO timestamp>"
"modernPageId": "<page ID from Graph API>"
"modernPageUrl": "<page web URL>"
"destinationSiteUrl": "<destination site URL>"
- Invoke
compare-and-refine in compare-only mode.
- build cleaned classic comparison data from the Phase 1 extraction bundle; do not structurally extract the live classic page
- extract the modern page, run
compare_migration_quality, and apply the compare-and-refine score sanity gate
- if a score below 50 conflicts with strong text or heading coverage, re-extract once before deciding
- do not iterate or fix issues in this phase
- if the comparison remains contradictory, record
comparisonScore: null and comparisonConfidence: "low" with an inconclusive summary instead of a misleading zero
- update the CIM with
comparisonScore, comparisonConfidence, comparisonSummary, and comparedAt
- On failure, set
"migrationStatus": "error", "error": "<error message>", and "lastAttemptAt": "<ISO timestamp>". The failed page stops; other pages continue.
Parallelism
- Use the Task tool to keep up to 5 page tasks in flight
- Set the model explicitly for every dispatched task as described above
- Start the next pending page whenever a slot frees up
- Wait for all tasks before Phase 3
- A Phase 1 task normally takes 1–2 minutes and a Phase 2 task usually takes under 5 minutes; if Phase 1 exceeds 5 minutes or Phase 2 exceeds 10 minutes, check it proactively
Phase 3: Summary Report
After all pages finish, call get_comparison_summary with the page-understanding site directory path, then report:
- a migration summary table with page, type, status, score, and issues
- totals for discovered pages, successfully migrated pages, pages with errors, and the average comparison score
- refinement recommendations for every migrated page below 95% or with notable missing content
CIM Status Fields
Add or maintain these top-level fields in each CIM JSON file:
| Field | Type | Description |
|---|
migrationStatus | "planned" | "migrated" | "error" | Current migration state |
plannedAt | ISO 8601 string | When the CIM was created/planned |
migratedAt | ISO 8601 string | When the modern page was created |
modernPageId | string | Graph API page ID |
modernPageUrl | string | Modern page web URL |
destinationSiteUrl | string | Site URL where the modern page was created |
comparisonScore | number (0–100) or null | Structural comparison score |
comparisonConfidence | "high" | "low" | Confidence in the comparison result |
comparisonSummary | string | Brief summary of comparison findings |
comparedAt | ISO 8601 string | When comparison ran |
error | string | Error from the last failed attempt |
lastAttemptAt | ISO 8601 string | When the last failed attempt occurred |
Autonomy Rules
Do not ask the user about:
- layout choices
- which web part type to use
- whether to skip empty/placeholder web parts
- page naming (
same name cross-site, -migrated same-site)
- page conflicts (update existing pages automatically)
- whether to proceed after planning
Only these confirmations are allowed:
- the destination site URL is unknown for a publishing site migration where the Site Pages feature is not activated
- an authentication failure cannot be retried
- the single grouped decision about reusing valid existing page-understanding JSON files from Phase 1 Step 3
MCP Tool Reference
| Tool | Purpose |
|---|
list_site_pages(siteUrl, library?, includeModernPages?) | Discover all pages in a site |
extract_classic_page(siteUrl, pageName) | Extract a single classic page (used via extract-and-understand) |
find_modern_page(siteUrl, pageName) | Check whether a modern page already exists |
get_comparison_summary(directory) | Load all comparison scores and summaries from a page-understanding directory |
Skills Invoked
Each subagent should load only its own skill.
| Skill | Phase | Where it runs | Notes |
|---|
extract-and-understand | Phase 1 | Inside a per-page subagent | Extract + CIM only, no handoff to transform |
transform-and-create | Phase 2 | Inside a per-page subagent | Reads CIM, creates modern page |
compare-and-refine | Phase 2 | Inside the same per-page subagent after transform | Compare only, no refinement |