Conventional Commits(コンベンショナル・コミット)は、Gitのコミットメッセージを type(scope): description の形式で書くための規約です。先頭の feat や fix を機械が読めるので、次のバージョン番号とCHANGELOGを履歴から自動で決められます。この記事では、仕様v1.0.0の形式とtype一覧を整理したうえで、commitlint 21・husky 9でコミット時に検査する手順と、semantic-releaseの既定設定では feat!: が破壊的変更として扱われない落とし穴を、手元で動かした結果とともに説明します。
まとめ:Conventional Commitsの形式と導入の要点
- 形式は
type(scope): description。必須はtypeとコロン+半角スペースと説明文だけで、scope・本文・フッターは任意。 - 仕様が意味を決めているtypeは
feat(MINOR)とfix(PATCH)の2つだけ。docs・chore・refactorなどは@commitlint/config-conventionalが採用している慣習で、破壊的変更を示さない限り、仕様上のバージョン更新には対応しない。 - 破壊的変更は
feat!:の!か、フッターのBREAKING CHANGE:で示す。どちらもMAJORに対応し、フッターではBREAKING-CHANGEも同じ意味。 - 検査はcommitlint+huskyのcommit-msgフック。commitlint 21はNode.js 22.12.0以上が必要で、設定ファイルは
commitlint.config.mjsにすると読み込みエラーを避けられる。 - semantic-releaseは既定のままだと
!を読まない。commit-analyzerの既定presetがangularのためで、preset: "conventionalcommits"を指定する。 - リリース自動化を使わない個人開発では、形式を守る手間に見合う効果は小さい。
Conventional Commitsとは:仕様v1.0.0の位置づけと読み方
Conventional Commitsは、コミットメッセージに機械可読な構造を持たせるための軽量な規約です。仕様はconventionalcommits.org の v1.0.0で公開されており、ライセンスはCC BY 3.0です。読み方は「コンベンショナル・コミッツ」または「コンベンショナル・コミット」で、Conventionalは「慣例に従った」という意味です。「カンバセーショナル」と読むのは誤りです。
仕様が目的として挙げているのは、CHANGELOGの自動生成、コミットのtypeに基づくバージョン番号の自動決定、変更内容を利用者に伝えること、ビルドや公開処理の起動などです。つまり人間が読みやすいことより、履歴を後段のツールの入力にできることに価値があります。コミットそのものの仕組みや1コミットの粒度の決め方はコミットとは?Gitが変更履歴を記録する仕組みと粒度・メッセージ設計で扱っています。
コミットメッセージの形式:ヘッダー・本文・フッター
メッセージは1行目のヘッダー(必須)、空行を挟んだ本文(任意)、さらに空行を挟んだフッター(任意)で構成します。
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
ヘッダー:type・scope・descriptionの書き方
typeの直後に、任意のscopeを丸括弧で付け、コロンと半角スペースを挟んで説明文を書きます。仕様はscopeを「コードベースの区画を表す名詞」と定めています。
feat(parser): add ability to parse arrays
fix: array parsing issue when multiple spaces were contained in string
docs: correct spelling of CHANGELOG
コロンの後のスペースは必須で、feat:add のように詰めると規約外になります。説明文の長さや言語は仕様では決まっていません。後述するcommitlintの既定ルールでは、ヘッダー全体の上限が100文字で、説明文の末尾のピリオドと先頭の大文字はエラーになります。
本文とフッター:トークンの規則とIssue参照
本文には変更の理由を自由に書けます。フッターは本文の後に空行を1行挟み、トークン: 値 または トークン #値 の形で並べます。トークン内の空白は、例外の BREAKING CHANGE を除き、Reviewed-by のようにハイフンに置き換えます。これはGitのtrailerの書き方にならったもので、複数段落の本文とフッターを機械が区別するための規則です。
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Reviewed-by: Z
Refs: #123
Refs: #123 はIssueとの関連付けに使う慣習です。GitHubでは、コミットメッセージに Closes #123 を含むコミットをデフォルトブランチへマージするとIssueが閉じますが、これはGitHub側の機能で、Conventional Commitsの仕様とは別物です。
破壊的変更の書き方:!とBREAKING CHANGE
後方互換性を壊す変更は、typeまたはscopeの直後に ! を付けるか、フッターに BREAKING CHANGE: を書いて示します。! を付けた場合はフッターを省略でき、その場合は説明文で何が壊れるかを伝えます。
feat(api)!: send an email to the customer when a product is shipped
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files
仕様の細則で見落とされやすいのは次の2点です。
- 大文字小文字:実装はtypeなどの要素の大文字小文字を区別してはならない(MUST NOT)。例外は
BREAKING CHANGEで、フッターでは大文字で書く。 - ハイフン版:フッターのトークンとしては
BREAKING-CHANGEもBREAKING CHANGEと同じ意味に解釈しなければならない(MUST)。
ただし、主要なツールはこの2点を仕様どおりには扱いません。具体的な挙動は後半の「semantic-releaseのpreset」の章で示します。
type一覧:仕様が決めるのはfeatとfixだけ
仕様(v1.0.0)が意味を定めているtypeは feat と fix だけです。その他のtypeは仕様で禁止されておらず、@commitlint/config-conventional がAngularの規約をもとに11種類を許可リスト(type-enum)にしています。なおAngular本家のガイドラインが挙げるtypeはbuild・ci・docs・feat・fix・perf・refactor・testの8種類で、chore・style・revertは含みません。この記事では、@commitlint/config-conventionalの既定設定で許可される11種類を紹介します。
| type | 対象の変更 | semantic-release既定のリリース |
|---|---|---|
| feat | 新機能の追加 | minor |
| fix | バグ修正 | patch |
| perf | 性能の改善 | patch |
| revert | 過去のコミットの取り消し | revertとして解析された場合はpatch |
| refactor | 機能もバグも変えない内部の書き換え | なし |
| docs | ドキュメントだけの変更 | なし |
| style | 空白・整形など意味を変えない変更 | なし |
| test | テストの追加・修正 | なし |
| build | ビルドの仕組み・依存関係 | なし |
| ci | CIの設定・スクリプト | なし |
| chore | 上記に当てはまらない雑務 | なし |
右列は@semantic-release/commit-analyzerの既定ルールによる扱いです。仕様はperfやrevertのバージョンへの影響を定めていません。既定ルールではperfと、パーサーがrevertとして認識したコミットがpatchの対象です。どのtypeでも、パーサーが破壊的変更として認識すればmajorになります。
迷いやすいtypeの切り分け
判断がぶれるのは、利用者から見た動作が変わるかどうかの境界です。次の基準で分けると、チーム内の表記ゆれが減ります。
- fixとrefactor:誤った動作を正したならfix。動作が同じで書き方だけ変えたならrefactor。
- styleとrefactor:フォーマッタを当てただけならstyle。変数名の変更や関数の分割はrefactor。
- buildとchore:依存パッケージの更新やバンドラ設定はbuild。
.gitignoreの修正などどれにも当たらないものはchore。 - ciとbuild:GitHub Actionsのワークフローファイルの変更はci。
fix(cart): prevent negative quantity on update
refactor(cart): extract price calculation into a helper
build(deps): update eslint and prettier
ci: cache npm dependencies in test workflow
1つの変更が2つのtypeにまたがる場合、仕様のFAQは「可能な限りコミットを分割する」ことを勧めています。
scopeの決め方とrevertの書き方
scopeは任意です。モジュール名やパッケージ名を入れておくと、CHANGELOGや git log --grep で範囲を絞り込めます。scopeの候補はチームで一覧にし、commitlintの scope-enum ルールで固定すると表記ゆれを防げます。モノレポではパッケージ名をscopeにするのが一般的です。
revertの扱いを仕様は定めていません。仕様のFAQは、revert typeを使い、取り消すコミットのSHAをフッターの Refs: で示す方法を勧めています。
revert: let us never again speak of the noodle incident
Refs: 676104e, a215868
この推奨形は、semantic-releaseではrevertとして扱われません。commit-analyzer 13.0.1で解析すると、angularとconventionalcommitsのどちらのpresetでも「リリースなし」でした。patchになったのは、本文に git revert が既定で書く This reverts commit <SHA>. の行を残したコミットです。取り消しをリリースに反映させたいなら、この行を消さずにコミットします。
SemVerとの対応:featはMINOR、fixはPATCH
Conventional CommitsはSemVer(MAJOR.MINOR.PATCH)と組み合わせる前提で設計されています。前回のリリース以降のコミットを走査し、最も大きい変更で次の版を決めます。
| 前回リリース以降のコミット | 上がる桁 | 例 |
|---|---|---|
| fixだけ | PATCH | 1.2.3 → 1.2.4 |
| featを1件以上含む | MINOR | 1.2.3 → 1.3.0 |
| 破壊的変更を1件以上含む | MAJOR | 1.2.3 → 2.0.0 |
開発初期のコミットについて、仕様のFAQは「すでに製品をリリースしているかのように扱う」ことを勧めています。SemVerそのものの規則や、APIのバージョン設計との違いはバージョニングとは?セマンティック/APIバージョニングの意味と運用設計で詳しく説明しています。
1.0.0未満の期間の扱いはツールによって違います。release-please 17.11.2の標準設定では0.x系でもfeatはminorを上げ、bump-patch-for-minor-pre-major を true にするとpatchに抑えられます(manifest-releaserのドキュメント)。
commitlintとhuskyによるコミットメッセージの検査
口頭で共有した規約だけでは、形式違反を自動で検出できません。commitlintでメッセージを検査し、huskyでGitのcommit-msgフックに組み込むと、規約外のメッセージはコミットの時点で止まります。以下は2026年10月29日時点の正式版(@commitlint/cli 21.2.4、husky 9.1.7)で確認した手順です。コマンドはGit管理下のnpmプロジェクトで、macOSまたはLinuxのシェルから実行します。
commitlintのインストールと.mjs設定ファイルの作成
npmレジストリ上の@commitlint/cli 21.2.4は engines に node >=22.12.0 を指定しています。Node.js 20のCIはこの条件を満たさないので、先にNode.jsの版を確認します。
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs
commitlintの公式手順は同じ内容を commitlint.config.js に書くよう案内しています。しかし npm init -y で作ったpackage.json(npm 11.17.0では "type": "commonjs" が入る)のままだと、コミット時に SyntaxError: Unexpected token 'export' で検査そのものが失敗しました。拡張子を .mjs にするか、package.jsonに "type": "module" を指定すれば読み込めます。
commit-msgフックの追加と動作確認
husky 9は husky init で .husky/ と prepare スクリプトを作ります。続けてcommit-msgフックにcommitlintの呼び出しを書きます。
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
husky init は npm test を実行するpre-commitフックも作ります。npm init -y が作る既定のtestスクリプトは exit 1 で終わるため、テストを用意していないリポジトリではコミットが毎回失敗します。不要なら .husky/pre-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
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky - commit-msg script failed (code 1)
config-conventionalの既定ルールで落ちるメッセージ
@commitlint/config-conventionalは、type-enumのほかに header-max-length(100文字)、subject-case、subject-full-stop などをエラーに設定しています。手元で9通りのメッセージを検査した結果は次のとおりです。
| メッセージ | 結果 | 違反したルール |
|---|---|---|
| feat: add login | 通過 | – |
| feat: Add login | エラー | subject-case |
| feat(auth): ログイン機能を追加 | 通過 | – |
| feat: ログイン機能を追加。 | 通過 | – |
| feat!: drop v1 | 通過 | – |
| Feat: add | エラー | type-case・type-enum |
| feature: add | エラー | type-enum |
| wip: tmp | エラー | type-enum |
| feat:add | エラー | type-empty・subject-empty |
英語で書くチームが最初につまずくのは subject-case です。説明文を大文字で始めると落ちるので、add のように小文字の動詞で始めます。日本語の説明文は大文字小文字の判定を受けず、句点「。」も subject-full-stop(対象はピリオド)に引っかかりません。句点を付けるかどうかはチームで決めておきます。
CIによるPull Request内のコミット検査
フックは git commit --no-verify で飛ばせます。そのため、CIでも同じ検査を回します。commitlintのCI設定ガイドにあるGitHub Actionsの例は、Pull Requestの差分にあるコミットをまとめて検査します。
以下は、pull_request を起動条件にしたGitHub Actionsワークフローの、ジョブ内の steps に置く抜粋です。
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: npm ci
- name: Validate PR commits with commitlint
if: github.event_name == 'pull_request'
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
fetch-depth: 0 を省くと、比較元のコミットが取得されず範囲を検査できません。ワークフローの書き方そのものはGitHub Actionsとは?できること・使い方と最初のワークフローを参照してください。
semantic-releaseのpreset:angularによるfeat!の解析制限
commitlintが通したメッセージでも、リリースツールが同じように読むとは限りません。semantic-releaseでコミットを解析する@semantic-release/commit-analyzerは、preset を指定しないと angular を使います。angularのパーサーはヘッダーを ^(\w*)(?:\((.*)\))?: (.*)$ で読むため、! を含むヘッダーはtypeとして解釈されません。
@semantic-release/commit-analyzer 13.0.1で、angular 9.4.0とconventionalcommits 10.4.1のpresetを切り替えた結果が次の表です。表中の「+」は空行を挟んだフッターの追加を表します。
| コミット | angular(既定) | conventionalcommits |
|---|---|---|
| feat!: drop node 18 | リリースなし | major |
| feat(api)!: drop v1 | リリースなし | major |
| feat: x + BREAKING CHANGE: y | major | major |
| feat: x + BREAKING-CHANGE: y | minor | major |
| perf: speed | patch | patch |
| Feat: Upper | リリースなし | リリースなし |
既定のままでは、feat!: で書いた破壊的変更がリリースされず、ハイフン版のフッターはminorで出てしまいます。先頭だけ大文字の Feat: はどちらのpresetでも無視されました。既定ルールには全大文字の FEAT(minor)と FIX(patch)だけが登録されているためで、仕様の「大文字小文字を区別してはならない」とは合っていません。! を使うなら、設定で conventionalcommits を明示します(presetのパッケージ conventional-changelog-conventionalcommits も devDependencies に入れます)。
以下をリポジトリ直下の .releaserc.json に保存します。
{
"branches": ["main"],
"plugins": [
["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }],
"@semantic-release/npm",
"@semantic-release/github"
]
}
大文字小文字の扱いはツールのルールに依存するため、この記事の運用ではcommitlintの type-case で小文字に統一します。commit-analyzerとrelease-notes-generatorのpresetをそろえないと、版は上がったのにリリースノートに破壊的変更が載らない、という食い違いが起きます。
リリース自動化ツールの選び方:semantic-release・release-please・commit-and-tag-version
Conventional Commitsの履歴から版とCHANGELOGを作るツールは、公開までを全自動にするか、人が確認してから出すかで選びます。
| ツール | 最新版(2026-10-29) | 動き方 | 向く場面 |
|---|---|---|---|
| semantic-release | 25.0.9 | CIで版決定・タグ・公開まで実行 | npmライブラリの全自動公開 |
| release-please | 17.11.2(Action v5) | リリース用PRを作り、マージ後にGitHub Releaseを作成 | 公開前に人が確認したい |
| commit-and-tag-version | 13.3.0 | 手元のコマンドで版・タグ・CHANGELOG | GitHub Actionsを使えない環境 |
semantic-release 25.0.9は node ^22.14.0 || >=24.10.0 を要求します。release-pleaseは feat!: や fix!: をmajorとして扱うとREADMEに明記しており、squashマージの利用を強く勧めています。以前よく使われたstandard-versionは、リポジトリのREADMEで非推奨と告知され、GitHub利用者にはrelease-please、それ以外にはフォークのcommit-and-tag-versionが案内されています。作ったタグとリリースノートをGitHub上でどう公開するかはGitHub Releasesの使い方にまとめています。
間違ったtypeを直す方法とsquashマージでの運用
仕様のFAQは、マージやリリースの前なら git rebase -i で履歴を書き換えて直すことを勧めています。直前のコミットだけなら git commit --amend で十分です。リリース後に見つけた場合の直し方は、使っているツール次第です。
仕様は、全員がこの規約でコミットする必要はないとも書いています。squashマージを使えば、マージする人が最後に1つのメッセージを整えればよいからです。GitHubでは、squashマージ時の既定のコミットメッセージを「Pull Requestのタイトル」にする設定があります(リポジトリ設定のPull Requests欄)。この設定にした場合、検査すべきなのは個々のコミットではなくPull Requestのタイトルです。タイトルの検査にはamannn/action-semantic-pull-request(v6.1.1)などのActionを使います。squashマージの仕組みはgit squashとは?、Pull Requestのマージ方式の選び方はGitHubのPull Requestの作り方で説明しています。
形式を覚えきれないメンバーがいる場合は、commitizen(4.3.2)を使うと、typeやscopeを選択肢から選んでメッセージを組み立てられます。公式READMEの手順は、CLIを入れたあとリポジトリでアダプターを設定し、git cz でコミットする流れです。
npm install -g commitizen
commitizen init cz-conventional-changelog --save-dev --save-exact
git cz
導入を見送るべき場面と失敗パターン
この記事では、リリース自動化やCHANGELOG生成を導入判断の軸にします。履歴を人が読みやすくする用途もありますが、その目的だけなら、まず短く具体的な説明文を優先する方針です。
- リリース自動化もCHANGELOG生成もしない個人開発:導入しない。
git logを人が読むだけなら、短く具体的な説明文で足りる。 - commitlintを入れず口頭のルールで始める:規約外のコミットはツールに無視され、CHANGELOGから黙って漏れる。導入するならフックとCIの検査を同じ日に入れる。
- semantic-releaseのpresetを既定のまま
!を使う:破壊的変更がリリースされない。前章のとおりconventionalcommitsを明示する。 - typeを自由に増やす:
updateやwipを足すと境界が曖昧になる。追加はtype-enumに登録し、リリースへの影響を決めてからにする。
複数人でライブラリを保守し、定期的にリリースするプロジェクトでは、版の判断とリリースノート作成の手作業がなくなるので導入する価値があります。ブランチ戦略や命名規則まで含めたチームのGit運用はGit運用のベストプラクティス、CI/CDの構築まで任せたい場合はDevOps・CI/CD導入支援も参考にしてください。
よくある質問
Conventional Commitsの形式とは何ですか?
1行目を type(scope): description で書く形式です。必須はtypeとコロン+半角スペースと説明文で、scope、空行の後の本文、フッターは任意です。
typeはfeatとfix以外に何がありますか?
@commitlint/config-conventionalが許可するbuild・chore・ci・docs・perf・refactor・revert・style・testが広く使われています。仕様が用途を定めているtypeはfeatとfixだけです。そのほかのtypeも、破壊的変更を示す場合はMAJORに対応します。
コミットメッセージは日本語で書いてもよいですか?
説明文と本文は日本語で書けます。typeは feat などの英単語のまま残します。commitlintの既定ルールでも日本語の説明文は通ります。
typeは大文字で書いてもよいですか?
仕様上は大文字小文字を区別しない決まりですが、commitlintの既定ルールはエラーにします。semantic-releaseの既定ルールも Feat: のような先頭だけ大文字のtypeは解析しません。小文字で統一してください。
破壊的変更はどう書けばよいですか?
feat!: のようにtypeの直後に ! を付けるか、フッターに BREAKING CHANGE: 内容 を書きます。semantic-releaseで ! を使う場合は、presetを conventionalcommits にしておく必要があります。