---
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）の有無で大抵は明らかなので、過剰に確認しない。

## 共通の方針

1. **frontmatter ブロックは保持して通過させる**。`---` で囲まれた YAML を見つけたら、その内容は変更せずに本文編集の対象から外す。
2. **本文は `##` (H2) から始める**。Zenn は記事タイトルを frontmatter の `title` または記事メタとして表示するので、本文の冒頭に `# H1` を置くと「タイトルが二重に出る」「アクセシビリティ的に H1 が複数になる」など見栄えと構造の両面で破綻する。本文中の見出しはすべて `##` 以下にする（mode B の新規執筆でも、mode A の変換で既存 `#` を見つけたときも、同じ）。
3. **本文の意味を変えない**。記法の置き換えはレンダリングの見た目を整えるためのもので、文意のリライトはこのスキルの仕事ではない。意味の調整が必要に見えたら、別途確認してから行う。
4. **コードブロックの中は触らない**。コード例の中の `> [!NOTE]` や URL は、それ自体が解説対象である可能性が高い。記法変換は地の文（コードブロックの外）に対してだけ行う。
5. **インラインリンクと裸 URL を区別する**。`[テキスト](url)` の形のインラインリンクは段落の途中で意味を持っているので、勝手にカード化しない。リンクカードは独立した行に置かれた裸 URL に対してだけ提案する。
6. **過剰に :::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 $$`

### 画像のサイズ指定とキャプション

```
![alt](https://example.com/x.png =400x)
*画像のキャプション*
```

`=幅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 件:

```bash
scripts/zenn-embed.sh https://speakerdeck.com/<user>/<deck>
# → @[speakerdeck](<32桁hex>)
```

stdin で複数:

```bash
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 として再解釈する
- 文意を変えるリライトを伴う「最適化」
- 過去記事の文体に強制的に揃える（参考にしてよいが押し付けない）

## 参考

- 全構文の正確な書式: `references/syntax.md`
- Zenn 公式: https://zenn.dev/zenn/articles/markdown-guide
- Zenn CLI（frontmatter 仕様、ファイル配置）: https://zenn.dev/zenn/articles/zenn-cli-guide
