| name | zenn-markdown |
| version | 0.1.0 |
| description | Zenn の Markdown 本文(:::message / :::message alert、:::details、リンクカード、```lang:filename、diff コードブロック、@[tweet] / @[youtube] / @[speakerdeck] などの埋め込み、KaTeX 数式、画像サイズ指定 =250x、脚注、mermaid)に沿って Markdown を整える。特に SpeakerDeck などサービスごとに必要な埋め込み ID を URL から抽出して `@[speakerdeck](id)` 形式に変換する処理は scripts/zenn-embed.sh を呼び出してこのスキルが面倒を見る。Zenn / zenn.dev に投稿する記事を書くとき、既存の md を Zenn 用に最適化したいとき、SpeakerDeck / CodePen / Figma / Gist / JSFiddle / StackBlitz / Twitter / YouTube / GitHub の URL を貼って「埋め込みにして」「Zenn 記法に直して」と言われたとき、また「メッセージ枠」「アコーディオン」「折り畳み」「リンクカード」「枠囲み」のような Zenn 寄りの表現を Markdown でやりたいと言われたとき、ユーザーが明示的に「Zenn」と言っていなくても積極的に発動する。frontmatter(title/emoji/type/topics/published)は対象外で、本文の Markdown 記法のみをチューニングする。 |
| license | MIT |
| compatibility | claude, codex |
zenn-markdown
Zenn の Markdown 本文を、Zenn の独自記法を活かした形に整えるためのスキル。frontmatter は触らず本文だけを編集する。新規執筆の支援、既存ドラフトの最適化、記法そのものの問い合わせ、いずれにも使う。
正確な構文を確認したいときは references/syntax.md を読むこと。本ファイルには判断のための要点だけを置いている。
スコープ
このスキルが扱うのは「本文の Markdown 記法」だけ。
- 扱う:
:::message / :::details / リンクカード化 / コードブロックのファイル名・diff 指定 / 埋め込み / KaTeX / 画像サイズ指定 / 脚注 / mermaid / GitHub Alerts → Zenn メッセージ枠の置換 など
- 扱わない:
--- で囲まれた YAML frontmatter(title, emoji, type, topics, published, published_at 等)。トピックの推定や絵文字の選定は別の関心事。本文を書き換える際も frontmatter ブロックは読み取り専用として保持する
frontmatter を触らない理由: 公開状態や予約日時 (published_at) は誤って書き換えると公開事故になりうるため、専用のフローで扱うべき関心事として切り離している。
起動モードの判断
ユーザーの言い回しと与えられた素材から、3 つのモードのどれかに振り分ける。明示的に「Zenn」と言われなくても、メッセージ枠・折り畳み・リンクカードといった Zenn 由来の表現を求められたら発動して構わない。
- mode A: 既存の .md を Zenn 用に整える — ユーザーが既存ファイルパスを指している、もしくは本文を貼って「Zenn 用に直して」「articles に出せる形にして」と言ったとき。
~/repos/github.com/shuntaka9576/zenn-content/articles/ や ~/repos/github.com/shuntaka9576/memo/articles/ 配下の .md を指してくることが多い。
- mode B: 新規記事の執筆支援 — 「Zenn に投稿する記事を書きたい」「~について Zenn 向けに書きたい」のような依頼。空のドラフトから組み立てる。
- mode C: 局所的な記法の質問 — 「Zenn の :::message ってどう書くの?」「リンクカードにする方法」のようなピンポイントの質問。
references/syntax.md で確認した上で短く答える。書き換えはしない。
迷ったらユーザーに「既存ドラフトの最適化と新規執筆のどちらを想定していますか?」と一言だけ確認する。素材(既存 .md)の有無で大抵は明らかなので、過剰に確認しない。
共通の方針
- frontmatter ブロックは保持して通過させる。
--- で囲まれた YAML を見つけたら、その内容は変更せずに本文編集の対象から外す。
- 本文は
## (H2) から始める。Zenn は記事タイトルを frontmatter の title または記事メタとして表示するので、本文の冒頭に # H1 を置くと「タイトルが二重に出る」「アクセシビリティ的に H1 が複数になる」など見栄えと構造の両面で破綻する。本文中の見出しはすべて ## 以下にする(mode B の新規執筆でも、mode A の変換で既存 # を見つけたときも、同じ)。
- 本文の意味を変えない。記法の置き換えはレンダリングの見た目を整えるためのもので、文意のリライトはこのスキルの仕事ではない。意味の調整が必要に見えたら、別途確認してから行う。
- コードブロックの中は触らない。コード例の中の
> [!NOTE] や URL は、それ自体が解説対象である可能性が高い。記法変換は地の文(コードブロックの外)に対してだけ行う。
- インラインリンクと裸 URL を区別する。
[テキスト](url) の形のインラインリンクは段落の途中で意味を持っているので、勝手にカード化しない。リンクカードは独立した行に置かれた裸 URL に対してだけ提案する。
- 過剰に :::message を増やさない。文章のあちこちを枠で囲むと逆に読みにくくなる。実際にユーザーの過去記事でも
:::message は記事ごとに 0〜数個で、無闇に増やしていない。
「全部 MUST」ではなく、「なぜそうするか」を踏まえて判断する。判断に迷う変換は独断で適用せず、提案として示してユーザーに選ばせる。
Zenn 独自記法の早見表
詳細・全構文は references/syntax.md を見ること。ここでは「これさえ覚えていれば本文を整えられる」最小集合。
メッセージ枠
:::message
通常のメッセージ
:::
:::message alert
警告系のメッセージ
:::
:::message は情報の補足、:::message alert は注意・警告。alert 以外の修飾子はない。
アコーディオン(折り畳み)
:::details タイトル
中身
:::
中に :::message などをネストする場合は、外側を ::::details(コロン 4 つ)にすることで内外の境界を明確にする。
コードブロックのファイル名・diff
```ts:src/foo.ts
const x = 1;
```
```diff ts
- const a = 1;
+ const a = 2;
```
リンクカードと埋め込み
サービスごとに「URL のままでよい / @[…](URL) で囲う / @[…](id) のように id 抽出が必要」が分かれる。判断は scripts/zenn-embed.sh に任せると安全(後述)。
- 単独行 URL のままで埋め込みになる: Twitter / X、YouTube、GitHub(行範囲指定は
#L1-L3)
@[サービス](URL) で URL を渡す: CodePen / JSFiddle / StackBlitz / Figma / Gist
@[サービス](id) で id 抽出が必要: SpeakerDeck(embed_id は oEmbed API から取得)
- 不明なホストの単独行 URL: そのまま置けば Zenn のリンクカードになる
詳細とサービスごとの URL 例は references/syntax.md を見ること。
数式 (KaTeX)
- インライン:
$a \ne 0$
- ブロック:
$$ \int_0^\infty f(x)\,dx $$
画像のサイズ指定とキャプション

*画像のキャプション*
=幅x高さ の片方だけ書ける(=400x で幅指定、=x300 で高さ指定)。キャプションは画像直後の段落を *…* で囲む。
脚注
本文中の脚注[^1]。
[^1]: 脚注の内容。
mermaid
```mermaid
graph TB
A --> B
```
mermaid には文字数(約 2000 字)とチェイン演算子(最大 10 個)の制限あり。
変換ジャッジの基準(mode A 用)
既存 Markdown を Zenn 向けに変換するときの典型的な判断。
| 入力に出てくるもの | 推奨アクション |
|---|
本文冒頭の # タイトル (H1) | 共通方針 2 のとおり削除して ## … に統一する。タイトルは frontmatter / 記事メタが受け持つ |
> [!NOTE] / > [!TIP] / > [!IMPORTANT] | :::message に置換。引用記号 > は外し、内容だけ枠の中へ |
> [!WARNING] / > [!CAUTION] | デフォルトは :::message。alert は下の「alert を使う基準」を満たす内容にだけ使う。GitHub Alerts のラベルが WARNING/CAUTION でも、中身が「単なる注意」「運用知見」程度であれば :::message に降格する |
| 段落冒頭の「注意:」「Tips:」「⚠️」「📝」など | 文脈次第で :::message 化を 提案。独断で全部囲わない |
| 段落冒頭の「警告:」「🚨」「⚠️ 重要」など、実害を強く示唆する書き出し | alert 基準を満たす内容なら :::message alert。満たさない場合は :::message |
| 単独行に置かれた裸 URL(前後が空行) | そのまま残してリンクカードとしてレンダリング |
段落中の [テキスト](url) で url が 埋め込み可能サービス (SpeakerDeck / CodePen / Figma / Gist / JSFiddle / StackBlitz / YouTube / Twitter / X / GitHub) | 「これは単独行に切り出して埋め込み記法にしたほうが映えそう」と 提案。本文の流れ上 URL がインラインのほうが自然なら据え置き。提案するときは scripts/zenn-embed.sh を通した結果を候補として示す |
段落中の [テキスト](url) で url がそれ以外(普通の記事 URL など) | そのまま。カード化しない |
| 段落の文末に括弧書きで貼られた埋め込み可能サービスの URL | 単独行に切り出して埋め込み化を 提案 |
引用としての > ...(GitHub Alerts ではない通常の引用) | そのまま。:::message に置き換えない |
| 長い補足・付録的なコード | :::details で折り畳むことを 提案 |
```ts で言語だけ指定されたコードブロックで、ファイルパスが文中で示されている | ```ts:path/to/file.ts に書き換えを 提案 |
| GitHub の URL(行ハイライトしたい行範囲が文中に書かれている) | #L1-L3 を付けることを提案 |
| ツイート / YouTube の URL | 単独行に出して埋め込みとしてレンダリングさせる |
「提案」と書いた項目は、独断で全置換せずまず候補を出す。半自動的に直してよいのは GitHub Alerts → :::message のようなパターン明確なものだけ。
alert を使う基準(厳しめに)
:::message alert は色とアイコンが強く、目立つので、本当に「読み飛ばされたら実害が出る」内容にだけ使う。Zenn の alert は警告灯であって、注意書き全般のための装飾ではない。
alert を 使ってよい 例:
- セキュリティ事故につながる設定ミス(公開バインド、認証なしエンドポイント、過剰権限の IAM など)
- データ消失・破壊につながる操作(
rm -rf、移行前のキー削除、不可逆な DB 操作)
- 公開事故(
published: true のままコミット、機密情報のリポジトリ push)
- 取り返しのつかない外部影響(誤った宛先への送信、本番への直接適用)
alert を 使うべきではない 例:
- 「設定はこのほうが安全です」程度の運用知見・ベストプラクティス
- 「ローカルでは動きません」のような開発時 Tips
- 「事前に確認してください」のような一般的注意
- 「読者の前提知識」を共有する補足
判断に迷ったら :::message を選ぶ。記事全体で :::message alert が 0〜2 個程度に収まるのが Zenn 記事の自然な姿で、3 つ以上 alert が並ぶと読者が警告に慣れて効果が薄れる。
ジャッジに迷ったときは、文の冒頭が「〜しないと事故が起きる / 〜してしまうと取り返しがつかない / セキュリティが破綻する」と読み替えても自然なら alert、そうでなければ message。
埋め込み変換ヘルパー (scripts/zenn-embed.sh)
サービスごとの記法選択を間違えないために、URL を scripts/zenn-embed.sh に通して結果を採用する。素のままでよい URL は素通し、明示記法が必要なものは @[…](URL) に整形され、SpeakerDeck だけは oEmbed を叩いて embed_id を埋めた @[speakerdeck](id) を返す(embed_id は URL からは推測できないので、これは Claude が暗算で書こうとすると確実に間違える領域)。
依存: bash, curl, jq。
使い方
引数で 1 件:
scripts/zenn-embed.sh https://speakerdeck.com/<user>/<deck>
stdin で複数:
awk '/^https?:\/\//{print}' draft.md | scripts/zenn-embed.sh
いつ呼ぶか
- mode A(既存記事の最適化)で本文中に SpeakerDeck の URL を見つけたとき
- mode A で
https://codepen.io/... のように「明示記法のほうが安定するサービス」の単独行 URL を見つけたとき
- mode B(新規執筆)でユーザーが「このスライド URL を貼り付けて」と URL を渡してきたとき
X/Twitter / YouTube / GitHub / リンクカード一般など「裸 URL のままで OK」のものは、スクリプトを通しても素通しで返ってくる(壊さない)ので、判断に迷ったら通してしまえばよい。
出力できないケース
- ネットワーク到達不可 / oEmbed が 4xx / 5xx を返した SpeakerDeck → スクリプトは元 URL をそのまま返し、
stderr に warn を出す。Claude はこれを見たら「id 取得に失敗したので URL のままにしておく」とユーザーに伝える
- 知らないホスト → 素通し。ユーザーがそのドメインの埋め込み記法を期待しているようなら、Zenn 側でサポートされているか
references/syntax.md を確認する
出力ポリシー
- mode A: 既存ファイルを編集するときは、Edit ツールで該当箇所だけを差し替える(全文書き換えはしない)。複数箇所を直す場合は変更前後の差分を要約してから編集する。
- mode B: 段階的に進める。まず構成案(見出しの並び)を提示してユーザーの合意を取り、それから各セクションを書く。frontmatter は要求されない限り作らない。
- mode C: 該当する記法だけを
references/syntax.md で確認し、簡潔に答える。
やらないこと
- frontmatter(
--- ブロック)の値を変更する
- インラインリンクをカード化する
- 裸の段落を片端から
:::message で囲む
- コードブロックの中身を別の Markdown として再解釈する
- 文意を変えるリライトを伴う「最適化」
- 過去記事の文体に強制的に揃える(参考にしてよいが押し付けない)
参考