Skills / Skills / Notion Ingest
All skills

/ skill

notion-ingest

Skills/notion-ingest.md

Fetch a Notion page via MCP, classify the content type, and route it to the right area of the vault (role / brand / decision / learning / inbox).

Open as raw file

Run this skill

The skill auto-classifies; this is just a nudge.

Runs with bypassPermissions — local dev only.

notion-ingest

Pull a Notion page into the Supernova vault and route its contents to the right place. The Notion side stays read-only by default — vault is the source of truth for synthesized output.

Pre-flight checks

  1. Notion MCP authenticated. Test with mcp__claude_ai_Notion__notion-search (lightweight call). If 401 / not configured, surface to Adam: "Run /mcp and authenticate Notion first."
  2. url_or_id looks like a Notion URL (contains notion.so) or a 32-char hex ID with optional dashes. Normalize to the page ID before fetching.
  3. Raw/Notion/ exists; create if missing.

Procedure

1. Fetch the page

Call mcp__claude_ai_Notion__notion-fetch with the URL or ID. Capture:

  • title
  • body (markdown)
  • created_time, last_edited_time
  • properties (any structured fields — date, attendees, tags, status, etc.)
  • url (canonical Notion URL)

If fetch fails: report and abort. Don't fall back silently.

2. Save raw provenance copy

If preserve_raw == true (default):

  • Slug = kebab-case of title, truncated to 60 chars.
  • Path: Raw/Notion/{YYYY-MM-DD}-{slug}.md
  • Frontmatter:
    ---
    type: raw-input
    source: notion
    notion_url: {canonical url}
    notion_page_id: {id}
    fetched: {today}
    original_title: {title}
    original_last_edited: {last_edited_time}
    ---
    
  • Body: the page markdown verbatim.

3. Classify the content type

Use heuristics first; ask Adam if ambiguous. Heuristics (in priority order):

SignalTypeRouting target
Title contains "1:1", "call notes", "meeting", "sync", "weekly", "standup"; OR body has attendee list / agenda / action items sectionMeeting notesrole doc + active-todos
Title contains "decision", "should we", "vs."; OR body has pros/cons / options / "TBD"DecisionWiki/Decisions/ + active-todos
Title contains "strategy", "roadmap", "OKR", "plan", "Q1/Q2/Q3/Q4 plan"Strategyrole/brand doc update
Title contains "lesson", "learning", "takeaway", "retro", "post-mortem"LearningWiki/Learnings/
Title contains "idea", "brainstorm", "raw"; OR body unstructured / thoughts in flowBrainstormRaw/Inbox/
OtherwiseAmbiguousAsk via AskUserQuestion

If the caller passed target_hint, that wins over heuristics.

4. Determine scope (brand / role / cross-cutting)

Detect from content:

  • Mentions of brand names from Context/Brands.md#brand/{slug}
  • Mentions of role-specific terms (e.g., "performance", "Meta ads", "creative team", channel names) AND brand context → role-scoped
  • No clear brand → cross-cutting (vault-wide / GMG-level)

If multiple plausible targets: AskUserQuestion with the candidates. Always include "Cross-cutting / no specific scope" as an option.

5. Route

Based on type × scope:

TypeScopeTarget file
Meeting notesrole-scopedBrands/{brand}/Roles/{role}.md (append to "Recent meetings" section) + new Brands/{brand}/Notes/{date}-{title-slug}.md for full notes
Meeting notesbrand-scoped (no role)Brands/{brand}/Notes/{date}-{title-slug}.md
Meeting notescross-cuttingWiki/Notes/{date}-{title-slug}.md
DecisionanyWiki/Decisions/{date}-{title-slug}.md (use decision-memo template if available)
Strategyrole-scopedappend/update Brands/{brand}/Roles/{role}.md strategic-bets section
Strategybrand-scopedappend to Brands/{brand}/knowledge-base.md or BrandContext.md quick-reference
LearninganyWiki/Learnings/{date}-{slug}.md (atomic — one insight per node)
BrainstormanyRaw/Inbox/{date}-{title-slug}.md (don't synthesize prematurely)

6. Spawn todos

If spawn_todos == true (default) AND content has detectable action items (lines starting with [ ], TODO:, "Action:", "@adam to..."):

  • For each: append to Wiki/Notes/_active-todos.md under ## Open with format:
    - [ ] **{action title}** — {context}. {role/brand tags}. <!-- created: {today}, source: <a class="wikilink wikilink-broken" href="#">Raw/Notion/{date}-{slug}</a> -->
    
  • Bump last_updated: in the todos file frontmatter.

7. Spawn decisions

If spawn_decisions == true (default) AND content surfaces an open decision (questions like "should we X?", "X vs Y?", explicit "Decision needed:" markers):

  • Create Wiki/Decisions/{date}-{decision-slug}.md with status: pending, link back to source Notion page + raw copy.
  • Append a todo under ## Open in active-todos: "Make decision on {topic}" with #dept/decisions tag.

8. Cross-link

Wherever a synthesized file is written, add wikilinks:

  • Up to the role doc / brand context (so it appears in graph view)
  • Down to source: <a class="wikilink wikilink-broken" href="#">Raw/Notion/{date}-{slug}</a> (or external Notion URL if preserve_raw == false)
  • Sideways to any mentioned people (Wiki/People/{slug}) or concepts

9. Append Log entry

Via log-operation:

  • agent: ea-orchestrator
  • skill: notion-ingest
  • input: notion_page="{title}", classified_as={type}, scope={scope}
  • output: synthesized={count} files, todos={N}, decisions={M}
  • artifacts: list of all wikilinks created/updated
  • notes: any classification ambiguity that required Adam's input

Failure modes

  • Notion MCP not auth'd / 401: abort, instruct Adam to authenticate.
  • Page is empty or 404: abort with clear message; don't write empty Raw file.
  • Page is huge (>50k chars): save raw, but ask Adam before deep-synthesizing — "this is a long page; want full synthesis or executive summary only?"
  • Sensitive content flagged (contains terms from _System/SecretsRegistry.md like API keys, passwords, secret tokens): refuse to write to Raw or Wiki; surface to Adam, suggest redacting in Notion first.
  • Routing genuinely unclear: fall back to Raw/Inbox with a follow-up todo to manually classify later.

Hard rules

  • Read-only on the Notion side. Don't update / delete / rename pages in Notion unless the caller explicitly asks.
  • Provenance is non-negotiable — every synthesized file references the Notion page (URL or <a class="wikilink wikilink-broken" href="#">Raw/Notion/...</a> wikilink). Adam should always be able to trace a vault note back to its origin.
  • Raw is sacred — once written to Raw/Notion/, never edit. Re-fetch creates a new dated file.
  • Don't push back to Notion with synthesized output (yet) — the vault is the synthesis layer; Notion is an input source. Phase 2 could add bidirectional sync (write the wiki node's permalink back as a Notion property), but not v1.
  • Never push sensitive vault content (Context/MI.md, Raw/Health/, brand compliance docs) into a Notion search query — search terms go to Notion's servers.

Caller patterns

Manual ingest (most common)

Adam pastes a Notion URL → /notion ingest <url> → skill routes everything.

EA daily briefing (Phase 2)

The EA-Orchestrator's daily-briefing skill (when built) calls notion-search to find pages updated in the last 24h that match #supernova-pull or a configured tag, then loops through notion-ingest for each.

Direct from chat

Adam pastes Notion content (without URL) → caller can skip the fetch step and pass the markdown directly to step 3 (classification) onward.

Future extensions (out of scope for v1)

  • Bidirectional sync: write the synthesized wiki path back to Notion as a property.
  • Database ingestion: walk a Notion database and ingest each row as a structured node.
  • Webhook trigger: ingest on-update via Notion webhook (requires hosted infra; defer until needed).
  • Diff-aware re-ingest: if the same Notion page is ingested twice, diff against the previous Raw copy and only resynthesize the changes.