CHANGELOG とリリースノートの違い
CHANGELOG とリリースノートはどちらも「何が変わったか」を記録するドキュメントだが、目的と読者が根本的に異なる。 CHANGELOG は技術者が詳細な変更差分を追跡するための内部ドキュメントであり、6つの変更カテゴリをすべて記録する。 一方、リリースノートは製品の変更をステークホルダーや利用者に伝えるためのコミュニケーション文書だ。
| 観点 | CHANGELOG | リリースノート |
|---|---|---|
| 主な読者 | 開発者・技術者 | エンドユーザー・運用担当者・管理職 |
| 形式 | Markdown(機械処理可能) | Markdown / HTML / PDF など読者に合わせた形式 |
| 記載範囲 | すべての変更(技術的詳細含む) | 利用者に関係する変更のみ(内部リファクタは除外) |
| トーン | 事実記述・簡潔 | 価値訴求・分かりやすい言葉で |
| 更新タイミング | 変更のたびに随時更新 | リリース直前に確定・公開 |
読者によって何が変わるか
リリースノートの最大の特徴は読者フィルタリングだ。 同じリリースでも、エンドユーザーと開発者では関心の異なる変更がある。 フィルタを適切に設定することで、各読者に必要な情報だけを届けられる。
👤 エンドユーザー向け
CHANGELOG の Added・Changed セクションから、機能改善とUI変更を中心に抜粋。技術的詳細は除き、「何ができるようになったか」を平易な言葉で伝える。
含める変更カテゴリ: Added / Changed(ユーザーに影響する変更のみ)
除外する変更カテゴリ: Fixed(内部バグ)/ Security(技術詳細)/ Deprecated(開発者向け)/ Removed
🔧 開発者・運用担当者向け
CHANGELOG の全セクションを含む。ブレイキングチェンジ・非推奨 API の告知・セキュリティパッチの内容を強調する。CVE 番号・マイグレーションガイドへのリンクも含む。
含める変更カテゴリ: Added / Changed / Deprecated / Removed / Fixed / Security(全セクション)
追加情報: ブレイキングチェンジハイライト / マイグレーション手順
リリースノートの構成要素
リリースノートに含める構成要素とその定義を以下に示す。
| 要素 | 内容 | 必須/任意 |
|---|---|---|
| バージョン番号とリリース日 | v1.4.0 — 2026-06-27 形式のヘッダー | 必須 |
| ハイライト(概要) | このリリースで最も重要な変更を2〜3文で要約する | 必須 |
| 新機能(What's New) | ユーザーが利用できるようになった機能の説明 | 必須(あれば) |
| 改善事項 | 既存機能の使い勝手・パフォーマンス改善 | 推奨 |
| バグ修正 | ユーザーに影響していた問題の解決(技術詳細は省略) | 推奨 |
| セキュリティ修正 | 脆弱性対応。CVE 番号を含める | 必須(あれば) |
| 非推奨・削除 | 廃止予定 / 削除された機能と移行先の案内 | 必須(あれば) |
| アップグレード手順 | ブレイキングチェンジを含む場合の移行手順 | 必須(ブレイキングチェンジ時) |
| 既知の問題 | 把握している未解決の問題と回避策 | 任意 |
⚠️ 「内部リファクタ」はユーザー向けリリースノートに記載しない
コードの整理・テストの追加・CI 環境の変更など、利用者に影響しない変更はユーザー向けリリースノートから除外する。ただし開発者向けのリリースノートには含めることがある。フィルタリングの基準を Python スクリプトで自動化することが望ましい。
自動生成フロー図
CHANGELOG.md を単一の真実のソースとして、読者別リリースノートを Python で自動生成するフローを示す。
--audience フラグで出力内容を切り替える設計だ。
図1:CHANGELOG を起点としたリリースノート自動生成フローと読者別フィルタリング
リリースノートの記述例
ユーザー向けリリースノートと開発者向けリリースノートの記述例を並べて示す。
# v1.4.0 リリースノート(2026-06-27)
## 🎉 ハイライト
このリリースではダッシュボードへのデータエクスポート機能と
検索パフォーマンスの大幅な改善を提供します。
## ✨ 新機能
- **データエクスポート機能**:ダッシュボードのデータを CSV / Excel 形式でダウンロードできるようになりました。
## 📈 改善
- 検索結果の表示が最大 3 倍速くなりました。
- Safari でのログイン画面の表示崩れを修正しました。
## 🔒 セキュリティ
- セッションの有効期限を短縮しました(セキュリティ強化)。
# v1.4.0 Developer Release Notes(2026-06-27)
## ⚠️ ブレイキングチェンジ
- 検索 API のレスポンスが JSON:API 準拠に変更。`data.results` → `data.items`
## Added
- ダッシュボードに CSV / Excel エクスポートエンドポイント追加(`GET /api/v1/export`)
## Changed
- 検索 API レスポンス形式を JSON:API 準拠に変更(後方互換性なし)
## Deprecated
- `/v1/search` エンドポイントは v2.0.0 で削除予定。`/v2/search` へ移行してください。
## Fixed
- Safari ブラウザでのセッションクッキー処理のバグを修正(#412)
## Security
- JWT 有効期限を 24h → 1h に短縮(セキュリティポリシー変更)
- 影響なし(後方互換あり)
## マイグレーション手順
```diff
- const res = await fetch('/api/v1/search');
- const items = res.data.results;
+ const res = await fetch('/api/v2/search');
+ const items = res.data.items;
```
Python 自動生成スクリプト
CHANGELOG.md から読者別リリースノートを自動生成するスクリプトだ。
--version で対象バージョンを、--audience で読者種別を指定する。
"""
CHANGELOG.md からリリースノートを自動生成するスクリプト。
--audience dev : 全セクション出力(開発者向け)
--audience user : Added / Changed のみ出力(エンドユーザー向け)
"""
import argparse
import re
import sys
from pathlib import Path
from dataclasses import dataclass, field
# ユーザー向けに含めるセクション
USER_SECTIONS = {"Added", "Changed"}
ALL_SECTIONS = {"Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"}
VERSION_PATTERN = re.compile(r"^## \[(\d+\.\d+\.\d+[^\]]*)\] - (\d{4}-\d{2}-\d{2})$")
SECTION_PATTERN = re.compile(r"^### (.+)$")
SECTION_EMOJI = {
"Added": "✨",
"Changed": "🔄",
"Deprecated": "⚠️",
"Removed": "🗑️",
"Fixed": "🐛",
"Security": "🔒",
}
@dataclass
class VersionBlock:
version: str
date: str
sections: dict = field(default_factory=dict) # section_name -> list[str]
def parse_changelog(path: str) -> list[VersionBlock]:
"""CHANGELOG.md をパースしてバージョンブロックのリストを返す"""
blocks: list[VersionBlock] = []
current_block: VersionBlock | None = None
current_section: str | None = None
for line in Path(path).read_text(encoding="utf-8").splitlines():
m = VERSION_PATTERN.match(line)
if m:
if current_block:
blocks.append(current_block)
current_block = VersionBlock(version=m.group(1), date=m.group(2))
current_section = None
continue
if line.startswith("## [Unreleased]"):
current_block = None
current_section = None
continue
if current_block is None:
continue
ms = SECTION_PATTERN.match(line)
if ms:
current_section = ms.group(1)
if current_section not in current_block.sections:
current_block.sections[current_section] = []
continue
if current_section and line.startswith("- "):
current_block.sections[current_section].append(line)
if current_block:
blocks.append(current_block)
return blocks
def generate_notes(block: VersionBlock, audience: str) -> str:
"""バージョンブロックからリリースノート文字列を生成する"""
target_sections = USER_SECTIONS if audience == "user" else ALL_SECTIONS
label = "ユーザー向け" if audience == "user" else "開発者向け"
lines = [
f"# v{block.version} リリースノート ({label})",
f"**リリース日:** {block.date}",
"",
]
for section in ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"]:
if section not in target_sections:
continue
entries = block.sections.get(section, [])
if not entries:
continue
emoji = SECTION_EMOJI.get(section, "")
lines.append(f"## {emoji} {section}")
lines.extend(entries)
lines.append("")
if not any(
block.sections.get(s) for s in target_sections
):
lines.append("_このバージョンには該当する変更がありません。_")
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser(
description="CHANGELOG.md からリリースノートを生成する"
)
parser.add_argument("changelog", nargs="?", default="CHANGELOG.md")
parser.add_argument("--version", default=None, help="対象バージョン(省略時は最新)")
parser.add_argument(
"--audience",
choices=["dev", "user"],
default="dev",
help="読者種別(dev: 開発者向け / user: エンドユーザー向け)",
)
parser.add_argument("--output", default=None, help="出力ファイルパス(省略時は標準出力)")
args = parser.parse_args()
if not Path(args.changelog).exists():
print(f"❌ ファイルが見つかりません: {args.changelog}")
return 1
blocks = parse_changelog(args.changelog)
if not blocks:
print("❌ CHANGELOG にバージョンブロックが見つかりません")
return 1
target = blocks[0]
if args.version:
matches = [b for b in blocks if b.version == args.version]
if not matches:
print(f"❌ バージョン {args.version} が見つかりません")
return 1
target = matches[0]
notes = generate_notes(target, args.audience)
if args.output:
Path(args.output).write_text(notes, encoding="utf-8")
print(f"✅ リリースノートを生成しました: {args.output}")
else:
print(notes)
return 0
if __name__ == "__main__":
sys.exit(main())
実行例を示す。
# 最新バージョンの開発者向けリリースノートを生成
python generate_release_notes.py CHANGELOG.md --audience dev
# v1.4.0 のユーザー向けリリースノートをファイル出力
python generate_release_notes.py CHANGELOG.md \
--version 1.4.0 \
--audience user \
--output release-notes-1.4.0-user.md
CI/CD への組み込みと公開先
リリースブランチ作成時に GitHub Actions でリリースノートを自動生成し、 GitHub Release・Confluence・Slack へ自動配信する設計例を示す。
name: Generate Release Notes
on:
push:
tags:
- "v*"
jobs:
release-notes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Generate dev release notes
run: |
python tools/generate_release_notes.py CHANGELOG.md \
--audience dev \
--output dist/release-notes-dev.md
- name: Generate user release notes
run: |
python tools/generate_release_notes.py CHANGELOG.md \
--audience user \
--output dist/release-notes-user.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body_path: dist/release-notes-dev.md
files: dist/release-notes-*.md
| 公開先 | 推奨読者 | 形式 |
|---|---|---|
| GitHub Releases | 開発者 | Markdown(dev 向け) |
| Confluence / Notion | 運用担当者・管理職 | Markdown or HTML(dev 向け) |
| 製品サイト / ヘルプページ | エンドユーザー | HTML(user 向け) |
| Slack #release チャンネル | 社内全体 | ハイライトのみ要約 |
まとめ
リリースノートは CHANGELOG の技術的記録を「読者ごとに最適化されたコミュニケーション文書」に変換したものだ。 以下の3点を押さえて運用を設計する。
- 単一ソース:CHANGELOG.md を唯一の真実のソースとして保守し、リリースノートは派生物として自動生成する
- 読者フィルタ:ユーザー向け(Added/Changed のみ)と開発者向け(全セクション)でフィルタリングを分ける
- CI 自動生成:タグプッシュをトリガーに GitHub Actions でリリースノートを自動生成・公開する
✅ 次のステップ
リリースノートの品質を担保するには、その前段としてリリース判定チェックリストで「リリースノート作成」が完了していることを確認する仕組みが必要だ。次の記事「リリース判定チェックリストで定義すべきこと」では、Python 検証スクリプトによる GO/NO-GO 判定の自動化を解説する。