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_classic_page retries a failed extraction two times before returning an error (three total attempts); invoke it once and set the CIM status to "error" only after the tool exhausts its retries
- 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.
create_modern_page retries a failed SavePage operation two times before reporting failure; invoke it once and do not repeat the create request after an ambiguous response
- Verify the newly created or updated modern page before recording success.
- For a create, retain the page ID and URL returned by the operation. For an update, retain the page ID supplied to the operation and use the post-update lookup URL. Never invent them or derive them from the intended file name.
- Call
find_modern_page(destinationSiteUrl, targetPageName). It must return found: true, a nonempty URL, and a page ID equal to the create result or update input (compare IDs as strings).
- Call
extract_page_data on the verified lookup URL after rendering. It must return a modern page without an extraction error.
- If any verification step fails, set
migrationStatus to "error" with the exact failure and lastAttemptAt; do not invoke comparison and do not persist migration or score fields.
- Update the CIM only after live-page verification 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.
- score the original classic page against the verified, newly created modern page--never one CIM against another CIM and never a CIM against a guessed destination URL
- build cleaned classic comparison data from the original Phase 1 extraction bundle; do not structurally extract the live classic page
- use the fresh verified modern-page extraction, capture screenshots of the live classic and verified modern pages, run
compare_migration_quality with its screenshot-based visual assessment, and apply the compare-and-refine score sanity gate
- if comparison/scoring returns an error, an unparseable report, or no finite score after its internal retries, rebuild normalized classic data, refresh the verified modern extraction and both screenshots, then retry the complete comparison once
- if a score below 50 conflicts with strong text or heading coverage, re-extract once before deciding
- if the comparison retry fails or the comparison remains contradictory, record
comparisonScore: null and comparisonConfidence: "low" with an inconclusive summary instead of a misleading zero; retain migrationStatus: "migrated" when live-page verification succeeded
- persist the initial result to the CIM with
comparisonScore, comparisonConfidence, comparisonSummary, and comparedAt
- Automatic refinement gate: when the persisted initial
comparisonScore is finite and below 80, immediately invoke compare-and-refine in refinement mode inside the same per-page task. Do not ask the user to approve this follow-up.
- Refine the existing verified page only. Use its authoritative page ID with
update_modern_page; never create a duplicate page.
- Preserve all source content. Unsupported or script-dependent behavior must remain a yellow-highlighted explanatory fallback with a supported modern alternative.
- After every update, repeat live lookup, modern extraction, screenshots, and comparison before persisting the new result.
- Continue while the score remains below
80 and the report identifies a concrete, supported remediation. Stop when the score reaches 80 or above, or when no further supported remediation can improve the result. Persist the final verified comparison fields to the CIM.
- Do not automatically refine a score of
80 or higher. A null or low-confidence score remains inconclusive and must not trigger automatic refinement.
- 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 first; automatically refine only when the initial verified score is below 80 |
1---2name: migrate-site3description: Orchestrate bulk classic-to-modern SharePoint page migration for an entire site. Supports plan, migrate, and refine modes.4---56# Site Migration78Orchestrate 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.910**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).1112---1314## Hard Rule: Always Dispatch Subagents1516Every `extract-and-understand` and `transform-and-create` invocation must run inside a dispatched subagent (`Agent` tool), with one subagent per page.1718### Model selection when dispatching subagents1920Subagents do **not** inherit the orchestrator's model, so specify it explicitly for every per-page task:21- **Claude Code:** already specified in `SKILL.md`22- **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)2324---2526## Workflow2728### Phase 1: Plan — Discover & Extract All Pages29301. **Discover pages** with `list_site_pages(sourceSiteUrl)`:31 - use `library: "both"` to include both Site Pages and Pages32 - use `includeModernPages: false` to skip already-modern pages33 - present the page list as a summary table (name, type, library)342. **Determine the destination site.** Same-site migration is preferred when possible, including publishing sites whose source site already has the **Site Pages** feature activated.35 - 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")`36 - If Site Pages resolves, use the **same site** as the destination37 - Ask for a separate destination site URL only when the source is a publishing site **and** Site Pages is not activated383. **Check for existing page-understanding JSON before dispatching extraction tasks.** For each discovered page:39 - derive `pageunderstanding/<sitename>/<page-name-without-aspx>.json`40 - treat a file as reusable only if it is valid JSON with `schemaVersion`, `source`, and `content`, and `source.siteUrl` / `source.pageName` match the discovered page41 - automatically reject invalid, unreadable, or source-mismatched files and report why they were rejected42 - if any reusable CIMs exist, present a table with page name, `extractedAt`, and `migrationStatus`, then ask the user **once** how to handle them:43 1. **Reuse all existing CIMs and skip page understanding (recommended)**44 2. **Re-extract all pages that already have CIMs**45 3. **Choose which existing CIMs to reuse**46 - if the user chooses individual pages, ask one follow-up question for the page names; reuse only those and re-extract the rest47 - if no reusable CIMs exist, do not ask anything and continue484. **Extract only pages that are not being reused.** For each classic page without a reusable/selected CIM:49 - invoke `extract-and-understand`50 - `extract_classic_page` retries a failed extraction two times before returning an error (three total attempts); invoke it once and set the CIM status to `"error"` only after the tool exhausts its retries51 - extract and save the CIM only; do **not** auto-handoff to `transform-and-create`525. **Mark CIMs as planned.** After each successful extraction, and for each existing CIM selected for reuse, update the file with:53 - `"migrationStatus": "planned"`54 - `"plannedAt": "<ISO timestamp>"`55 - reuse skips only `extract-and-understand`; the page still goes through transform, create/update, and comparison56 - if extraction fails, set `"migrationStatus": "error"`, `"error": "<message>"`, and `"lastAttemptAt": "<ISO timestamp>"`, then continue576. **Report the plan summary** with totals, reused vs freshly extracted CIMs, and any extraction failures, then proceed directly to Phase 2.5859### Phase 2: Migrate & Score — Parallel Page Migration6061Process all planned pages in parallel, up to **5 concurrent tasks**. Each task handles one page end-to-end.6263#### Per-page task64651. **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.66 - `create_modern_page` retries a failed `SavePage` operation two times before reporting failure; invoke it once and do not repeat the create request after an ambiguous response672. **Verify the newly created or updated modern page before recording success.**68 - For a create, retain the page ID and URL returned by the operation. For an update, retain the page ID supplied to the operation and use the post-update lookup URL. Never invent them or derive them from the intended file name.69 - Call `find_modern_page(destinationSiteUrl, targetPageName)`. It must return `found: true`, a nonempty URL, and a page ID equal to the create result or update input (compare IDs as strings).70 - Call `extract_page_data` on the verified lookup URL after rendering. It must return a modern page without an extraction error.71 - If any verification step fails, set `migrationStatus` to `"error"` with the exact failure and `lastAttemptAt`; do not invoke comparison and do not persist migration or score fields.723. **Update the CIM only after live-page verification** with:73 - `"migrationStatus": "migrated"`74 - `"migratedAt": "<ISO timestamp>"`75 - `"modernPageId": "<page ID from Graph API>"`76 - `"modernPageUrl": "<page web URL>"`77 - `"destinationSiteUrl": "<destination site URL>"`784. **Invoke `compare-and-refine` in compare-only mode.**79 - score the original classic page against the verified, newly created modern page--never one CIM against another CIM and never a CIM against a guessed destination URL80 - build cleaned classic comparison data from the original Phase 1 extraction bundle; do **not** structurally extract the live classic page81 - use the fresh verified modern-page extraction, capture screenshots of the live classic and verified modern pages, run `compare_migration_quality` with its screenshot-based visual assessment, and apply the `compare-and-refine` score sanity gate82 - if comparison/scoring returns an error, an unparseable report, or no finite score after its internal retries, rebuild normalized classic data, refresh the verified modern extraction and both screenshots, then retry the complete comparison once83 - if a score below 50 conflicts with strong text or heading coverage, re-extract once before deciding84 - if the comparison retry fails or the comparison remains contradictory, record `comparisonScore: null` and `comparisonConfidence: "low"` with an inconclusive summary instead of a misleading zero; retain `migrationStatus: "migrated"` when live-page verification succeeded85 - persist the initial result to the CIM with `comparisonScore`, `comparisonConfidence`, `comparisonSummary`, and `comparedAt`86 - **Automatic refinement gate:** when the persisted initial `comparisonScore` is finite and below `80`, immediately invoke `compare-and-refine` in refinement mode inside the same per-page task. Do not ask the user to approve this follow-up.87 - Refine the existing verified page only. Use its authoritative page ID with `update_modern_page`; never create a duplicate page.88 - Preserve all source content. Unsupported or script-dependent behavior must remain a yellow-highlighted explanatory fallback with a supported modern alternative.89 - After every update, repeat live lookup, modern extraction, screenshots, and comparison before persisting the new result.90 - Continue while the score remains below `80` and the report identifies a concrete, supported remediation. Stop when the score reaches `80` or above, or when no further supported remediation can improve the result. Persist the final verified comparison fields to the CIM.91 - Do not automatically refine a score of `80` or higher. A null or low-confidence score remains inconclusive and must not trigger automatic refinement.925. **On failure,** set `"migrationStatus": "error"`, `"error": "<error message>"`, and `"lastAttemptAt": "<ISO timestamp>"`. The failed page stops; other pages continue.9394#### Parallelism9596- Use the Task tool to keep up to **5** page tasks in flight97- Set the model explicitly for every dispatched task as described above98- Start the next pending page whenever a slot frees up99- Wait for all tasks before Phase 3100- 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 proactively101102### Phase 3: Summary Report103104After all pages finish, call `get_comparison_summary` with the page-understanding site directory path, then report:105- a migration summary table with page, type, status, score, and issues106- totals for discovered pages, successfully migrated pages, pages with errors, and the average comparison score107- refinement recommendations for every migrated page below 95% or with notable missing content108109---110111## CIM Status Fields112113Add or maintain these top-level fields in each CIM JSON file:114115| Field | Type | Description |116|-------|------|-------------|117| `migrationStatus` | `"planned" \| "migrated" \| "error"` | Current migration state |118| `plannedAt` | ISO 8601 string | When the CIM was created/planned |119| `migratedAt` | ISO 8601 string | When the modern page was created |120| `modernPageId` | string | Graph API page ID |121| `modernPageUrl` | string | Modern page web URL |122| `destinationSiteUrl` | string | Site URL where the modern page was created |123| `comparisonScore` | number (0–100) or `null` | Structural comparison score |124| `comparisonConfidence` | `"high" \| "low"` | Confidence in the comparison result |125| `comparisonSummary` | string | Brief summary of comparison findings |126| `comparedAt` | ISO 8601 string | When comparison ran |127| `error` | string | Error from the last failed attempt |128| `lastAttemptAt` | ISO 8601 string | When the last failed attempt occurred |129130---131132## Autonomy Rules133134Do **not** ask the user about:135- layout choices136- which web part type to use137- whether to skip empty/placeholder web parts138- page naming (`same name` cross-site, `-migrated` same-site)139- page conflicts (update existing pages automatically)140- whether to proceed after planning141142Only these confirmations are allowed:143- the destination site URL is unknown for a publishing site migration where the Site Pages feature is not activated144- an authentication failure cannot be retried145- the single grouped decision about reusing valid existing page-understanding JSON files from Phase 1 Step 3146147---148149## MCP Tool Reference150151| Tool | Purpose |152|------|---------|153| `list_site_pages(siteUrl, library?, includeModernPages?)` | Discover all pages in a site |154| `extract_classic_page(siteUrl, pageName)` | Extract a single classic page (used via `extract-and-understand`) |155| `find_modern_page(siteUrl, pageName)` | Check whether a modern page already exists |156| `get_comparison_summary(directory)` | Load all comparison scores and summaries from a page-understanding directory |157158### Skills Invoked159160Each subagent should load **only its own skill**.161162| Skill | Phase | Where it runs | Notes |163|-------|-------|---------------|-------|164| `extract-and-understand` | Phase 1 | Inside a per-page subagent | Extract + CIM only, no handoff to transform |165| `transform-and-create` | Phase 2 | Inside a per-page subagent | Reads CIM, creates modern page |166| `compare-and-refine` | Phase 2 | Inside the **same** per-page subagent after transform | Compare first; automatically refine only when the initial verified score is below 80 |