-
head / base の整合性チェックと自動ブランチ切り出し
base == head になるケース(例: dev に居てデフォルト base も dev)は、そのまま進めると PR が作れない。以下のいずれかで救済する:
- 作業中の変更(staged / unstaged / 直近の未 push コミット)がある場合、新しいブランチを切ってそこに退避してから続行する。
- 何の変更も無い場合は「PR 対象の差分が無い」と報告して中断する。
ブランチ名の推論(CONTRIBUTING.md の命名規則に従う):
| プレフィックス | 採用条件 |
|---|
fix/ | 変更内容や直近コミット件名にバグ修正・fix・修正・不具合 を示唆する語がある |
data/ | 変更が data/**/*.csv / data/create_table.sql などデータ系のみ |
chore/ | 依存更新(Cargo.toml / Cargo.lock)・ビルド設定など雑務のみ |
release/ | リリース作業(バージョンバンプなど。ユーザーが明示した場合のみ) |
feature/ | 上記いずれにも当たらない場合の既定(新機能・通常の改修) |
命名規則は pr-labeler.yml のラベル自動付与にも連動するため、プレフィックスは厳守(feat/ ではなく feature/、docs/ は使わない)。slug は変更ファイル・コミット件名から短い英小文字 kebab-case を作る(例: fix-line-symbol-color、data/keikyu-line-color)。確信が持てない場合は slug 候補を 1〜2 個出してユーザーに確認。
切り出し手順:
git switch -c <inferred-branch>
git add -u
git commit
git push -u origin <inferred-branch>
- コミット前に下記の品質チェックを通す(
CONTRIBUTING.md ルール、手順 3 で定義する「コード本体パス」に変更が無ければ省略可):
cargo fmt --all -- --check
SQLX_OFFLINE=true cargo clippy -- -D warnings
SQLX_OFFLINE=true cargo test
- データのみの変更(
data/*.csv 等)を含む場合は cargo run -p data_validator も流す。
- push は新規ブランチなので安全だが、実行前にユーザーへ要約(ブランチ名・含めるファイル・コミットメッセージ案)を提示して承認を取る。
以降の手順では推論後の head を使う。
-
状態確認とモード決定(新規作成 / 更新)
git fetch origin <base> <head> を実行。
git log --oneline origin/<base>..origin/<head> で差分があることを確認。無ければ中断して報告。
gh pr list --base <base> --head <head> --state open --json number,url,body で既存 open PR を確認。
- 存在しない場合: 新規作成モード。以降、手順 5 で
gh pr create。
- 存在する場合: 更新モード。既存本文を最新差分で再生成する。以降、手順 5 で
gh pr edit。タイトルは既存を原則尊重(ユーザー推論より優先)。ただし手順 5 の整合性チェックで主題が大きくズレていると判断した場合のみ更新案を提示する。
-
変更の種類を判定
origin/<base>..origin/<head> のコミット件名と変更ファイルを取得:
git log --pretty=%s origin/<base>..origin/<head>
git diff --name-only origin/<base>..origin/<head>
大原則: 判定はアプリ挙動/データに対する変更かどうかで決める。下の「コード本体パス」が一切変わっていない場合、「バグ修正」「新機能」「リファクタリング」は OFF(コミット件名に fix / feat 等の語があっても)。スキル・設定・ドキュメントのメタ変更を「新機能」と誤分類しないための安全弁。「データの修正・追加」は data/** の変更を独立に判定する(後述「変更ファイルパスベース」「コミット件名ベース」を参照)。
この大原則のもとで、各項目を独立に評価(複数該当可、大文字小文字無視・部分一致)。
コード本体パス(バグ修正 / 新機能 / リファクタリングのゲート)
stationapi/src/**
stationapi/proto/**
data_validator/src/**
tools/**
docker/**
Cargo.toml / Cargo.lock
compose.yml
コード本体変更ありの場合 — コミット件名ベース
| 項目 | トリガ語句 |
|---|
| バグ修正 | fix, Hotfix, バグ, 修正, 不具合 |
| 新機能 | feat, add, 新機能, 追加, 導入, 対応, RPC |
| リファクタリング | refactor, リファクタ, 整理, clean, tidy |
変更ファイルパスベース(コード本体変更の有無に関わらず評価)
| 項目 | パターン |
|---|
| データの修正・追加 | data/**/*.csv, data/create_table.sql, data/*.sql |
| ドキュメント | 変更が *.md / docs/** / README* / .claude/** / AGENTS.md / CONTRIBUTING.md のみ、またはそれらを主体とする |
| CI/CD | .github/workflows/**, .github/**/*.yml, Makefile のいずれかを含む |
コミット件名ベース(データ・ドキュメント・CI/CD)
| 項目 | トリガ語句 |
|---|
| データの修正・追加 | データ, data, 駅, 路線, numbering, CSV, csv |
| ドキュメント | docs, ドキュメント, README, changelog, AGENTS, CONTRIBUTING |
| CI/CD | ci, cd, workflow, release, Bump version, labeler |
判定ロジック:
- 上の「大原則」のゲートをまず適用。コード本体/データの変更が無ければバグ修正・新機能・リファクタリング・データの修正・追加は強制 OFF。
- 「データの修正・追加」は
data/** の変更があれば ON。data/README.md のみの変更なら「ドキュメント」のみ ON にする。
- 「ドキュメント」は変更にコード本体や CSV を含まない場合に ON。混在する場合は基本 OFF(主目的が分かるならそちらを優先)。ただし
.claude/** や AGENTS.md のみの変更は「ドキュメント」を ON にする(運用ドキュメント扱い)。
- 「CI/CD」は
.github/workflows/** 等の変更があれば独立に ON。
- 残りの項目は、コミット件名またはファイルパスのトリガに 1 つでも当てはまれば
- [x]、それ以外は - [ ]。
- 全項目が OFF のときのみ
その他 を - [x] にする。他項目が ON のときは その他 は必ず - [ ]。
-
本文組み立て
.github/pull_request_template.md の節構成をそのまま使い、下の置換だけを行う。節の追加・削除は禁止。
節は見出し(## 概要 / ## 変更の種類 / ## 変更内容 / ## テスト / ## 関連Issue / ## スクリーンショット(任意))で区切られる。各節の内容を下のルールで決める。
新規作成モード
- 「概要」節:
summary があれば挿入。無ければテンプレのコメントだけ残す。
- 「変更の種類」節: 手順 3 の結果で各
- [ ] / - [x] を決定。項目順序は必ずテンプレ通り(バグ修正 / 新機能 / データの修正・追加 / リファクタリング / ドキュメント / CI/CD / その他)。
- 「変更内容」節: コミット件名と変更ファイルから短い箇条書きを生成。
summary があればそれを優先。データのみの PR では追加・修正した路線・駅などを箇条書きで列挙すると親切。
- 「テスト」節:
- 判定基準: 手順 3 の「コード本体パス」(
stationapi/src/** ほか)に変更が無い場合は Step 1 の cargo チェックを省略したとみなし、3 項目すべて OFF(skip_checks より優先)。本文末尾に「省略: コード変更なし」等の短い注記を残す。
- 上記に該当しない場合は
skip_checks が真なら 3 項目すべて OFF、偽なら 3 項目すべて ON。テキストはテンプレのまま(cargo fmt --all -- --check / cargo clippy -- -D warnings / cargo test(SQLX_OFFLINE=true))。
- 「関連Issue」節:
related_issue が指定されていればユーザー入力を最優先で出力(#N のみなら Closes #N、Closes/Fixes/Refs #N 形式なら接頭語を維持)。空のときに限りコミット件名から Closes/Fixes/Refs #N を抽出。どちらも無ければコメントのみ。
- 「スクリーンショット」節: 常にコメントのみ(API レスポンスの diff など必要なら呼び出し側が後から編集する前提)。
更新モード(既存 PR の本文を再生成)
既存本文を節ごとに分割し、以下のルールで部分的に書き換える。人間が書き込んだ散文は壊さない。
| 節 | 更新方針 |
|---|
| 概要 | 既存内容を尊重。空欄(テンプレのコメントのみ)なら新規作成モードと同じ生成を試みる。 |
| 変更の種類 | 常に手順 3 の結果で上書き(機械的判定)。 |
| 変更内容 | 冒頭の箇条書きブロック(- で始まる連続行)を最新差分で再生成。その下に人間が書いた散文があれば残す。 |
| テスト | 常に skip_checks に従う(手順 4 の本文組み立てと同じルール)。 |
| 関連Issue | 既存内容を尊重。コミット件名に Closes/Fixes/Refs #N があり、かつ既存本文中に同じ Issue 番号 #N を指す表現が存在しない場合のみ追記(重複は作らない。比較時は Closes / closes / Fixes / fixes / Refs / refs を同一視し、空白・記号差は無視して #N 単位で照合)。 |
| スクリーンショット | 既存内容を尊重。自動では触らない。 |
差し替え後の本文と既存本文の差分をユーザーに提示し、承認を得てから手順 5 へ進む。自動上書き節で人間の手入れらしき痕跡(テンプレのコメント以外の文章)がある場合は、どう扱うかをユーザーに確認する。
-
PR 作成 / 更新
本文は 必ず一時ファイル経由で渡す(gh pr create --body-file / gh pr edit --body-file)。理由: --body "$(cat <<'EOF' ... EOF)" のようにヒアドキュメントをシェル経由で渡すと、エディタ側の癖や Claude Code 側の生成で本文中のバッククォートが \`` のように誤って escape されてしまい、PR 画面でコードスパン/フェンスがレンダリングされない事故が起きる。--body-file` ならシェルの引用符を一切介さないので構造的に起きない。
実装手順:
-
Write ツールで本文を一時ファイルに書き出す(例: /tmp/pr-body-<slug>.md)。ファイル名に使う ref(ブランチ名・PR 番号など)は ファイル名として安全な集合(A-Za-z0-9._-)にスラッグ化 する。具体的には:
/・改行・制御文字・空白・非 ASCII などを _ に置換
- 連続した
_ は 1 つに畳み、先頭・末尾の _ は除去
- 必要なら長さを 100〜200 文字程度に切り詰める
生のブランチ名を直結するとサブディレクトリ解釈や制御文字混入で Write/削除が失敗する。バッククォートは 素のまま 書く。escape しない。
-
下の gh コマンドをサブシェル内で trap と一緒に実行する。gh の成功・失敗に関わらず EXIT / INT / TERM のどれでも一時ファイルを確実に削除されるようにする(&& で rm を繋ぐだけだと失敗時に /tmp にゴミが残る)。
-
gh 呼び出しと rm(を含む trap)は Bash tool の 1 呼び出し内で完結させる。別呼び出しで後片付けすると、前段の呼び出しがエラー/中断で終わった場合にクリーンアップが実行されない。
新規作成モード
REF_SLUG="$(printf '%s' '<head>' \
| tr -d '\r\n' \
| tr -c 'A-Za-z0-9._-' '_' \
| sed -E 's/_+/_/g; s/^_+//; s/_+$//' \
| cut -c1-100)"
REF_SLUG="${REF_SLUG:-pr}"
BODY_FILE="/tmp/pr-body-${REF_SLUG}.md"
(
trap 'rm -f "$BODY_FILE"' EXIT INT TERM
gh pr create \
--base "<base>" \
--head "<head>" \
--title "<title>" \
--assignee TinyKitten \
[--label "<label1>" --label "<label2>" ...] \
--body-file "$BODY_FILE"
)
- Assignee は常に
TinyKitten(CODEOWNERS で全パスのオーナー)。
labels 入力があれば、その要素数だけ --label を繰り返して渡す。未指定なら --label 自体を書かない(pr_labeler.yml が自動でラベルを付ける)。
- 作成後の URL と、ON にしたチェック項目・判定根拠(例: コミット
fix: ... により「バグ修正」を ON、data/3!stations.csv の変更により「データの修正・追加」を ON)、付与したラベルがあればその名前を報告する。
更新モード
BODY_FILE="/tmp/pr-body-${pr_number}.md"
(
trap 'rm -f "$BODY_FILE"' EXIT INT TERM
gh pr edit <pr-number> \
[--title "<更新後タイトル>"] \
--body-file "$BODY_FILE"
)
- タイトルは原則として既存を維持する。ただし毎回スコープ整合性を再評価し、手順 1 のタイトル推論ルールと最新のコミット群を照合する。現タイトルが新しい主題(追加路線・大きな機能変更など)を拾えていない重大な不整合がある場合のみ、更新案を提示してユーザー承認を取り
--title で上書きする。整合している、または軽微な差分にとどまる場合は --title を付けない。
- Assignee は既に付いていれば再指定しない(重複操作を避ける)。付いてなければ
--add-assignee TinyKitten。
- 実行後、PR URL と「タイトルを変更したか・どの節を書き換えたか・変更の種類チェック差分」を簡潔に報告する。