
Conventional Commits 徹底解説 - チームと自動化ツールに優しいコミットメッセージ規約
「とりあえず update でコミット」「fix bug でなんとなく」を続けていると、3ヶ月後の自分や CI の自動化スクリプトに見放されます。Conventional Commits は、そのカジュアルなコミットメッセージに 軽量な規約を1枚乗せて、人間にも機械にも読めるコミット履歴に変えるための仕様です。
本記事では Conventional Commits 1.0.0 の仕様を、type / scope / description / body / footer / 破壊的変更の書き方からはじめて、commitlint + Husky による強制、semantic-release / changesets による自動リリース、Cursor などの AI エディタとの組み合わせまで実装目線で整理します。
Conventional Commits とは
Conventional Commits は、Git のコミットメッセージにつける軽量な規約です。仕様は2018年に v1.0.0-beta が登場し、現在の安定版は v1.0.0(公式仕様)。
設計目標は以下の通り。
- コミットメッセージから 何が起きたかを機械が読み取れること
- それをもとに CHANGELOG / バージョン番号 / リリースを自動生成できること
- 規約自体は ごく軽量で、覚えるのに5分・運用に1日もかからないこと
SemVer(Semantic Versioning) と協調動作するように作られていて、fix は PATCH、feat は MINOR、BREAKING CHANGE は MAJOR に対応します。後述する semantic-release を使うと、コミットメッセージを書くだけでバージョン番号が勝手に上がります。
基本フォーマット
仕様で定められているコミットメッセージの構造は次の通り。
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]<type>: 変更の種類を示す英単語(feat/fix/docsなど)[optional scope]: 変更が触れたコードの範囲を表す名詞を()で囲む(feat(api):など)<description>: 変更内容の短い要約(タイトル)[optional body]: タイトルと 空行を1行挟んで詳細を書く[optional footer(s)]:Refs: #123Reviewed-by: ZBREAKING CHANGE: ...などのメタ情報
最小限のコミットなら次のように1行だけでも仕様準拠です。
docs: correct spelling of CHANGELOGscope を付けると、どこを触ったかが一発で分かります。
feat(parser): add ability to parse arrays
feat(lang): add polish language
fix(auth): handle expired refresh tokenstype の一覧
仕様で 必須として定義されているのは feat と fix の2つだけ。それ以外は推奨や慣例で、現在は Angular 流の type 一覧(@commitlint/config-conventional のデフォルト)が事実上の標準として広く使われています。
| type | 用途 | SemVer 影響 |
|---|---|---|
feat | 新機能の追加 | MINOR |
fix | バグ修正 | PATCH |
docs | ドキュメントだけの変更(README、JSDoc など) | なし |
style | コードの意味に影響しない変更(空白、フォーマット、セミコロン等) | なし |
refactor | バグ修正でも機能追加でもないコード変更 | なし |
perf | パフォーマンス改善 | PATCH 扱いが一般的 |
test | テストの追加・修正 | なし |
build | ビルドシステム・外部依存の変更(npm、bundler など) | なし |
ci | CI 設定・スクリプトの変更(GitHub Actions、CircleCI 等) | なし |
chore | その他の雑多な変更(ライセンス、リネーム等) | なし |
revert | 過去のコミットの取り消し | 取り消し対象に依存 |
注意: 仕様の必須は
featとfixだけで、上記の他の type はBREAKING CHANGEを含まない限り SemVer に対する暗黙的な効果を持ちません。semantic-releaseなどのツールは「fixで PATCH、featで MINOR、BREAKING CHANGEで MAJOR」を読み取って自動的にバージョンを上げます。
type の選び方の指針
迷ったときの判断軸:
- ユーザー(呼び出し側)から見た振る舞いが変わる? → 変わるなら
featかfix、変わらないならrefactor系 - 新しい挙動が増える? →
feat - 壊れていたものを直す? →
fix - 見た目だけ整える? →
style(コードのフォーマットのみ。CSS の見た目変更はfeat/fix) - テストだけ? →
test - CI の YAML だけ? →
ci
scope の使い方
scope は どこを触ったかを示す名詞で、type(scope): の形式で書きます。
feat(api): add bulk import endpoint
fix(ui): correct dropdown alignment on Safari
test(parser): cover unicode edge cases
docs(readme): add minimum Node.js versionscope の値はチームで自由に決めて構いませんが、よくあるパターンは以下:
- モノレポのパッケージ名:
feat(web):fix(api):chore(shared): - 機能ドメイン:
feat(auth):fix(billing):refactor(search): - ファイル名・モジュール名:
feat(parser):fix(router):
scope は 省略可能です。プロジェクト全体に関わる変更や1スコープに切り出しにくい変更では、思い切って省略するほうが綺麗です。
description の書き方
description(タイトル)は コード変更の短い要約。読みやすさのために英語圏では以下が推奨されています。
- 命令形(imperative mood):
addfixremoveで書き始める。addedfixesではない - 小文字始まり:
feat: add(feat: Addではない) - 末尾にピリオドを付けない:
fix: handle null(fix: handle null.ではない) - 50〜72文字以内を目安に
日本語で書くケースも増えており、その場合のおすすめは:
- 体言止め or 名詞句で短く(「〜の追加」「〜を修正」)
- 50文字程度を上限の目安に
feat(api): 一括インポートエンドポイントを追加
fix(ui): Safariでドロップダウンの位置がずれる問題を修正
docs(readme): 最低Node.jsバージョンを追記英日どちらで書くかは、プロジェクトで統一するのが最重要。混在させると commitlint の subject-case ルールなどでブレが出ます。
body と footer
詳細が必要なときは、タイトルの 下に空行を1行挟んで body を書きます。
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123body の下にさらに空行を1行挟んでから footer を書きます。footer は Token: 値 または Token #値 の形式で、Git の trailer 規約に揃えてあります。
代表的な footer:
| Token | 用途 |
|---|---|
Refs: #123 | Issue / PR への参照 |
Closes #45 | クローズする Issue(GitHub が自動クローズ) |
Reviewed-by: Alice | レビュア記録 |
Co-authored-by: Bob <bob@example.com> | 共同執筆者(GitHub が contributors 表示) |
Signed-off-by: Carol <carol@example.com> | DCO サインオフ |
BREAKING CHANGE: ... | 唯一スペース可の特別 token。破壊的変更の説明 |
footer の token はスペースの代わりに - を使います(例: Acked-by)。例外として BREAKING CHANGE だけは大文字 + スペースで書きます(BREAKING-CHANGE も同義として許容)。
破壊的変更(BREAKING CHANGE)の書き方
「壊しました」を表現する方法は 2通り用意されています。
方法1: type/scope の直後に ! を付ける
feat!: send an email to the customer when a product is shipped
feat(api)!: send an email to the customer when a product is shipped! を付けた場合、footer の BREAKING CHANGE: は 省略できます(その代わりタイトルで破壊的変更の内容が分かるように書く必要があります)。
方法2: footer に BREAKING CHANGE: を書く
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files方法3: 両方使う(推奨)
実運用としては、! でリリースノートでも目立たせ、footer で詳細を書くのが一番親切です。
feat!: drop support for Node 6
BREAKING CHANGE: use JavaScript features not available in Node 6.semantic-release はこの両方の表記を認識して、自動的に MAJOR(1.x.x → 2.0.0)に上げてくれます。
重要:
BREAKING CHANGEは 任意の type で使えるため、refactor!:やchore!:に付けても MAJOR 扱いになります。「内部リファクタだから影響ない」と思っても、公開 API のシグネチャが変わるなら!を付けましょう。
良い例 / 悪い例(Before / After)
NG 例
update ← type なし、内容ゼロ
fix bug ← どのバグかわからない
WIP ← Work in Progress を本番ブランチに残してはいけない
mod fix ← 自作の type は仕様外
fix: Fixed bug ← 過去形・大文字始まり(プロジェクトで揃える前提だが Angular 流から逸脱)OK 例
fix(auth): refresh トークンが期限切れの場合の 401 を解消
feat(search): スコア順ソートの type を ASC/DESC で指定可能に
docs(readme): Node.js 20.9 以上が必須である旨を追記
chore(deps): next を 16.1.2 から 16.2.0 に更新
ci: GitHub Actions の Node セットアップを v4 に更新
refactor(parser): tokenize 関数を独立モジュールに分離
perf(image): 画像変換処理を sharp の WebP モードに切替えて約30%高速化
revert: feat(auth): 一時的に2FAを無効化SemVer との関係
仕様が SemVer と協調する設計になっているおかげで、コミットメッセージから自動でバージョンを上げられます。
| コミット種別 | SemVer の影響 | 例 |
|---|---|---|
fix: | PATCH(1.2.3 → 1.2.4) | fix(auth): handle expired tokens |
feat: | MINOR(1.2.3 → 1.3.0) | feat(api): add bulk export |
feat!: / BREAKING CHANGE: | MAJOR(1.2.3 → 2.0.0) | feat!: drop support for Node 18 |
docs: style: refactor: test: build: ci: chore: | リリースなし(or PATCH 扱い) | docs(readme): typo |
これは semantic-release のデフォルト挙動。プロジェクトの方針で「refactor も PATCH に上げる」「perf は MINOR に上げる」のようにカスタマイズ可能です。
何が嬉しいのか
Conventional Commits を採用するメリットを整理すると、
- CHANGELOG の自動生成:
conventional-changelogでコミットから整形された CHANGELOG.md を生成 - バージョンの自動決定:
semantic-releaseがfeat→ MINOR、fix→ PATCH、BREAKING CHANGE→ MAJOR と自動判定 - CI/CD トリガー: 「
featを含むタグでステージング、feat!ならカナリアリリース」など、コミット内容で動作を分岐 - PR レビュー効率化: PR タイトルが Conventional Commits になっていれば、レビュー範囲を一目で把握できる
- 将来の自分への取扱説明書: 半年後に
git logを遡るときの可読性が桁違い - AI による自動コミット: Cursor、Claude Code、Copilot などの AI ツールがコミットメッセージを生成するときの 共通の作法になっている
commitlint + Husky で強制する
「規約はあっても、人間は忘れる」を解決するのが commitlint と Husky の組み合わせです。
インストール
npm install --save-dev @commitlint/cli @commitlint/config-conventional huskycommitlint の設定
commitlint.config.js を用意します(CommonJS / ESM どちらでも可)。
/** @type {import('@commitlint/types').UserConfig} */
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'build',
'ci',
'chore',
'revert',
],
],
'subject-case': [0],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100],
},
};subject-case: [0] は日本語タイトルを許容するために無効化しています。英語のみで運用するなら [2, 'never', ['upper-case', 'pascal-case']] などに戻すと良いでしょう。
Husky のセットアップ
npx husky init
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg
chmod +x .husky/commit-msgこれで、規約違反のメッセージで git commit するとフックが弾いてくれます。
$ git commit -m "update stuff"
⧗ input: update stuff
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
husky - commit-msg script failed (code 1)CI でも検査する
ローカルフックは --no-verify で簡単にスキップできるため、CI でも同じ検査を走らせるのが安全です。
name: Lint commits
on: [pull_request]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci
- name: Lint PR commits
run: npx commitlint --from=origin/${{ github.base_ref }} --to=HEAD --verbosesemantic-release で自動リリース
semantic-release を入れると、main への push をトリガーに次のすべてが自動化されます。
- コミットメッセージから次のバージョン番号を 自動決定
- CHANGELOG.md を自動生成
package.jsonのversionを書き換え- Git タグを作成して push
- GitHub / GitLab Release を作成
- npm に publish(必要なら)
npm install --save-dev semantic-release \
@semantic-release/changelog \
@semantic-release/git \
@semantic-release/github{
"release": {
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/changelog", { "changelogFile": "CHANGELOG.md" }],
"@semantic-release/npm",
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
}
}name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0, persist-credentials: false }
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci
- run: npm run build --if-present
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}これで、main に feat: ... をマージすればマイナーバージョンが上がり、fix: ... ならパッチが上がります。リリースノートは PR の本文ではなく、コミットメッセージから組み立てられるようになります。
モノレポなら Changesets が向くことも
複数パッケージを抱えるモノレポでは、バージョン上げを意図的にコントロールしたいケースが多く、その場合は Changesets のほうが向いています。Conventional Commits とは思想が少し違いますが、どちらも「コミット情報からリリースを組み立てる」点は共通です。
AI コーディングツールとの相性
Cursor、Claude Code、Copilot などの AI エディタは コミットメッセージの自動生成機能を持っており、デフォルトで Conventional Commits 形式を出力します。
- Cursor の
Generate Commit Message機能は、ステージされた diff からfeat(scope): ...形式のメッセージを推論 - Claude Code の
/commitコマンドも Conventional Commits を前提に動作 - GitHub Copilot Chat の
@github /commitも同じ
つまり、チームの規約として Conventional Commits を採用しておくと、AI が出してきたメッセージがそのまま使える確率が大幅に上がります。逆に、独自規約を採用すると AI が学習データから引っ張ってくる典型形と合わなくなり、毎回直すコストが発生します。
よくある質問
Q. コミットを type 別に分けるのが面倒なときは?
仕様の FAQ にある通り、可能な限り git rebase -i でコミットを分割します。AI ツールの Squash and split 機能を使ってもよいですが、最終的に main にマージされるコミットは1つの type に収まる粒度であるべき、という思想です。
Q. PR のタイトルだけ Conventional Commits にすればいい?
GitHub で Squash merge を使うチームなら、それで十分です。マージコミットのメッセージが PR タイトルから生成されるので、個々のコミットは雑でも、マージコミットだけ規約に準拠させれば自動化は回ります。semantic-release の世界では「コミット粒度で書く派」と「PR 粒度で書く派」が二大派閥。
Q. type を間違えてコミットしてしまった
main にマージ前なら git commit --amend か git rebase -i で書き換え。マージ・公開後は基本そのまま放置で OK。semantic-release の commit-analyzer で 過去コミットを再解釈することは普通やりません(リリース履歴の整合性が崩れるため)。
Q. 日本語と英語、どちらで書くべき?
社内専用ツールなら 日本語でまったく問題なし。OSS や海外利用者がいるなら 英語推奨。混在は最悪なので、リポジトリ単位で統一しましょう。commitlint.config.js の subject-case を緩めるかどうかで強制できます。
Q. revert はどう書く?
仕様では明示的なルールはなく、revert 型 + 元コミットの SHA を Refs: フッターに書くのが推奨されています。
revert: feat(auth): 一時的に2FAを無効化
Refs: 676104eGitHub の Revert ボタンが生成するコミットもこの形式に近いので、ボタン経由でやるのが楽です。
まとめ
- Conventional Commits は
<type>[(scope)]: <description>+ 任意の body / footer という、超軽量な規約 - 必須は
featとfixの2つだけ。それ以外は Angular 流のdocs/style/refactor/perf/test/build/ci/chore/revertがデファクト - 破壊的変更は
!をタイプ直後に付けるか、BREAKING CHANGE:フッターを書く(両方併用が一番親切) - SemVer と協調設計されており、
fix→ PATCH、feat→ MINOR、BREAKING CHANGE→ MAJOR が自動で決まる - commitlint + Husky でローカルとCIの両方で強制すれば、規約違反は実質ゼロにできる
- semantic-release でバージョン番号 / CHANGELOG / Release ノート / npm publish までフル自動化
- AI エディタ(Cursor / Claude Code / Copilot)と相性が良く、自動生成メッセージがそのまま使える
「コミットメッセージの規約」は地味ですが、半年後の自分・新しく入ってくるメンバー・自動化スクリプトの全員に効く投資です。新しいリポジトリを切るタイミング、または既存リポジトリで CHANGELOG を整備したくなったタイミングで、まず @commitlint/config-conventional を入れるところから始めるのがおすすめです。