Conventional Commits 徹底解説 - チームと自動化ツールに優しいコミットメッセージ規約

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: #123 Reviewed-by: Z BREAKING CHANGE: ... などのメタ情報

最小限のコミットなら次のように1行だけでも仕様準拠です。

最小例
docs: correct spelling of CHANGELOG

scope を付けると、どこを触ったかが一発で分かります。

scope ありの例
feat(parser): add ability to parse arrays
feat(lang): add polish language
fix(auth): handle expired refresh tokens

type の一覧

仕様で 必須として定義されているのは featfix の2つだけ。それ以外は推奨や慣例で、現在は Angular 流の type 一覧@commitlint/config-conventional のデフォルト)が事実上の標準として広く使われています。

type用途SemVer 影響
feat新機能の追加MINOR
fixバグ修正PATCH
docsドキュメントだけの変更(README、JSDoc など)なし
styleコードの意味に影響しない変更(空白、フォーマット、セミコロン等)なし
refactorバグ修正でも機能追加でもないコード変更なし
perfパフォーマンス改善PATCH 扱いが一般的
testテストの追加・修正なし
buildビルドシステム・外部依存の変更(npm、bundler など)なし
ciCI 設定・スクリプトの変更(GitHub Actions、CircleCI 等)なし
choreその他の雑多な変更(ライセンス、リネーム等)なし
revert過去のコミットの取り消し取り消し対象に依存

注意: 仕様の必須は featfix だけで、上記の他の type は BREAKING CHANGE を含まない限り SemVer に対する暗黙的な効果を持ちませんsemantic-release などのツールは「fix で PATCH、feat で MINOR、BREAKING CHANGE で MAJOR」を読み取って自動的にバージョンを上げます。

type の選び方の指針

迷ったときの判断軸:

  • ユーザー(呼び出し側)から見た振る舞いが変わる? → 変わるなら featfix、変わらないなら 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 version

scope の値はチームで自由に決めて構いませんが、よくあるパターンは以下:

  • モノレポのパッケージ名: feat(web): fix(api): chore(shared):
  • 機能ドメイン: feat(auth): fix(billing): refactor(search):
  • ファイル名・モジュール名: feat(parser): fix(router):

scope は 省略可能です。プロジェクト全体に関わる変更や1スコープに切り出しにくい変更では、思い切って省略するほうが綺麗です。

description の書き方

description(タイトル)は コード変更の短い要約。読みやすさのために英語圏では以下が推奨されています。

  • 命令形(imperative mood): add fix remove で書き始める。added fixes ではない
  • 小文字始まり: feat: addfeat: Add ではない)
  • 末尾にピリオドを付けない: fix: handle nullfix: handle null. ではない)
  • 50〜72文字以内を目安に

日本語で書くケースも増えており、その場合のおすすめは:

  • 体言止め or 名詞句で短く(「〜の追加」「〜を修正」)
  • 50文字程度を上限の目安に
日本語の例
feat(api): 一括インポートエンドポイントを追加
fix(ui): Safariでドロップダウンの位置がずれる問題を修正
docs(readme): 最低Node.jsバージョンを追記

英日どちらで書くかは、プロジェクトで統一するのが最重要。混在させると commitlintsubject-case ルールなどでブレが出ます。

詳細が必要なときは、タイトルの 下に空行を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: #123

body の下にさらに空行を1行挟んでから footer を書きます。footer は Token: 値 または Token #値 の形式で、Git の trailer 規約に揃えてあります。

代表的な footer:

Token用途
Refs: #123Issue / 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:省略できます(その代わりタイトルで破壊的変更の内容が分かるように書く必要があります)。

footer で詳しく説明
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 で詳細を書くのが一番親切です。

! と BREAKING CHANGE を併用
feat!: drop support for Node 6
 
BREAKING CHANGE: use JavaScript features not available in Node 6.

semantic-release はこの両方の表記を認識して、自動的に MAJOR1.x.x2.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.31.2.4fix(auth): handle expired tokens
feat:MINOR(1.2.31.3.0feat(api): add bulk export
feat!: / BREAKING CHANGE:MAJOR(1.2.32.0.0feat!: 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 を採用するメリットを整理すると、

  1. CHANGELOG の自動生成: conventional-changelog でコミットから整形された CHANGELOG.md を生成
  2. バージョンの自動決定: semantic-releasefeat → MINOR、fix → PATCH、BREAKING CHANGE → MAJOR と自動判定
  3. CI/CD トリガー: 「feat を含むタグでステージング、feat! ならカナリアリリース」など、コミット内容で動作を分岐
  4. PR レビュー効率化: PR タイトルが Conventional Commits になっていれば、レビュー範囲を一目で把握できる
  5. 将来の自分への取扱説明書: 半年後に git log を遡るときの可読性が桁違い
  6. AI による自動コミット: Cursor、Claude Code、Copilot などの AI ツールがコミットメッセージを生成するときの 共通の作法になっている

commitlint + Husky で強制する

「規約はあっても、人間は忘れる」を解決するのが commitlintHusky の組み合わせです。

インストール

commitlint と Husky のインストール
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky

commitlint の設定

commitlint.config.js を用意します(CommonJS / ESM どちらでも可)。

commitlint.config.js
/** @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 のセットアップ

Husky 初期化と commit-msg フック登録
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 でも同じ検査を走らせるのが安全です。

.github/workflows/commitlint.yml
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 --verbose

semantic-release で自動リリース

semantic-release を入れると、main への push をトリガーに次のすべてが自動化されます。

  1. コミットメッセージから次のバージョン番号を 自動決定
  2. CHANGELOG.md を自動生成
  3. package.jsonversion を書き換え
  4. Git タグを作成して push
  5. GitHub / GitLab Release を作成
  6. npm に publish(必要なら)
セットアップ
npm install --save-dev semantic-release \
  @semantic-release/changelog \
  @semantic-release/git \
  @semantic-release/github
package.json (一部)
{
  "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"
    ]
  }
}
.github/workflows/release.yml
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 }}

これで、mainfeat: ... をマージすればマイナーバージョンが上がり、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 --amendgit rebase -i で書き換え。マージ・公開後は基本そのまま放置で OK。semantic-releasecommit-analyzer過去コミットを再解釈することは普通やりません(リリース履歴の整合性が崩れるため)。

Q. 日本語と英語、どちらで書くべき?

社内専用ツールなら 日本語でまったく問題なし。OSS や海外利用者がいるなら 英語推奨。混在は最悪なので、リポジトリ単位で統一しましょう。commitlint.config.jssubject-case を緩めるかどうかで強制できます。

Q. revert はどう書く?

仕様では明示的なルールはなく、revert 型 + 元コミットの SHA を Refs: フッターに書くのが推奨されています。

revert: feat(auth): 一時的に2FAを無効化
 
Refs: 676104e

GitHub の Revert ボタンが生成するコミットもこの形式に近いので、ボタン経由でやるのが楽です。

まとめ

  • Conventional Commits は <type>[(scope)]: <description> + 任意の body / footer という、超軽量な規約
  • 必須は featfix の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 を入れるところから始めるのがおすすめです。

参考リンク