- name
- git-commit
- description
- git差分を分析し、フォーマットに従ったコミットを自動作成する。
# 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 の「対応方針」の書き先に決定記録と書かれていれば、それに従う。このトレーラーの付いたコミットは、`npm run gen:adr-index` が `docs/adr/adr-commit-list.md` の一覧に載せる。トレーラーの段落以外に `Decision-Record: yes` の行を書かない。
```
feat: 一覧 API のページングをカーソル方式にする (#123)
決定: 一覧 API のページングは、オフセットではなくカーソル(最後に返した行のキー)で行う。
理由: 一覧の途中で行が追加・削除されると、オフセット方式では行の重複や抜けが起きる。
却下した案:
- オフセット方式:件数が増えるほど遅くなり、途中の追加・削除で重複や抜けが起きる
Decision-Record: yes
```
### コミットの粒度
粒度は基本的に任意とする。ただし、コミットメッセージと無関係なファイルを含めてはならない。気づいた割れ窓は直すか起票するが、無関係な変更を1つのコミットに詰めるのは別問題である(「割れ窓を放置しない」[refined-engineer-judgment-principles](../../../docs/policy/refined-engineer-judgment-principles.md))。
```
# 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 プロセスの数を数える。
```bash
top=$(git rev-parse --show-toplevel)
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
```
2以上なら並行セッションがある。0 は cwd を引けず(`lsof` が無いなど)判定できなかったことを示すので、2以上と同じく扱う。**このとき既定スコープを「このセッションで自分が Edit/Write したファイルのみ」に切り替える**(引数 `work` / `stage` の指定も、この絞り込みの中で適用する)。切り替えたことと、除外したファイルのパスを Phase 5 の出力に列挙する。
1なら単独セッションなので、既定スコープをそのまま使う。
範囲を決めたら現在の状態を把握する。**スコープに不要なコマンドは実行しない**(`work` なら staged 側、`stage` なら worktree 側は取得不要)。依存関係のないコマンドは1回の Bash 呼び出しにまとめる:
```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 # Untracked
```
### Phase 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:メッセージの生成
上記「コミットのルール」に従って組み立てる:
1. **件名**:`type: 説明`。説明は変更内容が伝わるよう簡潔に(目安30文字以内)。
2. **issue番号**:件名末尾に `(#番号)` を付与する。番号はブランチ名等から推定し、**不明な場合はユーザーに確認する**。
3. **本文(なぜ)**:本文を書くかどうかは上記「『なぜ』をどこに書くか」の表で判断する。書く場合はコードから読み取れない背景・判断理由を記す。決定記録にあたる決定を含むなら、上記「決定記録を残すコミット」の書式で書く。
### 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 の対象リストと一致するか確認する。
```bash
git diff --cached --name-only
```
- **対象外のパスが混じっている**:別セッションが staged にしたもの。`git reset` で外さない(相手の index を壊す)。手順3のパス指定でコミット対象から外れるので、除外したパスとして出力に列挙する
- **対象のパスが出てこない**:別セッションが先にコミットした可能性がある。`git log --oneline -3` と `git show --stat <hash>` で取り込み済みか確かめ、済んでいればグループから外す
**3. コミットする**:対象パスを `--` の後ろに並べ、index 全体ではなくそのパスだけをコミットする。
```bash
git commit -m "<件名>" -- <対象パス>...
```
パス指定を省くと、index に載った他セッションの変更ごとコミットされる。Phase 1 でスコープを正しく絞っても実行段階で巻き込むので、ここは AI の判断に委ねず常にパスで縛る。
このコマンドは index を経由せず作業ツリーの内容をコミットする。手順1のステージ後にファイルを直していると、index には古い内容が残り、次のコミットがそれを拾う。コミット後に `git status --short` を見て、対象ファイルが残っていたら `git add <パス>` で index を揃える。
ファイルの一部だけをステージしているときは、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のみ対象
```
Ver en GitHub