diff --git a/custom-skills/32-notion-writer/code/scripts/md_translate.py b/custom-skills/32-notion-writer/code/scripts/md_translate.py
new file mode 100644
index 0000000..c280b7c
--- /dev/null
+++ b/custom-skills/32-notion-writer/code/scripts/md_translate.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Translate the notion-writer markdown dialect into Notion enhanced
+markdown for the markdown write engine. Pure functions, no I/O."""
+
+from __future__ import annotations
+
+import re
+from typing import List
+
+# NOTE: `notion_writer` is imported lazily inside _translate_mentions to avoid
+# a circular import (notion_writer imports this module at its top level).
+
+# Skill alert type -> (emoji, Notion enhanced-markdown background color)
+CALLOUT_MAP = {
+ "NOTE": ("âšī¸", "blue_bg"),
+ "TIP": ("đĄ", "green_bg"),
+ "IMPORTANT": ("âī¸", "purple_bg"),
+ "WARNING": ("â ī¸", "yellow_bg"),
+ "CAUTION": ("đ¨", "red_bg"),
+}
+_ALERT_RE = re.compile(r'^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$')
+_MENTION_RE = re.compile(r'@\[([^\]]+)\]\(([^)\s]+)\)')
+
+
+def _translate_mentions(text: str) -> str:
+ from notion_writer import extract_notion_id, format_id_with_dashes # lazy: breaks import cycle
+
+ def repl(m: "re.Match") -> str:
+ title, target = m.group(1), m.group(2)
+ page_id = extract_notion_id(target)
+ if page_id:
+ return (f''
+ f'{title}')
+ return f"@{title}"
+ return _MENTION_RE.sub(repl, text)
+
+
+def _indent(lines: List[str]) -> List[str]:
+ return ["\t" + ln if ln.strip() else ln for ln in lines]
+
+
+def translate(content: str) -> str:
+ lines = content.split("\n")
+ out: List[str] = []
+ i = 0
+ while i < len(lines):
+ line = lines[i]
+
+ # Callout: > [!TYPE] then contiguous > body lines
+ if line.lstrip().startswith(">"):
+ body_first = line.lstrip()[1:].strip()
+ alert = _ALERT_RE.match(body_first)
+ if alert:
+ emoji, color = CALLOUT_MAP[alert.group(1)]
+ i += 1
+ body: List[str] = []
+ while i < len(lines) and lines[i].lstrip().startswith(">"):
+ body.append(lines[i].lstrip()[1:].lstrip())
+ i += 1
+ out.append(f'')
+ out.extend(_indent([_translate_mentions(b) for b in body]))
+ out.append("")
+ continue
+
+ # Columns: ::: columns / ::: column / :::
+ # State machine: "::: column" opens a column; ":::" closes a column
+ # when one is open, otherwise closes the wrapper. The Pandoc layout
+ # emits N "::: column" opens, N ":::" column-closes, then one final
+ # ":::" wrapper-close.
+ if line.strip() == "::: columns":
+ i += 1
+ cols: List[List[str]] = []
+ cur: List[str] = None
+ while i < len(lines):
+ s = lines[i].strip()
+ if s == "::: column":
+ if cur is not None:
+ cols.append(cur)
+ cur = []
+ i += 1
+ elif s == ":::":
+ if cur is not None: # close the open column
+ cols.append(cur)
+ cur = None
+ i += 1
+ else: # close the wrapper
+ i += 1
+ break
+ else:
+ if cur is not None:
+ cur.append(lines[i])
+ i += 1
+ out.append("")
+ for col in cols:
+ out.append("\t")
+ out.extend(_indent(_indent(
+ [_translate_mentions(c) for c in col])))
+ out.append("\t")
+ out.append("")
+ continue
+
+ # Toggle: ..
body
+ if line.strip() == "":
+ out.append("")
+ i += 1
+ if i < len(lines) and lines[i].lstrip().startswith(""):
+ out.append(lines[i].strip())
+ i += 1
+ body = []
+ while i < len(lines) and lines[i].strip() != "
":
+ body.append(_translate_mentions(lines[i]))
+ i += 1
+ out.extend(_indent(body))
+ out.append(" ")
+ if i < len(lines): # skip closing
+ i += 1
+ continue
+
+ out.append(_translate_mentions(line))
+ i += 1
+ return "\n".join(out)
diff --git a/custom-skills/32-notion-writer/code/scripts/test_md_translate.py b/custom-skills/32-notion-writer/code/scripts/test_md_translate.py
new file mode 100644
index 0000000..e55c8a7
--- /dev/null
+++ b/custom-skills/32-notion-writer/code/scripts/test_md_translate.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+"""Tests for md_translate.py â run with `python test_md_translate.py`."""
+
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent))
+
+import md_translate
+
+
+def test_passthrough_basics():
+ src = "# Heading\n\n- item\n\n```python\nx=1\n```"
+ assert md_translate.translate(src) == src
+
+
+def test_callout_note():
+ out = md_translate.translate("> [!NOTE]\n> Be careful")
+ assert '' in out
+ assert "\tBe careful" in out
+ assert "" in out
+
+
+def test_callout_warning_color():
+ out = md_translate.translate("> [!WARNING]\n> Danger")
+ assert 'color="yellow_bg"' in out
+ assert 'icon="â ī¸"' in out
+
+
+def test_columns():
+ src = "::: columns\n::: column\nLeft\n:::\n::: column\nRight\n:::\n:::"
+ out = md_translate.translate(src)
+ assert "" in out
+ assert out.count("") == 2
+ assert "" in out
+ assert "\tLeft" in out or "\t\tLeft" in out
+
+
+def test_toggle_children_indented():
+ src = "\nMore
\nBody line\n "
+ out = md_translate.translate(src)
+ assert "More" in out
+ assert "\tBody line" in out
+
+
+def test_mention_url():
+ out = md_translate.translate(
+ "See @[ADR](https://notion.so/X-abcdef0123456789abcdef0123456789).")
+ assert "ADR" in out
+
+
+def test_mention_invalid_plain():
+ out = md_translate.translate("ping @[Bob](not-an-id)")
+ assert "@Bob" in out
+ assert "mention-page" not in out
+
+
+def run_all():
+ tests = [
+ test_passthrough_basics,
+ test_callout_note,
+ test_callout_warning_color,
+ test_columns,
+ test_toggle_children_indented,
+ test_mention_url,
+ test_mention_invalid_plain,
+ ]
+ for t in tests:
+ print(f"\n{t.__name__}")
+ t()
+ print("\n" + "=" * 50)
+ print(f"â
All {len(tests)} tests passed")
+ print("=" * 50)
+
+
+if __name__ == "__main__":
+ run_all()