From 8e8d20fc4e30a0f25e2d533345e819084f48d3eb Mon Sep 17 00:00:00 2001
From: Tsubasa SEKIGUCHI <oss@tinykitten.me>
Date: Mon, 4 May 2026 17:58:40 +0900
Subject: [PATCH 1/4] =?UTF-8?q?create-pr=20=E3=82=B9=E3=82=AD=E3=83=AB?=
 =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .claude/skills/create-pr/SKILL.md | 222 ++++++++++++++++++++++++++++++
 1 file changed, 222 insertions(+)
 create mode 100644 .claude/skills/create-pr/SKILL.md

diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md
new file mode 100644
index 00000000..2e1f7f37
--- /dev/null
+++ b/.claude/skills/create-pr/SKILL.md
@@ -0,0 +1,222 @@
+---
+name: create-pr
+description: Create a GitHub pull request for TrainLCD StationAPI that conforms to .github/pull_request_template.md, assigns @TinyKitten, and auto-checks the 変更の種類 boxes based on the commit/file diff. Use whenever the user asks to open a PR in this repo.
+---
+
+# create-pr
+
+このリポジトリの PR 作成手順を一本化したスキル。`.github/pull_request_template.md` を厳守し、Assignee・変更の種類・テスト欄を自動で組み立てる。
+
+## 入力（呼び出し元が指定）
+
+すべて任意。未指定なら下の既定値・推論で埋める。推論結果に不安があるとき（例: 多数のコミットで方向性がバラバラ）はユーザーに確認してから進める。
+
+| 項目 | 既定値 / 推論元 |
+| ---- | ---- |
+| `base` | リポジトリの既定ブランチ（`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`、StationAPI では通常 `dev`） |
+| `head` | カレントブランチ（`git rev-parse --abbrev-ref HEAD`） |
+| `title` | 下の「タイトル推論ルール」参照 |
+| `summary` | 空なら「概要」「変更内容」本文はテンプレのコメントのみ残す |
+| `related_issue` | 空なら節のコメントのみ。コミット件名に `Closes #N` / `Fixes #N` / `Refs #N` があれば拾う |
+| `skip_checks` | `false`（テスト 3 項目を ON）。`true` なら全 OFF |
+| `labels` | 文字列配列、または未指定。**通常は未指定で OK**（`.github/workflows/pr_labeler.yml` がブランチ名と変更ファイルから自動付与する）。手動指定が必要な場合は `gh pr create --label <name>` で渡す（作成後に `gh pr edit --add-label` すると `pull_request: opened` トリガのワークフローに間に合わないため、必ず `gh pr create` 時に渡す） |
+
+### タイトル推論ルール
+
+`origin/<base>..origin/<head>` のコミット件名を対象に、以下を順に試す:
+
+1. **コミット 1 件のみ**: その件名をそのまま使う。
+2. **コミット複数・共通テーマあり**: 最新コミットの件名、もしくは件名群を要約した日本語の単文を使う。
+3. **ブランチ名が `feature/` / `fix/` / `data/` / `chore/` / `release/` で始まる**: プレフィックスを取り除き、残りの `kebab-case` を日本語や自然文に整える。確信が持てないときは整形せずブランチ名のまま使ってよい。
+4. **どれでも決まらない**: 最新コミット件名を採用し、「このタイトルで作成してよいか」をユーザーに確認する。
+
+このリポジトリの直近 PR タイトル（例: `近鉄生駒鋼索線 鳥居前駅と近鉄生駒駅をグループ接続`、`京急本線 line_symbol2_colorのコードが違う`）に倣い、**簡潔な日本語の単文**に整形する。Conventional Commits プレフィックス（`fix:` `feat:` など）は基本的に剥がす。整形時は意味を変えないこと。
+
+## 前提条件
+
+- カレントディレクトリが `git rev-parse --show-toplevel` で解決できるリポジトリ内。
+- `gh` CLI が認証済み。
+- `head` ブランチが origin に push 済み。未 push の場合はユーザーに push の可否を確認する（勝手に push しない）。
+
+## 手順
+
+1. **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 個出してユーザーに確認。
+
+   切り出し手順:
+   ```bash
+   git switch -c <inferred-branch>
+   # 未コミットなら:
+   git add <files>          # 対話的ステージは避ける
+   git commit               # コミットメッセージは日本語単文
+   git push -u origin <inferred-branch>
+   ```
+   - コミット前に下記の品質チェックを通す（`CONTRIBUTING.md` ルール、コードに変更が無ければ省略可）:
+     - `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 を使う。
+
+2. **状態確認とモード決定（新規作成 / 更新）**
+   - `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`。タイトルは既存のものを尊重（ユーザー推論より優先）。
+
+3. **変更の種類を判定**
+
+   `origin/<base>..origin/<head>` のコミット件名と変更ファイルを取得:
+   ```bash
+   git log --pretty=%s origin/<base>..origin/<head>
+   git diff --name-only origin/<base>..origin/<head>
+   ```
+
+   **大原則: 判定はアプリ挙動／データに対する変更かどうかで決める**。`stationapi/src/**`・`stationapi/proto/**`・`data_validator/**`・`data/**`・`docker/**`・`tools/**` が一切変わっていない場合、「バグ修正」「新機能」「リファクタリング」「データの修正・追加」は OFF（コミット件名に `fix` / `feat` / `追加` 等の語があっても）。スキル・設定・ドキュメントのメタ変更を「新機能」と誤分類しないための安全弁。
+
+   この大原則のもとで、各項目を独立に評価（複数該当可、大文字小文字無視・部分一致）。
+
+   **コード本体パス**（バグ修正 / 新機能 / リファクタリングのゲート）
+
+   - `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 のときは `その他` は必ず `- [ ]`。
+
+4. **本文組み立て**
+
+   `.github/pull_request_template.md` の節構成をそのまま使い、下の置換だけを行う。節の追加・削除は禁止。
+
+   節は見出し（`## 概要` / `## 変更の種類` / `## 変更内容` / `## テスト` / `## 関連Issue` / `## スクリーンショット（任意）`）で区切られる。各節の内容を下のルールで決める。
+
+   **新規作成モード**
+   - 「概要」節: `summary` があれば挿入。無ければテンプレのコメントだけ残す。
+   - 「変更の種類」節: 手順 3 の結果で各 `- [ ]` / `- [x]` を決定。**項目順序は必ずテンプレ通り**（バグ修正 / 新機能 / データの修正・追加 / リファクタリング / ドキュメント / CI/CD / その他）。
+   - 「変更内容」節: コミット件名と変更ファイルから短い箇条書きを生成。`summary` があればそれを優先。データのみの PR では追加・修正した路線・駅などを箇条書きで列挙すると親切。
+   - 「テスト」節: `skip_checks` が真なら 3 項目すべて OFF、偽なら 3 項目すべて ON。テキストはテンプレのまま（`cargo fmt --all -- --check` / `cargo clippy -- -D warnings` / `cargo test`（`SQLX_OFFLINE=true`））。
+   - 「関連Issue」節: `related_issue` があれば `Closes #N` を書く。無ければコメントのみ。
+   - 「スクリーンショット」節: 常にコメントのみ（API レスポンスの diff など必要なら呼び出し側が後から編集する前提）。
+
+   **更新モード**（既存 PR の本文を再生成）
+
+   既存本文を節ごとに分割し、以下のルールで部分的に書き換える。人間が書き込んだ散文は壊さない。
+
+   | 節 | 更新方針 |
+   | ---- | ---- |
+   | 概要 | 既存内容を尊重。空欄（テンプレのコメントのみ）なら新規作成モードと同じ生成を試みる。 |
+   | 変更の種類 | **常に手順 3 の結果で上書き**（機械的判定）。 |
+   | 変更内容 | 冒頭の箇条書きブロック（`-` で始まる連続行）を最新差分で再生成。その下に人間が書いた散文があれば残す。 |
+   | テスト | **常に `skip_checks` に従う**（手順 4 の本文組み立てと同じルール）。 |
+   | 関連Issue | 既存内容を尊重。コミット件名に新規 `Closes #N` 等があれば追記。 |
+   | スクリーンショット | 既存内容を尊重。自動では触らない。 |
+
+   差し替え後の本文と既存本文の差分をユーザーに提示し、承認を得てから手順 5 へ進む。自動上書き節で人間の手入れらしき痕跡（テンプレのコメント以外の文章）がある場合は、どう扱うかをユーザーに確認する。
+
+5. **PR 作成 / 更新**
+
+   本文は **必ず一時ファイル経由で渡す**（`gh pr create --body-file` / `gh pr edit --body-file`）。理由: `--body "$(cat <<'EOF' ... EOF)"` のようにヒアドキュメントをシェル経由で渡すと、エディタ側の癖や Claude Code 側の生成で本文中のバッククォートが `\`` のように誤って escape されてしまい、PR 画面でコードスパン／フェンスがレンダリングされない事故が起きる。`--body-file` ならシェルの引用符を一切介さないので構造的に起きない。
+
+   実装手順:
+
+   1. Write ツールで本文を一時ファイルに書き出す（例: `/tmp/pr-body-<pr-or-branch>.md`）。バッククォートは **素のまま** 書く。escape しない。
+   2. 下の `gh` コマンドをサブシェル内で `trap` と一緒に実行する。`gh` の成功・失敗に関わらず `EXIT` / `INT` / `TERM` のどれでも一時ファイルを確実に削除されるようにする（`&&` で `rm` を繋ぐだけだと失敗時に `/tmp` にゴミが残る）。
+   3. `gh` 呼び出しと `rm`（を含む `trap`）は Bash tool の 1 呼び出し内で完結させる。別呼び出しで後片付けすると、前段の呼び出しがエラー／中断で終わった場合にクリーンアップが実行されない。
+
+   **新規作成モード**
+
+   ```bash
+   BODY_FILE=/tmp/pr-body-<pr-or-branch>.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）、付与したラベルがあればその名前を報告する。
+
+   **更新モード**
+
+   ```bash
+   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 と「タイトルを変更したか・どの節を書き換えたか・変更の種類チェック差分」を簡潔に報告する。
+
+## 注意事項
+
+- テンプレの節構成は改変しない。追加・削除はメンテナ承認が必要。
+- `git push --no-verify` や force push はしない。push が必要ならユーザーに確認。
+- 既存 open PR を上書きしない（重複作成禁止）。
+- ブランチプレフィックスは `feature/` / `fix/` / `data/` / `chore/` / `release/` のみ使用（`pr-labeler.yml` のラベル自動付与に直結する）。
+- 本文は `gh pr create --body` / `gh pr edit --body` のようにインラインで渡さない。必ず `--body-file` で一時ファイル経由で渡す（バッククォートなど特殊文字の escape 事故を構造的に防ぐため）。
+- データ変更を伴う PR では `cargo run -p data_validator` の実行結果を「テスト」または「変更内容」節に追記すると `AGENTS.md` のガイドライン（変更内容と検証コマンドの記録）に沿う。

From 16ec0b408abb22fa58f6b452e0742e428ba33769 Mon Sep 17 00:00:00 2001
From: Tsubasa SEKIGUCHI <oss@tinykitten.me>
Date: Mon, 4 May 2026 18:09:02 +0900
Subject: [PATCH 2/4] fix: apply CodeRabbit auto-fixes

---
 .claude/skills/create-pr/SKILL.md | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md
index 2e1f7f37..1059acda 100644
--- a/.claude/skills/create-pr/SKILL.md
+++ b/.claude/skills/create-pr/SKILL.md
@@ -17,8 +17,8 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
 | `head` | カレントブランチ（`git rev-parse --abbrev-ref HEAD`） |
 | `title` | 下の「タイトル推論ルール」参照 |
 | `summary` | 空なら「概要」「変更内容」本文はテンプレのコメントのみ残す |
-| `related_issue` | 空なら節のコメントのみ。コミット件名に `Closes #N` / `Fixes #N` / `Refs #N` があれば拾う |
-| `skip_checks` | `false`（テスト 3 項目を ON）。`true` なら全 OFF |
+| `related_issue` | 空なら節のコメントのみ。`#N`（数値のみ）なら `Closes #N` を出力。`Closes #N` / `Fixes #N` / `Refs #N` の形式で指定された場合は接頭語を保ったまま出力。コミット件名から拾う場合も元の接頭語を維持する |
+| `skip_checks` | `false`（PR本文「テスト」節のチェック欄 3 項目を ON）。`true` なら全 OFF。**本文表示のみを制御するフラグで、Step 1 の `cargo fmt` / `clippy` / `test` 実行有無とは独立**（実行可否は「コードに変更があるか」で決める） |
 | `labels` | 文字列配列、または未指定。**通常は未指定で OK**（`.github/workflows/pr_labeler.yml` がブランチ名と変更ファイルから自動付与する）。手動指定が必要な場合は `gh pr create --label <name>` で渡す（作成後に `gh pr edit --add-label` すると `pull_request: opened` トリガのワークフローに間に合わないため、必ず `gh pr create` 時に渡す） |
 
 ### タイトル推論ルール
@@ -63,8 +63,10 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    ```bash
    git switch -c <inferred-branch>
    # 未コミットなら:
-   git add <files>          # 対話的ステージは避ける
-   git commit               # コミットメッセージは日本語単文
+   git add -u                       # 追跡済みの staged/unstaged をまとめてステージ
+   # 未追跡ファイルも退避対象なら明示的にパス指定で追加（`git add -A` / `.` は使わない）:
+   #   git add path/to/untracked-file ...
+   git commit                       # コミットメッセージは日本語単文
    git push -u origin <inferred-branch>
    ```
    - コミット前に下記の品質チェックを通す（`CONTRIBUTING.md` ルール、コードに変更が無ければ省略可）:
@@ -81,7 +83,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    - `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 で `gh pr edit`。タイトルは既存を**原則尊重**（ユーザー推論より優先）。ただし手順 5 の整合性チェックで主題が大きくズレていると判断した場合のみ更新案を提示する。
 
 3. **変更の種類を判定**
 
@@ -148,7 +150,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    - 「変更の種類」節: 手順 3 の結果で各 `- [ ]` / `- [x]` を決定。**項目順序は必ずテンプレ通り**（バグ修正 / 新機能 / データの修正・追加 / リファクタリング / ドキュメント / CI/CD / その他）。
    - 「変更内容」節: コミット件名と変更ファイルから短い箇条書きを生成。`summary` があればそれを優先。データのみの PR では追加・修正した路線・駅などを箇条書きで列挙すると親切。
    - 「テスト」節: `skip_checks` が真なら 3 項目すべて OFF、偽なら 3 項目すべて ON。テキストはテンプレのまま（`cargo fmt --all -- --check` / `cargo clippy -- -D warnings` / `cargo test`（`SQLX_OFFLINE=true`））。
-   - 「関連Issue」節: `related_issue` があれば `Closes #N` を書く。無ければコメントのみ。
+   - 「関連Issue」節: `related_issue` があれば、入力またはコミット件名から得た `Closes #N` / `Fixes #N` / `Refs #N` の接頭語をそのまま書く（数値 `N` だけが渡された場合のみ `Closes #N` を補う）。無ければコメントのみ。
    - 「スクリーンショット」節: 常にコメントのみ（API レスポンスの diff など必要なら呼び出し側が後から編集する前提）。
 
    **更新モード**（既存 PR の本文を再生成）
@@ -172,14 +174,15 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
 
    実装手順:
 
-   1. Write ツールで本文を一時ファイルに書き出す（例: `/tmp/pr-body-<pr-or-branch>.md`）。バッククォートは **素のまま** 書く。escape しない。
+   1. Write ツールで本文を一時ファイルに書き出す（例: `/tmp/pr-body-<slug>.md`）。ファイル名に使うブランチ名は `/` を `_` 等に置換してスラッグ化する（`feature/foo` → `feature_foo`）。生のブランチ名を直結するとサブディレクトリ解釈になり Write／削除が失敗する。バッククォートは **素のまま** 書く。escape しない。
    2. 下の `gh` コマンドをサブシェル内で `trap` と一緒に実行する。`gh` の成功・失敗に関わらず `EXIT` / `INT` / `TERM` のどれでも一時ファイルを確実に削除されるようにする（`&&` で `rm` を繋ぐだけだと失敗時に `/tmp` にゴミが残る）。
    3. `gh` 呼び出しと `rm`（を含む `trap`）は Bash tool の 1 呼び出し内で完結させる。別呼び出しで後片付けすると、前段の呼び出しがエラー／中断で終わった場合にクリーンアップが実行されない。
 
    **新規作成モード**
 
    ```bash
-   BODY_FILE=/tmp/pr-body-<pr-or-branch>.md
+   REF_SLUG="$(printf '%s' '<head>' | tr '/' '_')"
+   BODY_FILE="/tmp/pr-body-${REF_SLUG}.md"
    (
      trap 'rm -f "$BODY_FILE"' EXIT INT TERM
      gh pr create \
@@ -199,7 +202,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    **更新モード**
 
    ```bash
-   BODY_FILE=/tmp/pr-body-<pr-number>.md
+   BODY_FILE="/tmp/pr-body-${pr_number}.md"
    (
      trap 'rm -f "$BODY_FILE"' EXIT INT TERM
      gh pr edit <pr-number> \
@@ -208,7 +211,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    )
    ```
 
-   - **タイトルは毎回スコープ整合性を再評価する**。手順 1 のタイトル推論ルールと最新のコミット群を照合し、現タイトルが新しい主題（追加路線・大きな機能変更など）を拾えていなければ更新案を提示してユーザー承認を取り、`--title` に含めて反映する。タイトルが最新差分と整合している場合は `--title` を付けない。
+   - **タイトルは原則として既存を維持する**。ただし毎回スコープ整合性を再評価し、手順 1 のタイトル推論ルールと最新のコミット群を照合する。現タイトルが新しい主題（追加路線・大きな機能変更など）を拾えていない**重大な不整合**がある場合のみ、更新案を提示してユーザー承認を取り `--title` で上書きする。整合している、または軽微な差分にとどまる場合は `--title` を付けない。
    - Assignee は既に付いていれば再指定しない（重複操作を避ける）。付いてなければ `--add-assignee TinyKitten`。
    - 実行後、PR URL と「タイトルを変更したか・どの節を書き換えたか・変更の種類チェック差分」を簡潔に報告する。
 

From 8f84c1f210606136575c26d33bb8232cf9a91756 Mon Sep 17 00:00:00 2001
From: Tsubasa SEKIGUCHI <oss@tinykitten.me>
Date: Mon, 4 May 2026 20:19:16 +0900
Subject: [PATCH 3/4] fix: apply CodeRabbit auto-fixes

---
 .claude/skills/create-pr/SKILL.md | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md
index 1059acda..63f1e102 100644
--- a/.claude/skills/create-pr/SKILL.md
+++ b/.claude/skills/create-pr/SKILL.md
@@ -17,8 +17,8 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
 | `head` | カレントブランチ（`git rev-parse --abbrev-ref HEAD`） |
 | `title` | 下の「タイトル推論ルール」参照 |
 | `summary` | 空なら「概要」「変更内容」本文はテンプレのコメントのみ残す |
-| `related_issue` | 空なら節のコメントのみ。`#N`（数値のみ）なら `Closes #N` を出力。`Closes #N` / `Fixes #N` / `Refs #N` の形式で指定された場合は接頭語を保ったまま出力。コミット件名から拾う場合も元の接頭語を維持する |
-| `skip_checks` | `false`（PR本文「テスト」節のチェック欄 3 項目を ON）。`true` なら全 OFF。**本文表示のみを制御するフラグで、Step 1 の `cargo fmt` / `clippy` / `test` 実行有無とは独立**（実行可否は「コードに変更があるか」で決める） |
+| `related_issue` | **ユーザー入力を最優先**。指定が `#N`（数値のみ）なら `Closes #N`、`Closes #N` / `Fixes #N` / `Refs #N` 形式ならその接頭語を保って出力。`related_issue` が空のときに限り、コミット件名から `Closes #N` / `Fixes #N` / `Refs #N` を抽出（接頭語を維持。`#N` 単体表記なら `Closes` を補う）。両方とも見つからなければ節のコメントのみ |
+| `skip_checks` | `false`（PR本文「テスト」節のチェック欄 3 項目を ON）。`true` なら全 OFF。**本文表示のみを制御するフラグで、Step 1 の `cargo fmt` / `clippy` / `test` の実際の実行は保証しない**（実行可否は「コードに変更があるか」で決める）。**Step 1 で `cargo` チェックを実行していない（=コード／データ変更なし）ケースでは、`skip_checks` の値に関わらず 3 項目すべて OFF にする** |
 | `labels` | 文字列配列、または未指定。**通常は未指定で OK**（`.github/workflows/pr_labeler.yml` がブランチ名と変更ファイルから自動付与する）。手動指定が必要な場合は `gh pr create --label <name>` で渡す（作成後に `gh pr edit --add-label` すると `pull_request: opened` トリガのワークフローに間に合わないため、必ず `gh pr create` 時に渡す） |
 
 ### タイトル推論ルール
@@ -149,8 +149,10 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    - 「概要」節: `summary` があれば挿入。無ければテンプレのコメントだけ残す。
    - 「変更の種類」節: 手順 3 の結果で各 `- [ ]` / `- [x]` を決定。**項目順序は必ずテンプレ通り**（バグ修正 / 新機能 / データの修正・追加 / リファクタリング / ドキュメント / CI/CD / その他）。
    - 「変更内容」節: コミット件名と変更ファイルから短い箇条書きを生成。`summary` があればそれを優先。データのみの PR では追加・修正した路線・駅などを箇条書きで列挙すると親切。
-   - 「テスト」節: `skip_checks` が真なら 3 項目すべて OFF、偽なら 3 項目すべて ON。テキストはテンプレのまま（`cargo fmt --all -- --check` / `cargo clippy -- -D warnings` / `cargo test`（`SQLX_OFFLINE=true`））。
-   - 「関連Issue」節: `related_issue` があれば、入力またはコミット件名から得た `Closes #N` / `Fixes #N` / `Refs #N` の接頭語をそのまま書く（数値 `N` だけが渡された場合のみ `Closes #N` を補う）。無ければコメントのみ。
+   - 「テスト」節:
+     - **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 の本文を再生成）
@@ -163,7 +165,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    | 変更の種類 | **常に手順 3 の結果で上書き**（機械的判定）。 |
    | 変更内容 | 冒頭の箇条書きブロック（`-` で始まる連続行）を最新差分で再生成。その下に人間が書いた散文があれば残す。 |
    | テスト | **常に `skip_checks` に従う**（手順 4 の本文組み立てと同じルール）。 |
-   | 関連Issue | 既存内容を尊重。コミット件名に新規 `Closes #N` 等があれば追記。 |
+   | 関連Issue | 既存内容を尊重。コミット件名に `Closes/Fixes/Refs #N` があり、かつ既存本文中に同じ Issue 番号 `#N` を指す表現が存在しない場合のみ追記（重複は作らない。比較時は `Closes` / `closes` / `Fixes` / `fixes` / `Refs` / `refs` を同一視し、空白・記号差は無視して `#N` 単位で照合）。 |
    | スクリーンショット | 既存内容を尊重。自動では触らない。 |
 
    差し替え後の本文と既存本文の差分をユーザーに提示し、承認を得てから手順 5 へ進む。自動上書き節で人間の手入れらしき痕跡（テンプレのコメント以外の文章）がある場合は、どう扱うかをユーザーに確認する。
@@ -174,14 +176,25 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
 
    実装手順:
 
-   1. Write ツールで本文を一時ファイルに書き出す（例: `/tmp/pr-body-<slug>.md`）。ファイル名に使うブランチ名は `/` を `_` 等に置換してスラッグ化する（`feature/foo` → `feature_foo`）。生のブランチ名を直結するとサブディレクトリ解釈になり Write／削除が失敗する。バッククォートは **素のまま** 書く。escape しない。
+   1. Write ツールで本文を一時ファイルに書き出す（例: `/tmp/pr-body-<slug>.md`）。ファイル名に使う ref（ブランチ名・PR 番号など）は **ファイル名として安全な集合（`A-Za-z0-9._-`）にスラッグ化** する。具体的には:
+      - `/`・改行・制御文字・空白・非 ASCII などを `_` に置換
+      - 連続した `_` は 1 つに畳み、先頭・末尾の `_` は除去
+      - 必要なら長さを 100〜200 文字程度に切り詰める
+
+      生のブランチ名を直結するとサブディレクトリ解釈や制御文字混入で Write／削除が失敗する。バッククォートは **素のまま** 書く。escape しない。
    2. 下の `gh` コマンドをサブシェル内で `trap` と一緒に実行する。`gh` の成功・失敗に関わらず `EXIT` / `INT` / `TERM` のどれでも一時ファイルを確実に削除されるようにする（`&&` で `rm` を繋ぐだけだと失敗時に `/tmp` にゴミが残る）。
    3. `gh` 呼び出しと `rm`（を含む `trap`）は Bash tool の 1 呼び出し内で完結させる。別呼び出しで後片付けすると、前段の呼び出しがエラー／中断で終わった場合にクリーンアップが実行されない。
 
    **新規作成モード**
 
    ```bash
-   REF_SLUG="$(printf '%s' '<head>' | tr '/' '_')"
+   # ref 名をファイル名として安全な集合（A-Za-z0-9._-）にスラッグ化
+   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

From 70f86a223d5cb1de9b2155ffde500320977ddcbe Mon Sep 17 00:00:00 2001
From: Tsubasa SEKIGUCHI <oss@tinykitten.me>
Date: Mon, 4 May 2026 20:28:01 +0900
Subject: [PATCH 4/4] fix: apply CodeRabbit auto-fixes

---
 .claude/skills/create-pr/SKILL.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md
index 63f1e102..c0f847c7 100644
--- a/.claude/skills/create-pr/SKILL.md
+++ b/.claude/skills/create-pr/SKILL.md
@@ -69,7 +69,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    git commit                       # コミットメッセージは日本語単文
    git push -u origin <inferred-branch>
    ```
-   - コミット前に下記の品質チェックを通す（`CONTRIBUTING.md` ルール、コードに変更が無ければ省略可）:
+   - コミット前に下記の品質チェックを通す（`CONTRIBUTING.md` ルール、手順 3 で定義する「コード本体パス」に変更が無ければ省略可）:
      - `cargo fmt --all -- --check`
      - `SQLX_OFFLINE=true cargo clippy -- -D warnings`
      - `SQLX_OFFLINE=true cargo test`
@@ -93,7 +93,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    git diff --name-only origin/<base>..origin/<head>
    ```
 
-   **大原則: 判定はアプリ挙動／データに対する変更かどうかで決める**。`stationapi/src/**`・`stationapi/proto/**`・`data_validator/**`・`data/**`・`docker/**`・`tools/**` が一切変わっていない場合、「バグ修正」「新機能」「リファクタリング」「データの修正・追加」は OFF（コミット件名に `fix` / `feat` / `追加` 等の語があっても）。スキル・設定・ドキュメントのメタ変更を「新機能」と誤分類しないための安全弁。
+   **大原則: 判定はアプリ挙動／データに対する変更かどうかで決める**。下の「コード本体パス」が一切変わっていない場合、「バグ修正」「新機能」「リファクタリング」は OFF（コミット件名に `fix` / `feat` 等の語があっても）。スキル・設定・ドキュメントのメタ変更を「新機能」と誤分類しないための安全弁。「データの修正・追加」は `data/**` の変更を独立に判定する（後述「変更ファイルパスベース」「コミット件名ベース」を参照）。
 
    この大原則のもとで、各項目を独立に評価（複数該当可、大文字小文字無視・部分一致）。
 
@@ -150,7 +150,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms
    - 「変更の種類」節: 手順 3 の結果で各 `- [ ]` / `- [x]` を決定。**項目順序は必ずテンプレ通り**（バグ修正 / 新機能 / データの修正・追加 / リファクタリング / ドキュメント / CI/CD / その他）。
    - 「変更内容」節: コミット件名と変更ファイルから短い箇条書きを生成。`summary` があればそれを優先。データのみの PR では追加・修正した路線・駅などを箇条書きで列挙すると親切。
    - 「テスト」節:
-     - **Step 1 で `cargo` チェックを実行していない（コード／データ変更なし）場合は 3 項目すべて OFF**（`skip_checks` より優先）。本文末尾に「省略: コード変更なし」等の短い注記を残す。
+     - **判定基準: 手順 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 など必要なら呼び出し側が後から編集する前提）。
