notion-plan
/notion-plan — 從 Notion URL 自動建立實作計畫
貼上 Notion URL,自動擷取頁面需求內容並串接 /design 建立實作計畫。一條指令完成 Notion 抓取 → 結構化整理 → plans/active/.md 產出。
Step 1:解析 URL
從使用者提供的引數中取得 Notion URL。支援以下格式:
| 格式 | 範例 |
|---|---|
| notion.so 頁面 | https://www.notion.so/workspace/Page-Title-abc123 |
| notion.site 公開頁面 | https://workspace.notion.site/Page-Title-abc123 |
| notion.so 資料庫 | https://www.notion.so/workspace/abc123?v=def456 |
| 短網址 | https://notion.so/abc123 |
從 URL 提取 pageId(最後的 32 字元 hex,可能以 - 分隔或連續)。
若 $ARGUMENTS 為空,使用 AskUserQuestion 請使用者提供 Notion URL。
若 URL 不符合 Notion 格式,提示使用者確認。
Step 2:Playwright MCP 抓取
不使用 WebFetch:Notion 100% client-side rendering,WebFetch 永遠只能拿到載入骨架,直接用 Playwright。
2a. 導航至頁面
mcp__playwright__browser_navigate(url=<notion_url>)
2b. 等待內容載入
mcp__playwright__browser_wait_for(selector=".notion-page-content", timeout=10000)
若等待逾時,嘗試備用 selector:
.notion-frame[data-block-id].layout-content
2c. 如需登入
使用 browser_snapshot 檢查頁面狀態。判斷是否為登入頁面:
- snapshot 不含任何
[data-block-id]區塊 - 且包含
input[type="email"]或文字 "Sign in"、"Log in"、"Continue with"
若確認為登入頁面,使用 AskUserQuestion 告知使用者:
此頁面需要登入。請在瀏覽器中登入 Notion 後重試, 或將頁面設為公開(Share → Publish to web)。
若使用者的 Playwright 已有 Notion session(persistent profile),則直接繼續。
2d. 擷取頁面內容
先展開所有 Toggle 區塊(Notion toggle 預設收合,子內容不在 DOM 中):
() => {
const toggles = document.querySelectorAll('.notion-toggle-block > div[role="button"]');
toggles.forEach(t => {
if (t.getAttribute('aria-expanded') !== 'true') t.click();
});
return toggles.length;
}
若有 toggle 被展開,等待 1 秒讓子內容載入。
接著使用 browser_snapshot 取得 accessibility tree:
mcp__playwright__browser_snapshot()
若內容過長或需要更精確擷取,使用 browser_evaluate 執行 DOM 提取:
() => {
// 擷取頁面標題
const title = document.querySelector('.notion-page-block, .notion-collection_view-block h1, [data-root="true"] h1')?.textContent?.trim() || '';
// 只擷取 page content 容器下的頂層區塊,避免嵌套重複
const pageContent = document.querySelector('.notion-page-content');
const topLevelBlocks = pageContent
? Array.from(pageContent.children).filter(el => el.dataset.blockId)
: Array.from(document.querySelectorAll('[data-block-id]'));
const content = [];
topLevelBlocks.forEach(block => {
const text = block.innerText?.trim();
if (text && text.length > 0) {
content.push(text);
}
});
return { title, content: content.join('\n\n') };
}
注意: 使用
pageContent.children(直接子元素)而非querySelectorAll('[data-block-id]')(所有後代), 避免嵌套區塊(如 toggle 內的段落)被重複擷取。
2e. 處理長頁面
若頁面內容需要捲動才能載入完全:
() => {
return new Promise(resolve => {
let lastHeight = document.body.scrollHeight;
const distance = 500;
const timer = setInterval(() => {
window.scrollBy(0, distance);
const newHeight = document.body.scrollHeight;
// 若 scrollHeight 不再增長,表示已載入完畢
if (newHeight === lastHeight) {
clearInterval(timer);
resolve(true);
}
lastHeight = newHeight;
}, 300);
// 安全上限:最多捲動 30 秒
setTimeout(() => { clearInterval(timer); resolve(true); }, 30000);
});
}
使用
scrollHeight穩定性檢查(連續兩次高度不變即停止),而非累計距離比較, 避免 lazy-load 不斷擴展頁面導致無限捲動。
捲動完成後捲回頂部,重新擷取內容。
Step 3:整理為結構化 Markdown
將擷取到的原始內容轉為結構化 Markdown:
3a. 內容清理
- 移除 Notion UI 元素(側邊欄、工具列、分享按鈕等的殘留文字)
- 保留語意結構(標題層級、清單、程式碼區塊、表格)
- 移除重複的空行
- 保留圖片 alt text(若有)
3b. 輸出格式
# [頁面標題]
> 來源:[Notion URL]
> 擷取時間:[YYYY-MM-DD HH:MM]
---
[頁面內容,保留原始 Markdown 結構]
3c. 特殊內容處理
| 內容類型 | 處理方式 |
|---|---|
| 表格 / Database | 轉為 Markdown table |
| Toggle(摺疊區塊) | 展開內容,以 <details> 或縮排呈現 |
| Callout | 以 blockquote > 呈現 |
| 程式碼區塊 | 保留 ``` 格式和語言標記 |
| 嵌入(embed) | 保留 URL,標記為 [Embedded: URL] |
| 圖片 |  或 [Image: description] |
Step 4:內容品質確認
檢查整理後的 Markdown 是否包含有效需求內容:
- ✅ 有實質內容(標題 + 至少一段文字或清單)→ 進入 Step 5
- ❌ 內容為空、僅有標題、或明顯不完整 → 使用 AskUserQuestion 詢問使用者:
擷取到的內容似乎不完整。請確認:
- 頁面是否需要登入?
- 是否要手動貼上需求內容?
Step 5:輸出 Notion 內容並觸發 /design
5a. 在對話中輸出完整 Markdown
將 Step 3 整理好的結構化 Markdown 完整輸出至對話中,供後續 /design 讀取。
5b. 觸發 /design 建立實作計畫
Skill(skill="design", args="[SOURCE: /notion-plan] 根據上方 Notion 頁面的需求內容建立實作計畫")
注意:
[SOURCE: /notion-plan]為 traceability 標記,僅標示需求來源。/design不做任何 skip 處理(與/update → /pr的[PIPELINE]語意不同), 而是從對話脈絡中讀取 Notion 內容,零修改/design。
使用方式
/notion-plan https://www.notion.so/workspace/Page-Title-abc123
/notion-plan https://workspace.notion.site/public-page-abc123
限制
- 私人頁面需要 Playwright 有 Notion session 或使用者手動登入
- Database view 的篩選/排序/分組以頁面當前狀態為準
- 非常長的頁面(100+ 區塊)可能需要多次捲動,擷取時間較長