| name | extract-and-understand |
| description | Extract classic SharePoint pages and build a CIM (Canonical Intermediate Model) for migration. |
| model | sonnet |
Phase 1: Extract & Understand
Extract a classic SharePoint page (wiki, web part page, or publishing page), classify each content block by intent, build a CIM (Canonical Intermediate Model), and save it.
After building and saving the CIM, immediately invoke the transform-and-create skill to proceed — unless invoked from migrate-site (which extracts all pages first, then migrates in Phase 2).
Core Rules
- Understand intent, not just tags. Classify by what the content is trying to do.
- Try your best. A creative approximation beats a bland fallback; use
build_any_webpart as the escape hatch.
- Be honest about limits. JavaScript-dependent content cannot run in modern pages. Preserve it as text with alternatives.
- Never silently drop content.
- Be autonomous. Ask only when you cannot proceed:
- the destination site URL is unknown for a publishing site migration where the Site Pages feature is not activated
- the content is genuinely ambiguous in a way that would materially change the result
- Do not ask about layout choices, web part type, skipping empty web parts, or page naming.
Workflow
Step 0: Extract & Build CIM
- Call
extract_classic_page(siteUrl, pageName).
- Build a CIM from the extracted bundle that captures:
- page title, type (
wiki / webpart / publishing), author, and layout
- content zones or wiki zones with their HTML
- web parts found (type, title, resolved content) and whether any content contains scripts
- for publishing pages: field controls from the layout ASPX, web part zones and column widths, and publishing field values such as images, byline, and dates
- if
publishingLayoutHtml is present, analyze it as hardcoded page-layout ASPX content shared by every page using that layout; identify headings, navigation tiles, link grids, images, CTAs, and similar template-level blocks
- the proposed migration plan for each content block / field / zone (text web part, image web part, Quick Links, and so on)
- cross-tenant asset detection: when the destination site is on a different tenant, call
discover_page_assets(siteUrl, pageName, destSiteUrl) and include the crossTenantAssets count and list in the CIM. Server-relative URLs (for example, /sites/pub1/images/photo.jpg) resolve only within the same tenant.
- Classify each content block by intent using CEWP Content Classification:
- consult the Web Part Mapping Reference table below for the modern equivalent
- inspect the actual HTML content to understand what the user is trying to accomplish
- consider the page as a whole (dashboard, wiki article, landing page, and so on)
- Present a brief CIM summary to the user (page type, section count, key decisions) as informational output, but do not wait for confirmation; proceed immediately to saving and handoff.
Step 1: Save CIM to File
- Save to
<git_repo_path>\pageunderstanding\<sitename>\<pagename>.json
<sitename>: derive from the site URL path and replace / with - (for example, /sites/pub1 → sites-pub1)
<pagename>: page name without .aspx
- Create
pageunderstanding/<sitename>/ if needed.
- Always overwrite existing files without reading or merging them.
CIM Schema
{
"schemaVersion": "1.0",
"extractedAt": "<ISO 8601>",
"source": { "siteUrl": "", "pageUrl": "", "pageName": "", "pageType": "wiki|webpart|publishing", "title": "" },
"metadata": { "author": {}, "created": "", "modified": "", "modifiedBy": "", "contentTypeId": "", "uniqueId": ""
...
...
...
...
...
Publishing fields in content.publishingFields use type discriminators:
| Type | Properties | Transform Action |
|---|
image | html, imageUrl, altText, imgWidth, imgHeight | build_image_webpart or build_text_webpart(html) without sourceUrl |
richHtml | html, plainText | build_text_webpart(html, sourceUrl) |
text | value | build_text_webpart(<p>{value}</p>) |
dateTime | value, isoDate | build_text_webpart(<p>{value}</p>) |
Web part modernMapping: For each web part in content.webParts, consult get_webpart_mapping_hints and add a modernMapping with the correct schema properties. See the webpart-mapping-reference skill for the authoritative List web part property schema and examples.
Zone-aware hints: When PublishingPageContent or WikiField contains embedded web parts, the transformationHints note must reference the concrete modernMapping strategy, not “render as placeholder”.
HTML table layout detection: When PublishingPageContent uses an outer <table> (typically class="ms-rteTable-*" or width="100%") for multi-column layout, the CIM transformationHints must capture that structure:
{
"transformationHints": {
"layoutStrategy": "html-table",
"tableLayout": {
"columnRatio": "oneThirdLeftColumn",
"rows": [
{ "leftContent": "hero image", "rightContent": "Getting Started through section X" },
{ "leftContent": "empty", "rightContent": "Section Y through Z" }
]
}
}
}
This ensures transform-and-create builds multi-column modern sections instead of flattening everything into oneColumn. Inspect the top-level table's <td> widths or proportions to determine the column ratio.
Image dimensions: For every image in contentBlocks or publishingFields, capture imgWidth and imgHeight from the extracted data. The classic extraction already provides naturalWidth/naturalHeight or explicit width/height attributes, and the Image web part needs these values to avoid collapsing to 0×0.
Web Part Mapping Reference
Sorted by usage. Tier: 1 = Direct, 2 = Conditional, 3 = Complex, 4 = No OOB (SPFx needed), 5 = Deprecated/Dropped.
| # | Classic Web Part | Modern Target | Builder Tool | Tier | Notes |
|---|
| 1 | XsltListViewWebPart (3.1B/mo) | List / Events | build_any_webpart | 2 | TaskList/DiscussionBoard/Survey → no OOB |
| 2 | ContentEditorWebPart (2.3B/mo) | See CEWP Classification | varies | 2 | AI classification required — 7-branch PnP selector |
| 3 | ScriptEditorWebPart (1.6B/mo) | Text (scripts can't run) | build_text_webpart | 4 | Add explanatory note about lost scripts |
| 4 | ClientSideWebPart (311M/mo) | SPFx passthrough | build_any_webpart | 1 | 100% fidelity — reuse original component ID |
| 5 | ContentBySearchWebPart (223M/mo) | Highlighted Content | build_any_webpart | 3 | Lost: display templates, query rules |
| 6 | ContentByQueryWebPart (192M/mo) | Highlighted Content | build_any_webpart | 3 | Lost: cross-site CAML, XSL |
| 7 | SummaryLinkWebPart (184M/mo) | Quick Links / Text | build_quick_links_webpart | 1 | ~90% fidelity |
| 8 | ResultScriptWebPart (158M/mo) | Highlighted Content | build_any_webpart | 3 | Lost: JS display templates |
| 9 | ClientWebPart (139M/mo) | Add-in passthrough | build_any_webpart | 1 | ~95% fidelity |
| 10 | RSSAggregatorWebPart (137M/mo) | Text (no modern RSS) | build_text_webpart | 4 | Add note: no OOB RSS web part |
| 11 | ListFormWebPart (125M/mo) | Modern list forms | skip | 2 | Handled by modern form infrastructure |
| 12 | ListViewWebPart (125M/mo) | List / Events |
CEWP Content Classification
ContentEditorWebPart (CEWP) contains arbitrary HTML. Classify by intent:
| Pattern | Signals | Action |
|---|
| Navigation / Link Lists | <ul> or <ol> where most <li> contain <a> tags | build_quick_links_webpart — extract link text and URLs |
| Embedded Content | <iframe>, <embed>, <object> tags | build_embed_webpart with the src URL. YouTube URLs auto-route to the YouTube web part. |
| Image Galleries / Hero Images | Multiple <img> tags, or one large image with overlay text | Single image → build_image_webpart; multiple images → build_any_webpart with Image Gallery; image with text overlay → Hero via build_any_webpart |
| Data Tables | <table> with <thead> and <tbody> | build_text_webpart — modern text web parts render tables well |
| Styled Banners / Announcements | Large text, colored backgrounds, call-to-action buttons | Hero or Call to Action via build_any_webpart, or build_text_webpart |
| JavaScript-Dependent Content | <script>, onclick, jQuery references, SP.js calls | build_text_webpart fallback whose complete explanatory note is wrapped in <span class="ms-rtebackcolor-3">...</span> |
Scripts cannot run in modern pages.
Content Pattern → Tool Quick Reference
| Content Pattern | Tool | Notes |
|---|
| Navigation link lists | build_quick_links_webpart | Pick layout by density |
| Pure text / formatted content | build_text_webpart | Preserves rich HTML |
| Standalone images (same site) | build_image_webpart | Supports alt text, captions, links |
| Images from another site (cross-site) | build_text_webpart | Pass raw <img> HTML — inline RTE images for cross-site |
| Embedded content / iframes | build_embed_webpart | Auto-routes YouTube, Documents |
| Section separators | build_divider_webpart | — |
| Any known modern web part type | build_any_webpart | Escape hatch for any type by GUID |
| Last resort / unknown | build_text_webpart | Fallback — preserve content as yellow-highlighted explanatory text |
Troubleshooting & Tools
- If
extract_classic_page fails, verify the site URL and page name.
- Publishing sites can host modern pages only when the Site Pages feature is activated (a Site Pages library exists). If activated, migrate in the same site; otherwise the user must specify a separate destination site.
- Permission errors mean the app needs at least
Sites.Read.All.
extract_page_data (used in compare-and-refine) only works on modern pages. It returns No content container found on classic wiki/publishing pages. Always use extract_classic_page for source extraction.
| Tool | Purpose |
|---|
extract_classic_page(siteUrl, pageName) | Extract classic page content (WikiField HTML, web parts, resolved CEWP content) |
get_modern_webpart_catalog(apiVersion?) | Discover available modern web parts with schemas |
discover_page_assets(siteUrl, pageName, destSiteUrl?) | Scan page assets and classify cross-tenant vs same-tenant |