Install
npx skillscat add kasiopeiya/claude-dev-template/git-commit Install via the SkillsCat registry.
Git Commit Helper
git の変更内容を分析し、適切なコミットメッセージを生成し、自律的にコミットを実行するスキルです。サブエージェントを使わず、このスキル内で完結して処理します。
- 対象範囲はデフォルトでワーキングツリー・staged 双方の全差分。引数
work(ワーキングツリーのみ)/stage(stagedのみ)で範囲を限定できる - そのセッションで編集したかどうかは問わず、リポジトリの現在の git 状態にある差分ファイルすべてが対象。ただし並行する別セッションを検出したときは、このセッションで編集したファイルだけに絞る(Phase 1)
- ブランチは確認しない。main ブランチでも確認なしでコミットしてよい
- コミット実行前のユーザー承認は求めない。コミット分割は Phase 2.5 の機械的な規則(type×ディレクトリ)だけで決め、その規則で1通りに定まらないときだけ
AskUserQuestionで確認する git rebaseは使用しない(マージには 3-way merge または fast-forward を使う)。履歴の書き換えがバグ特定を困難にするためである
コミットのルール
commit・branch 操作をする前に従うルール。
コミットの書き方
コミットメッセージには変更の理由・背景を書く。
「何を変えたか」はコードを読めばわかる。「なぜ変えたか」はコードからは読み取れない。その情報をメッセージに残す。
# Bad: 何をしたかしか書かれていない
パスワードバリデーションを追加
# Good: なぜ必要だったかが書かれている
パスワードバリデーションを追加
短いパスワードによる不正アクセスのリスクを下げるため、
8文字以上を必須とするバリデーションを実装した。issue対応時のコミットメッセージ
コミットは、件名の末尾に (#issue番号) を付与する。
形式は type: 説明 (#issue番号) とする。
# Bad: どのissue対応か追跡できない
fix: ログイン失敗時のエラーメッセージを修正
# Good: 件名にissue番号があり、対応関係を追跡できる
fix: ログイン失敗時のエラーメッセージを修正 (#42)issue番号を記録するのは、コミットとissue(背景・議論・受け入れ条件)を双方向に辿れるようにするためである。
なお、Closes #42 のようなクローズ用キーワードは、コミットメッセージの件名・本文に書かない。本プロジェクトの開発フローではissueのタスク一覧(チェックリスト)を1つずつ進める運用であり、コミット単位でissueを自動クローズすると作業途中で閉じてしまうためである。
この禁止が掛かるのはコミットメッセージだけである。PR本文には Closes #42 を書いてよい。 PRのマージは作業が完了した時点なので、途中で閉じてしまう問題が起きない。
「なぜ」をどこに書くか(本文とissueの使い分け)
「なぜ変えたか」を残す場所は、コミット本文 と issue の2つがある。件名(type: 説明)はどの場合も必須である。
| issueの書かれ方 | 「なぜ」の置き場所 | コミット本文 |
|---|---|---|
| issueの「この変更が必要な理由」節に、この変更の理由が書かれている | issue | 省略可((#番号) で辿る) |
| issueに無い判断をした | issue+コミット | その判断の理由を書く |
# Good: issueに背景があり、本文は省略
fix: ログイン失敗時のエラーメッセージを修正 (#42)
# Good: issueに無い「実装上の判断」を本文に残す
fix: ログイン失敗時のエラーメッセージを修正 (#42)
ライブラリXのバリデーションは多言語対応が不十分なため、
issueの方針とは別に自前のメッセージ生成へ差し替えた。この使い分けが成立する前提は、上記の表の1行目の条件を満たすことである。満たさない場合は、本方式は理由がどこにも残らない状態になるため、本文に書く。
決定記録を残すコミット
コミットに含まれる決定が、docs/policy/adr-policy.md の判定で「コミットの決定記録」になるときは、本文に決定・理由・却下した案を書き、最後のトレーラーの段落に Decision-Record: yes を足す。Issue の「対応方針」の書き先に決定記録と書かれていれば、それに従う。このトレーラーの付いたコミットは、/past-decision-search が git log から引く。トレーラーの段落以外に Decision-Record: yes の行を書かない。
feat: 一覧 API のページングをカーソル方式にする (#123)
決定: 一覧 API のページングは、オフセットではなくカーソル(最後に返した行のキー)で行う。
理由: 一覧の途中で行が追加・削除されると、オフセット方式では行の重複や抜けが起きる。
却下した案:
- オフセット方式:件数が増えるほど遅くなり、途中の追加・削除で重複や抜けが起きる
Decision-Record: yesコミットの粒度
粒度は基本的に任意とする。ただし、コミットメッセージと無関係なファイルを含めてはならない。気づいた割れ窓は直すか起票するが、無関係な変更を1つのコミットに詰めるのは別問題である(「割れ窓を放置しない」refined-engineer-judgment-principles)。
# Bad: ログイン修正とは無関係なCSSの変更が混入している
fix: ログイン失敗時のエラーメッセージを修正
- src/auth/login.ts
- src/styles/global.css ← 関係ない
# Good: メッセージに関係するファイルだけが含まれている
fix: ログイン失敗時のエラーメッセージを修正
- src/auth/login.tsブランチ命名規則
ブランチ名は <prefix>/<説明> の形式とする。
feat/:新機能・機能変更fix/:バグ修正refactor/:振る舞いを変えない構造の変更chore/:設定変更・依存関係の更新などci/:CI/CD・GitHub Actionsの変更test/:テストの追加・修正docs/:ドキュメントの変更
処理フロー
Phase 1:対象範囲の決定と変更内容の取得
引数に応じて対象範囲を決定する:
- なし(デフォルト):ワーキングツリー・staged 双方の差分
work:ワーキングツリー(未ステージ)の差分のみstage:staged の差分のみ
並行セッションを検出する
同じ作業ツリーを別の Claude セッションが編集していると、既定スコープは無関係な変更まで巻き込む。巻き込まれた側は自分の作業が別 Issue のコミットに紛れたことに気づけない。そこで、範囲を決めたらまず同じ作業ツリーで動いている claude セッションの数を数える。
top=$(git rev-parse --show-toplevel)
if command -v lsof >/dev/null 2>&1; then
ps -eo pid=,comm= | awk '$2 == "claude" {print $1}' | while read -r pid; do
cwd=$(lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p')
[ -n "$cwd" ] && [ "$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)" = "$top" ] && echo "$pid"
done | wc -l
else
npm run -s --prefix "$top" count-registered-sessions
fi2以上なら並行セッションがある。0 は判定できなかったことを示すので、2以上と同じく扱う。このとき既定スコープを「このセッションで自分が Edit/Write したファイルのみ」に切り替える(引数 work / stage の指定も、この絞り込みの中で適用する)。切り替えたことと、除外したファイルのパスを Phase 5 の出力に列挙する。
1なら単独セッションなので、既定スコープをそのまま使う。
範囲を決めたら現在の状態を把握する。スコープに不要なコマンドは実行しない(work なら staged 側、stage なら worktree 側は取得不要)。依存関係のないコマンドは1回の Bash 呼び出しにまとめる:
git status --short # 常に取得
git diff --stat # スコープが「なし」または work のときのみ
git diff --cached --stat # スコープが「なし」または stage のときのみgit diff(内容)と git log --oneline は無条件に取得しない。Phase 2 の type 判定・Phase 3 の本文執筆で実際に必要になった時点で、必要な対象だけに絞って取得する(type がファイル名だけで判定できるならどちらも不要)。
ステータス出力の先頭2文字でステージ状態・変更タイプを判定する:
M file.ts # Modified(ステージ済み)
M file.ts # Modified(未ステージ)
A file.md # Added(ステージ済み)
?? file.txt # UntrackedPhase 2:Commit Type の判定
type の語彙は上記「ブランチ命名規則」の prefix 一覧に従う(末尾の / を除いたものが type)。まずファイル名・パス・ステータス(A/M)だけで判定を試み、それで確定しない場合のみ diff 内容を取得する:
- テストファイル(
*.test.*,*.spec.*)のみ:test docs/または*.mdのみ:docs- 設定ファイル(
package.json,tsconfig.json,*.config.*など)のみ:chore - CI・GitHub Actions の変更:
ci - ソースの新規追加(A)による機能拡張:
feat - ソースの修正(M)によるバグ修正:
fix - 振る舞いを変えない構造変更:
refactor
A=feat・M=fix はあくまで目安。修正が機能追加のこともあるため、ファイル名だけでは判断がつかない場合に限り diff 内容(該当ファイルのみ)を読む。それでもdiff 内容が type 一覧(feat/fix/refactor/docs/chore/ci/test)のどれとも一致しない、または複数の type に同時に当てはまるときは AskUserQuestion でユーザーに確認する。
Phase 2.5:コミット分割の判定
上記「コミットの粒度」(無関係なファイルを混ぜない)を実践する。ファイルを type × トップレベルディレクトリでグループ化し、2グループ以上に分かれる場合は分割して進める:
- 異なる type の混在(例:
docsとfeat)→ type ごとに分割 - 異なるトップレベルディレクトリの混在 → ディレクトリごとに分割
- 同一 type・同一ディレクトリ → 原則1コミット
同じファイルが複数の type に同時に当てはまり、上記の規則で1グループに定まらないときだけ AskUserQuestion で確認してよい。
Phase 3:メッセージの生成
上記「コミットのルール」に従って組み立てる:
- 件名:
type: 説明。説明は変更内容が伝わるよう簡潔に(目安30文字以内)。 - issue番号:件名末尾に
(#番号)を付与する。番号はブランチ名等から推定し、不明な場合はユーザーに確認する。 - 本文(なぜ):本文を書くかどうかは上記「『なぜ』をどこに書くか」の表で判断する。書く場合はコードから読み取れない背景・判断理由を記す。決定記録にあたる決定を含むなら、上記「決定記録を残すコミット」の書式で書く。
Phase 4:ステージング対象の決定
- Phase 1 で決定した範囲(デフォルト=work+stage全体、または
work/stage指定範囲)に含まれるソース・ドキュメント・設定・テストの各変更ファイルを対象に含める。単独セッションなら、セッション内で編集したかどうかは問わない。 - 除外して警告表示:
.env*,node_modules/,dist/・build/,.DS_Store/Thumbs.db,*.log。並行セッションを検出したときは、このセッションで Edit/Write していないファイルも除外して警告表示する。 - デフォルト範囲かつ既にステージ済みのファイルは対象に含め、確認表示では
✓を付ける。
Phase 5:コミット実行
ユーザー承認は求めず、自律的に実行する。グループごとに、次の手順を順に行う。
1. ステージする:Phase 4 で決めた対象ファイルのパスを明示して git add <パス>... する(-A や . を使わない)。
2. index を突き合わせる:ステージ済みの一覧を取り、Phase 4 の対象リストと一致するか確認する。
git diff --cached --name-only- 対象外のパスが混じっている:別セッションが staged にしたもの。
git resetで外さない(相手の index を壊す)。手順3のパス指定でコミット対象から外れるので、除外したパスとして出力に列挙する - 対象のパスが出てこない:別セッションが先にコミットした可能性がある。
git log --oneline -3とgit show --stat <hash>で取り込み済みか確かめ、済んでいればグループから外す
3. コミットする:対象パスを -- の後ろに並べ、index 全体ではなくそのパスだけをコミットする。
git commit -m "<件名>" -- <対象パス>...パス指定を省くと、index に載った他セッションの変更ごとコミットされる。Phase 1 でスコープを正しく絞っても実行段階で巻き込むので、ここは AI の判断に委ねず常にパスで縛る。
このコマンドは index を経由せず作業ツリーの内容をコミットする。手順1のステージ後にファイルを直していると、index には古い内容が残り、次のコミットがそれを拾う。コミット後に git status --short を見て、対象ファイルが残っていたら git add <パス> で index を揃える。pre-commit hook が再生成して再ステージする docs/adr/ 配下のファイルは別で、コミット後に MM で残る。hook の git add が効くのは一時 index だけだからである。作業ツリーは hook が再生成した最新で HEAD と同じだから、git restore --staged <パス> で index だけを HEAD に戻す。
ファイルの一部だけをステージしているときは、pre-commit hook がそのファイルの prettier 整形と再ステージを見送る(git add <パス> が未ステージの変更ごとコミットに混ぜてしまうため)。整形の取りこぼしは、push 前の pre-push hook(npm run format:check)が止める。
=== Git Commit Summary ===
Commit Type: feat
Message: feat: アカウント同期処理の実装 (#42)
Committed Files:
✓ src/sync.ts
Excluded (warnings):
⚠ .env is ignored (environment file)
⚠ docs/guide/foo.html — 並行セッションを検出したため除外(このセッションで編集していない)分割する場合は各コミットの Type・Message・対象ファイルを順に列挙する。
- 複数コミットの途中で失敗(pre-commit hook 等)した場合、成功済みコミットはロールバックしない。失敗したグループはステージされたまま残し、原因と再実行を促す。
- 完了後
git log --oneline -n <件数>で結果を表示する。
使用方法
/git-commit # デフォルト:ワーキングツリー+staged全差分が対象
/git-commit work # ワーキングツリー(未ステージ)のみ対象
/git-commit stage # stagedのみ対象