git:20260609.fb3d951 to git:20260616.7bfe19e

100 added, 38 removed. Audit A to A.

---
name: content-governance
sheetId: "1.12"
- description: Sync a newsletter article from Google Docs to the Notion Content DB. Use this skill whenever the user shares a Google Doc URL and wants to create a Notion page, sync to Notion, add to Content DB, or archive a newsletter. Trigger on phrases like "幫我放上 Notion", "sync to Notion", "add this to Notion", "同步到 Notion", or any message combining a Google Doc link with Notion/archive intent.
+ description: Put a newsletter in its proper place — reconcile it between Google Docs and the Notion Content DB in EITHER direction (Doc-first OR Notion-first), and file the Doc into the newsletter Drive folder. The goal is that the content ends up correctly placed and cross-linked regardless of which was created first. Use whenever the user shares a Google Doc and/or a Notion Content-DB page and wants them synced, filed, archived, or "put in the right place". Triggers on "幫我放上 Notion", "sync to Notion", "同步到 Notion", "add this to Notion", "幫我歸檔電子報", "file this newsletter", "put this in the right place", "從 Notion 的想法建 Doc", or any message pairing a Doc and/or a Notion page with sync / file / archive intent.
category: brand-marketing
project: content-governance
platform: claude
status: Done
author: Peter Tu
- input: "Google Doc URL for a newsletter article"
- process: "Fetch doc content, classify Subject tags, confirm with user, create Notion page in Content DB"
- output: "Notion page URL in the Content DB"
+ input: "A Google Doc URL and/or a Notion Content-DB page (idea/stub) for a newsletter — in either order"
+ process: "Detect entry point (Doc-first / Notion-first / both), create whichever anchor is missing, file the Doc into the newsletter Drive folder, assign the issue number, classify Subject tags, and sync full metadata + body into a single deduped Notion Content-DB page — cross-linked"
+ output: "A filed Google Doc (newsletter folder, (N) Title) + a deduped Notion Content-DB page with full metadata, cross-linked"
synergy: []
---
- # Newsletter → Notion Sync
+ # Content Governance — Doc ⇄ Notion reconciler
- Syncs a newsletter from Google Docs to Peter's Notion Content DB with auto-classified Subject metadata.
+ This skill makes one guarantee: **a newsletter ends up in its proper place regardless of where its life cycle started.** Peter sometimes drafts the Google Doc first, sometimes captures the idea in Notion first. Either way, this skill reconciles the two into a single consistent, cross-linked record.
## Hardcoded context
- - **Google account**: `<your-google-workspace-account>`
+ - **Google account**: `peter_tu@zynkr.ai`
- **Notion Content DB data source**: `collection://64bd3535-0cb3-4f96-946c-c2240294307c`
- - **Subject taxonomy file**: `<your-subject-taxonomy-csv>`
+ - **Newsletter Drive folder**: `[@] 電子報 (Newsletter)` — ID `1RvIRPNg4Kzp_B-9VFZRTXMX4tRgXpSlN`
+ - **Subject taxonomy file**: `/Users/petertu/Desktop/Claude/zynkr/1.0 brand-marketing/subject-taxonomy.csv`
- ## Steps
+ ## The end-state this skill guarantees (the invariant)
- ### 1. Fetch Google Doc content
+ When this skill finishes, all of the following are true:
- Use `mcp__google-workspace__get_doc_content` with:
- - `user_google_email`: `<your-google-workspace-account>`
- - `document_id`: extracted from the URL the user provided
+ 1. A **Google Doc** exists, named `(N) Title` (recent issues use `(N) 電子報|Title`), living **inside the newsletter folder**.
+ 2. A **Notion Content DB page** exists with: `Article name` (clean title), `No.` = N, `Content type` = `["Article"]`, `Subject` (3–5 tags incl. Newsletter), `Published channel` = `["Newsletter"]`, `Google Doc` = the Doc URL, `Status` (lifecycle-appropriate), `Last Synced` = today, `Author` = `Peter Tu`, and the article **body synced into the page content**.
+ 3. The two are **cross-linked and consistent** (same title + issue number). Exactly **one** Notion row — never a duplicate.
- Extract:
- - **Doc title** → use as `Article name` in Notion
- - **Full body text** → used for subject classification
+ If part of this is already true, only do the missing parts (the skill is idempotent — safe to re-run).
- ### 2. Read the subject taxonomy
+ ---
- Read the CSV at `<your-subject-taxonomy-csv>`.
+ ## Step 0 — Detect the entry point
- Columns: `subject`, `description_zh`, `keywords_zh`, `keywords_en`
+ Look at what the user gave you and branch:
- Match the article body against `keywords_zh` and `keywords_en` for each row. Select the **3–5 most relevant** Subject tags. `Newsletter` should always be included for newsletter content.
+ | What the user provides | Path |
+ |---|---|
+ | A **Google Doc URL** only | **§A Doc-first** |
+ | A **Notion page URL** (a Content-DB page / idea / stub) only | **§B Notion-first** |
+ | **Both** a Doc and a Notion page | **§C Reconcile** |
+ | **Neither** (just a topic / pasted text) | Ask: "你已經有 Google Doc 或 Notion 頁面了嗎?還是要我從這個想法開始建立?" If starting fresh, treat as **§B** (create the Notion page, then the Doc). |
- ### 3. Confirm Subject tags with user
+ Always run the **dedup check (§D.1)** before creating any new Notion page.
- Before creating the page, show the selected tags and a one-line reason for each. Example:
+ ---
- > 建議 Subject:Newsletter、Gen AI、Automation、Content Writing
- > - **Newsletter** — 電子報本身
- > - **Gen AI** — 文章核心是 Claude Code 使用心得
- > - **Automation** — 討論到 pipeline 與 Agent 流程
- > - **Content Writing** — 分析 Threads 貼文曝光表現
+ ## §A — Doc-first
- Let the user approve or adjust before proceeding.
+ 1. Fetch the Doc with `mcp__google-workspace__get_doc_content` → body + filename. Get its current folder with `mcp__claude_ai_Google_Drive__get_file_metadata` (read `parentId`).
+ 2. **File it (§D.2)** — ensure the filename is `(N) Title` and it lives in the newsletter folder. If the name has no `(N)`, assign the next issue number (§D.3).
+ 3. **Dedup-check Notion (§D.1).** Found → update that page; not found → create one.
+ 4. **Classify Subject tags (§D.4)** and confirm.
+ 5. **Write Notion metadata + body (§D.5)**; set `Status` per §D.6.
+ 6. **Cross-link** — `Google Doc` = the Doc URL.
- ### 4. Create the Notion page
+ ## §B — Notion-first
- Use `mcp__claude_ai_Notion__notion-create-pages` with:
- - `parent`: `{ "type": "data_source_id", "data_source_id": "64bd3535-0cb3-4f96-946c-c2240294307c" }`
+ 1. Fetch the Notion page with `mcp__claude_ai_Notion__notion-fetch` (content + properties).
+ - **Stub fix**: if the whole article is crammed into the `Article name` title and the page body is blank (a capture anti-pattern), first extract a clean title and move the body into the page content (`replace_content`). If there is no real title yet, propose 5 in Peter's style (see §D.7) and let him pick.
+ 2. **Establish the Doc:**
+ - If the page's `Google Doc` field already holds a URL → that's the Doc; jump to §D.2 to confirm it's filed + named.
+ - If missing → assign the issue number (§D.3), **create** a Google Doc in the newsletter folder named `(N) Title` with the article body (`mcp__google-workspace__create_doc` with `parent_folder_id` if available, else create then file via §D.2), then set the Notion `Google Doc` field to the new URL.
+ 3. **Classify Subject tags (§D.4)** and confirm.
+ 4. **Enrich Notion metadata (§D.5)**; set `Status` per §D.6.
- Properties:
+ ## §C — Reconcile (both already exist)
+ 1. Decide the **body source of truth** — default the Google Doc; if Doc and Notion body diverge, ask which wins.
+ 2. **File the Doc (§D.2)**, **enrich the Notion page (§D.5)**, **cross-link**, and make the **title + issue number consistent** across both.
+
+ ---
+
+ ## §D — Shared routines
+
+ ### D.1 Dedup check
+ Before creating a Notion page, search the Content DB with `mcp__claude_ai_Notion__notion-search` (`data_source_url`: the Content DB collection) by **issue number** and by **title**. If a match exists, **update that page** — never create a second row. (We previously created stub pages from raw ideas; this is what catches them.)
+
+ ### D.2 File the Google Doc
+ - Target folder: `1RvIRPNg4Kzp_B-9VFZRTXMX4tRgXpSlN`.
+ - Naming: `(N) Title`. The two most recent issues use a `(N) 電子報|Title` form — match that. Keep the **clean Title**.
+ - Move + rename in one call: `mcp__google-workspace__update_drive_file` with `name`, `add_parents` = newsletter folder, `remove_parents` = current `parentId`. Skip the move if it's already in the folder.
+
+ ### D.3 Assign the issue number
+ - `mcp__google-workspace__list_drive_items` on the newsletter folder (`file_type: document`), parse the leading `(N)` from each filename, take **max + 1**.
+ - **Confirm with the user** — show the proposed number and the latest existing issue. Issue numbers are sequential and human-visible, so never guess silently.
+
+ ### D.4 Classify Subject tags
+ - Read the taxonomy CSV. Match the body against `keywords_zh` + `keywords_en` per row. Select the **3–5 most relevant**; **`Newsletter` is always included**.
+ - Show the tags with a one-line reason each and **wait for approval/adjustment** before writing.
+
+ ### D.5 Notion property mapping
+ If unsure the schema is current, fetch the data source first (`notion-fetch` on the collection) — it has all of these properties:
+
| Field | Value |
|-------|-------|
- | `Article name` | Doc title (string) |
+ | `Article name` | Clean title — **no `(N)`, no `電子報|` prefix** (the `No.` field carries the number) |
+ | `No.` | Issue number (number) |
| `Content type` | `["Article"]` |
- | `Subject` | JSON array of confirmed tags, e.g. `["Newsletter", "Gen AI"]` |
+ | `Subject` | Confirmed tags, JSON array e.g. `["Newsletter", "Gen AI"]` |
| `Published channel` | `["Newsletter"]` |
- | `Google Doc` | Full Google Doc URL as provided by the user |
- | `Status` | `Draft` |
+ | `Google Doc` | Full Doc URL |
+ | `Status` | Per §D.6 |
+ | `date:Last Synced:start` | Today, ISO `YYYY-MM-DD` (+ `date:Last Synced:is_datetime` = 0) |
+ | `Author` | `Peter Tu` |
- ### 5. Confirm success
+ When creating a page or refreshing it from the Doc, also sync the **article body into the page content** (`replace_content`). Use `notion-create-pages` (parent `data_source_id`) for a new page, or `notion-update-page` for an existing one.
- Return the Notion page URL to the user.
+ ### D.6 Status — lifecycle-aware (do NOT hardcode)
+ Set `Status` to match where the piece actually is, asking if unclear:
+ - **Draft** — written but not sent → the default for a fresh draft.
+ - **Ready** — finalized and queued to send.
+ - **Published** — actually sent / published. Only then is it correct for `Schedule publish date` to be a real send date (a Content-DB automation stamps it when Status flips to Published).
+
+ Never default an unsent draft to Published.
+
+ ### D.7 Title generation (only if missing)
+ If a Notion stub has no real title, read the body and propose **5 options** in Peter's newsletter style (punchy, question- or insight-based, zh-TW). Reference:
+ - 為什麼真正厲害的人都在花錢買經驗,而不是只買知識
+ - 99% 的創作者,都撐不到寫作開始回報的那一天
+ - 做最多、扛最重,卻最容易被忽略:能者過勞不是你的錯
+
+ Wait for Peter to pick or supply his own.
+
+ ---
+
+ ## Step final — Confirm
+
+ Report both anchors so Peter can see the content landed in its proper place:
+ - **Notion page URL** (in the Content DB)
+ - **Filed Google Doc URL** (in the newsletter folder)
+ - The **issue number** and **Status** you set.