From 1659a4aef8f525003c8c3f5ce1466f33d313f8ad Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Sat, 12 Sep 2026 00:45:06 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E7=AE=A1=E7=90=86=E6=89=8B=E9=A0=86=E3=82=92=20jj=20?= =?UTF-8?q?=E3=81=8B=E3=82=89=20git=20=E3=81=B8=E6=88=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PbzF82KsZBG6gbXqXpPWyr --- .claude/skills/benchmark-gql/SKILL.md | 2 +- .claude/skills/create-pr/SKILL.md | 126 ++++++++++++-------------- AGENTS.md | 46 +++++----- 3 files changed, 81 insertions(+), 93 deletions(-) diff --git a/.claude/skills/benchmark-gql/SKILL.md b/.claude/skills/benchmark-gql/SKILL.md index c6c85f36..ee7148af 100644 --- a/.claude/skills/benchmark-gql/SKILL.md +++ b/.claude/skills/benchmark-gql/SKILL.md @@ -63,7 +63,7 @@ CPU Time はリクエストと `cf-ray` で突き合わせる。`wrangler tail` 3. **レポートの「所見」節を埋める。** ここだけは自動生成しない。差が出たクエリについて、 `src/graphql/query.rs` や `stationapi/src/use_case/interactor/query.rs` の実装、 - および `jj diff --from 'master@origin' --to 'dev@origin'` を見て、 + および `git diff origin/master..origin/dev` を見て、 **どの変更が効いているか**を書く。差が出なかったこと自体が結論なら、それも明記する。 仮説を確かめたいときは、一時的なケース定義を作って `--queries` と `--out-dir` を diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md index 9b4aff43..2518ccc1 100644 --- a/.claude/skills/create-pr/SKILL.md +++ b/.claude/skills/create-pr/SKILL.md @@ -14,7 +14,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms | 項目 | 既定値 / 推論元 | | ---- | ---- | | `base` | リポジトリの既定ブランチ(`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`、StationAPI では通常 `dev`) | -| `head` | 作業コミット `@` に最も近いローカルブックマーク(`jj log -r 'heads(::@ & bookmarks())' --no-graph -T 'local_bookmarks.map(\|b\| b.name()).join("\n") ++ "\n"'`)。該当が無ければ手順 1 で切り出す。**出力が 2 行以上のときは自動選択しない**(同一コミットに複数ブックマークがある、または `@` の上流に head が複数ある場合)。候補を列挙してユーザーに確認してから進める | +| `head` | カレントブランチ(`git rev-parse --abbrev-ref HEAD`)。出力が `HEAD`(detached HEAD)のとき、または `base` と同じブランチのときは自動選択せず、手順 1 で切り出す | | `title` | 下の「タイトル推論ルール」参照 | | `summary` | 空なら「概要」「変更内容」本文はテンプレのコメントのみ残す | | `related_issue` | **ユーザー入力を最優先**。指定が `#N`(数値のみ)なら `Closes #N`、`Closes #N` / `Fixes #N` / `Refs #N` 形式ならその接頭語を保って出力。`related_issue` が空のときに限り、コミット件名から `Closes #N` / `Fixes #N` / `Refs #N` を抽出(接頭語を維持。`#N` 単体表記なら `Closes` を補う)。両方とも見つからなければ節のコメントのみ | @@ -23,7 +23,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms ### タイトル推論ルール -`@origin..@origin`(jj revset)のコミット件名を対象に、以下を順に試す: +`origin/..origin/` のコミット件名を対象に、以下を順に試す: 1. **コミット 1 件のみ**: その件名をそのまま使う。 2. **コミット複数・共通テーマあり**: 最新コミットの件名、もしくは件名群を要約した日本語の単文を使う。 @@ -34,28 +34,22 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms ## 前提条件 -- カレントディレクトリが `jj root` で解決できるリポジトリ内。 -- **バージョン管理は jj で行う。** このリポジトリは colocated(`.jj/` と `.git/` が同居)なので `git` も動いてしまうが、書き込み系の git コマンド(`commit` / `switch` / `branch` / `push` など)は使わない。jj が次回起動時に Git の ref を再取り込みし、変更が破棄されるか divergent change として二重化する。詳細は `AGENTS.md` の **Version Control (Jujutsu)** を参照。 -- `gh` CLI が認証済み(PR 操作だけは従来どおり `gh`)。 -- `head` ブックマークが origin に push 済み。未 push の場合はユーザーに push の可否を確認する(勝手に push しない)。散文の前提で終わらせず、手順 2 でローカルと origin の commit ID を突き合わせて機械的に検出する。 -- **ref 名をシェルソースへ直接埋め込まない。** 本書の `` / `` は説明用のプレースホルダ。実際のコマンドでは値を `BASE_REF` / `HEAD_REF` に取り込み、以降は必ず `"$BASE_REF"` / `"$HEAD_REF"` で参照する。jj のブックマーク名は git の ref 名と同じ規則で、`'` / `$( )` / バッククォート / `;` を含められるため、リテラルを直接置換すると構文が壊れるか、意図しないコマンドが実行される。値はコマンド出力から取り込む(ユーザー指定がある場合のみ、その値を代入する): +- カレントディレクトリが `git rev-parse --show-toplevel` で解決できるリポジトリ内。 +- `gh` CLI が認証済み。 +- `head` ブランチが origin に push 済み。未 push の場合はユーザーに push の可否を確認する(勝手に push しない)。散文の前提で終わらせず、手順 2 でローカルと origin の commit ID を突き合わせて機械的に検出する。 +- **ref 名をシェルソースへ直接埋め込まない。** 本書の `` / `` は説明用のプレースホルダ。実際のコマンドでは値を `BASE_REF` / `HEAD_REF` に取り込み、以降は必ず `"$BASE_REF"` / `"$HEAD_REF"` で参照する。git の ref 名は `'` / `$( )` / バッククォート / `;` を含められるため、リテラルを直接置換すると構文が壊れるか、意図しないコマンドが実行される。値はコマンド出力から取り込む(ユーザー指定がある場合のみ、その値を代入する): ```bash BASE_REF="$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)" - # 候補は一度 HEAD_CANDIDATES に受ける。0 件/2 件以上をそのまま代入すると、 - # 内部改行を含んだ値が gh --head や slug 処理へそのまま流れる - HEAD_CANDIDATES="$(jj log -r 'heads(::@ & bookmarks())' --no-graph \ - -T 'local_bookmarks.map(|b| b.name()).join("\n") ++ "\n"')" - if [ "$(printf '%s\n' "$HEAD_CANDIDATES" | grep -c .)" -ne 1 ]; then - printf '%s\n' "$HEAD_CANDIDATES" # 候補を列挙してユーザーに確認する(自動選択しない) - exit 1 + HEAD_REF="$(git rev-parse --abbrev-ref HEAD)" + if [ "$HEAD_REF" = "HEAD" ] || [ "$HEAD_REF" = "$BASE_REF" ]; then + printf 'PR の head にできるブランチに居ない: %s\n' "$HEAD_REF" >&2 + exit 1 # 手順 1 で切り出す fi - HEAD_REF="$HEAD_CANDIDATES" - # ref 名の文字種を検証する。gh --head・ファイル名 slug・revset の - # 文字列リテラルのすべてで安全に使える集合か(理由は下の 3 項目)。 - # BASE_REF / HEAD_REF に同じ規則を適用する + # ref 名の文字種を検証する。gh --head・ファイル名 slug・シェル展開の + # すべてで安全に使える集合か。BASE_REF / HEAD_REF に同じ規則を適用する validate_ref() { case "$1" in '' | *[!A-Za-z0-9._/-]*) @@ -65,34 +59,31 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms validate_ref "$BASE_REF" || exit 1 validate_ref "$HEAD_REF" || exit 1 - # origin 上のブックマークを commit ID へ解決する。 + # origin 上のブランチを commit ID へ解決する。 # 実際の解決は fetch 後(手順 2)に一度だけ行うので、ここでは定義のみ resolve_remote_rev() { validate_ref "$1" || return 1 - rev="$(jj log -r "remote_bookmarks(exact:\"$1\", exact:\"origin\")" \ - --no-graph -T 'commit_id ++ "\n"')" - if [ "$(printf '%s\n' "$rev" | grep -c .)" -ne 1 ]; then - printf 'origin 上で commit ID 1 件に解決できない: %s\n' "$1" >&2; return 1 + rev="$(git rev-parse --verify --quiet "refs/remotes/origin/$1")" + if [ -z "$rev" ]; then + printf 'origin 上に存在しない: %s\n' "$1" >&2; return 1 fi printf '%s\n' "$rev" } ``` -- **ブックマーク名を jj の revset へ直接連結しない。** `&` と `|` は git の ref 名には使えるが jj では revset の演算子で、`"$BASE_REF@origin..$HEAD_REF@origin"` のような連結は `Error: Revision ... doesn't exist` で落ちる。`jj bookmark create` 自身がこの種の名前を拒否するため発生源は Git 側で作られた/fetch されたブランチに限られるが、上のように **`BASE_REV` / `HEAD_REV`(commit ID)へ一度解決し、以降の revset は commit ID だけで組み立てる**。`gh` に渡すのは GitHub 上のブランチ名なので `$BASE_REF` / `$HEAD_REF` のままでよい。 -- **`<名前>@origin` 形式ではなく `remote_bookmarks(exact:"<名前>", exact:"origin")` で解決する。** 裸のシンボルは revset の構文解析にかかるため、末尾が `-` の名前は `-`(親)演算子と誤読され `Failed to parse revset: Syntax error` になる(jj 0.44.0 で確認。`dev-@origin` が失敗する一方、名前の途中の `-` は `feature/jj-workflow-docs@origin` のように問題なく解決する)。`exact:` は文字列リテラルなので、この解析差に依存しない。 -- **`remote_bookmarks()` は該当が無くてもエラーにならない。** `<名前>@origin` が `Error: Revision ... doesn't exist` で落ちるのに対し、`remote_bookmarks(exact:...)` は終了コード 0 のまま空を返す。切り替えるなら **解決結果が commit ID ちょうど 1 行であることの検証が必須**(上の `resolve_remote_rev`)。省くと、未 push のブックマークが空の `BASE_REV` / `HEAD_REV` として無言で通過する。 -- **`BASE_REF` / `HEAD_REF` が `^[A-Za-z0-9._/-]+$` に一致しない場合は自動で進めない。** `local_bookmarks.map(|b| b.name())` は要引用の名前を revset 用の引用付き(`"feature/x&dev"`、内部の `"` は `\"`)で返す。この表記は revset では正しいが `gh --head` やファイル名 slug には使えず、`exact:"…"` の文字列リテラルへ埋め込むと引用が壊れる。`gh repo view` 由来の `BASE_REF` にも同じ検査を適用し(`resolve_remote_rev` の `case` がこれを担う)、一致しない値が返ったらユーザーに正しいブランチ名を確認する。 +- **ref は `refs/remotes/origin/<名前>` / `refs/heads/<名前>` の完全形で解決する。** 短縮名を `git rev-parse` に渡すと、同名のタグやローカルブランチが優先されて意図と違うコミットを指すことがある。`--verify --quiet` を付けると、存在しない ref は終了コード 1 と空出力になるので、**解決結果が空でないことの検証が必須**。省くと未 push のブランチが空の `BASE_REV` / `HEAD_REV` として無言で通過する。 +- **`BASE_REF` / `HEAD_REF` が `^[A-Za-z0-9._/-]+$` に一致しない場合は自動で進めない。** git の ref 名には `gh --head` やファイル名 slug に使えない文字、シェルに解釈される文字が入り得る。`gh repo view` 由来の `BASE_REF` にも同じ検査を適用し(`resolve_remote_rev` の `case` がこれを担う)、一致しない値が返ったらユーザーに正しいブランチ名を確認する。 ## 手順 -1. **head / base の整合性チェックと自動ブックマーク切り出し** +1. **head / base の整合性チェックと自動ブランチ切り出し** - `head` が `base` と同じブックマーク(例: `dev` の上で作業していて base も `dev`)や、そもそもブックマークが無い状態は、そのまま進めると PR が作れない。`jj status` で `@` の内容を確認し、以下のいずれかで救済する: + `base == head` になるケース(例: `dev` に居てデフォルト base も `dev`)や detached HEAD は、そのまま進めると PR が作れない。`git status` で作業ツリーの内容を確認し、以下のいずれかで救済する: - - `@` に変更がある、または未 push のコミットがある場合、**新しいブックマークを作ってそこに載せる**。 - - 何の変更も無い(`@` が empty で `trunk()` と同じ)場合は「PR 対象の差分が無い」と報告して中断する。 + - 作業中の変更(staged / unstaged / 直近の未 push コミット)がある場合、**新しいブランチを切ってそこに退避**してから続行する。 + - 何の変更も無い場合は「PR 対象の差分が無い」と報告して中断する。 - **ブックマーク名の推論**(`CONTRIBUTING.md` の命名規則に従う。git のブランチ名とそのまま対応する): + **ブランチ名の推論**(`CONTRIBUTING.md` の命名規則に従う): | プレフィックス | 採用条件 | | ---- | ---- | @@ -106,47 +97,48 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms 切り出し手順: ```bash - jj status # @ に何が入っているか必ず先に確認 - jj commit -m "<日本語単文>" # @ に説明を付けて確定し、新しい空の @ を作る - jj bookmark create -r @- # 直前に確定したコミットにブックマークを置く - jj git push -b + git switch -c + git status # 何をコミットするか必ず先に確認 + # 未コミットなら: + git add -u # 追跡済みの staged/unstaged をまとめてステージ + # 未追跡ファイルも退避対象なら明示的にパス指定で追加(`git add -A` / `.` は使わない): + # git add path/to/untracked-file ... + git commit -m "<日本語単文>" + git push -u origin ``` - - **jj にステージング領域は無く、未追跡ファイルという概念も無い**(`snapshot.auto-track = "all()"`)。`.gitignore` に載っていない一時ファイルは黙って `@` に入るので、`jj status` の出力を読んでから確定する。関係ないファイルは `jj restore ` で戻すか、`jj split` で別コミットに分ける。`git add` に相当する「一部だけ含める」操作は無い。 - - 変更が既に `@` ではなく確定済みコミット側にある場合は `jj commit` を飛ばし、`jj bookmark create -r ` で直接そのコミットに置く。 - - ブックマークは自動で追従しないので、追加コミット後は `jj bookmark set -r @-` で必ず動かす。忘れると push が空振りする。 + - **`git add -A` / `git add .` は使わない。** `.gitignore` に載っていない一時ファイルまで巻き込む。`git status` の出力を読んでから、追跡済みは `git add -u`、未追跡は明示パスで追加する。関係ないファイルが入ったら `git restore --staged ` で外す。 + - 変更が既にコミット済みでブランチだけが無い(`dev` の上に直接コミットした等)場合は、`git switch -c ` だけでそのコミットを新ブランチへ引き継げる。`dev` 側を元に戻す必要があれば、`git switch dev && git reset --hard origin/dev` の可否をユーザーに確認してから実行する。 - コミット前に下記の品質チェックを通す(`CONTRIBUTING.md` ルール、手順 3 で定義する「コード本体パス」に変更が無ければ省略可): - `cargo fmt --all -- --check` - `make clippy` - `make test` - データのみの変更(`data/*.csv` 等)を含む場合は `cargo run -p data_validator` も流す。 - - push は新規ブックマークなので安全だが、実行前にユーザーへ要約(ブックマーク名・含めるファイル・コミットメッセージ案)を提示して承認を取る。未トラックのブックマークは `-b` 指定で自動的にトラックされる。 + - push は新規ブランチなので安全だが、実行前にユーザーへ要約(ブランチ名・含めるファイル・コミットメッセージ案)を提示して承認を取る。 以降の手順では推論後の head を使う。 2. **状態確認とモード決定(新規作成 / 更新)** - - `jj git fetch` を実行(remote bookmark を更新する)。**`BASE_REV` / `HEAD_REV` の解決は必ず fetch の後に行う。** fetch は remote bookmark を動かすので、先に解決すると古い commit ID で差分を測ることになる。加えて `remote_bookmarks()` が読むのは jj が記録している remote の状態なので、まだ fetch していないブックマークは解決できない。前提条件で済ませておくのは ref 名の文字種検証までにとどめる。 - - **fetch 対象は `--remote` と `--branch` で明示する。** 引数無しの `jj git fetch` は remote を `git.fetch`、ブックマークを `remotes..fetch-bookmarks` の設定から決める。これらはユーザーグローバル設定なので、別マシンでは `origin` や対象ブックマークが更新されないことがあり、その場合 `@origin` を読む後続の解決と一致検査が古い値のまま通ってしまう。`--branch` のパターンは既定が glob なので `exact:` を付ける。 - - **`BASE_REV` / `HEAD_REV` は origin 側の位置なので、ローカルの `HEAD_REF` がそれと一致することを機械的に確かめる。** 一致しなければ未 push のコミットがあり、そのまま進むとその分を含まない範囲で PR が組み上がる。検出したら push の可否をユーザーに確認して中断する(勝手に push しない)。ブックマークは自動追従しないので、`jj commit` 後に `jj bookmark set` を忘れた場合もここに現れる。 - - **ローカルに `HEAD_REF` が無い場合も中断する。** 空は「未 push が無い」証拠ではなく、単に検証できていない状態(ローカルで削除済み、あるいは origin にしか無いブックマークを `head` に指定した、など)。`jj bookmark track "<名前>@origin"` でローカルへ取り込んでからやり直す。 - - コミットとファイル差分の**両方**を確認する。`jj log` はコミットの有無しか見ないため、空コミットだけが載ったブックマークが通過してしまう。 + - `git fetch` で `BASE_REF` / `HEAD_REF` の remote-tracking ref を更新する。**`BASE_REV` / `HEAD_REV` の解決は必ず fetch の後に行う。** 先に解決すると古い commit ID で差分を測ることになる。前提条件で済ませておくのは ref 名の文字種検証までにとどめる。 + - **fetch 対象は refspec で明示する。** ブランチ名だけを渡す形(`git fetch origin `)は remote-tracking ref の更新が `remote.origin.fetch` の設定に依存する。別マシンで refspec が絞られていると `refs/remotes/origin/` が古いまま後続の解決と一致検査を通ってしまう。 + - **`BASE_REV` / `HEAD_REV` は origin 側の位置なので、ローカルの `HEAD_REF` がそれと一致することを機械的に確かめる。** 一致しなければ未 push のコミットがあり、そのまま進むとその分を含まない範囲で PR が組み上がる。検出したら push の可否をユーザーに確認して中断する(勝手に push しない)。 + - **ローカルに `HEAD_REF` が無い場合も中断する。** 空は「未 push が無い」証拠ではなく、単に検証できていない状態(ローカルで削除済み、あるいは origin にしか無いブランチを `head` に指定した、など)。`git switch --track "origin/<名前>"` でローカルへ取り込んでからやり直す。 + - コミットとファイル差分の**両方**を確認する。`git log` はコミットの有無しか見ないため、空コミットだけが載ったブランチが通過してしまう。 ```bash - # 設定(git.fetch / remotes..fetch-bookmarks)に左右されないよう - # remote と対象ブックマークを明示する - jj git fetch --remote origin \ - --branch "exact:$BASE_REF" \ - --branch "exact:$HEAD_REF" + # remote.origin.fetch の設定に左右されないよう refspec で明示する + git fetch origin \ + "+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF" \ + "+refs/heads/$HEAD_REF:refs/remotes/origin/$HEAD_REF" BASE_REV="$(resolve_remote_rev "$BASE_REF")" || exit 1 # 前提条件で定義したヘルパ。解決はここが最初 HEAD_REV="$(resolve_remote_rev "$HEAD_REF")" || exit 1 # ローカルの HEAD_REF を解決し、origin の先端と一致することを確認する。 - # HEAD_REF は resolve_remote_rev の case で検証済み - HEAD_LOCAL_REV="$(jj log -r "bookmarks(exact:\"$HEAD_REF\")" --no-graph \ - -T 'commit_id ++ "\n"')" - # 該当が無くても終了コードは 0 なので、|| ではなく中身で判定する + # HEAD_REF は validate_ref で検証済み + HEAD_LOCAL_REV="$(git rev-parse --verify --quiet "refs/heads/$HEAD_REF")" + # 該当が無いと空を返すので、中身で判定する if [ -z "$HEAD_LOCAL_REV" ]; then printf 'ローカルに %s が無い。origin だけを見て組むと一致を検証できない。\n' "$HEAD_REF" >&2 - printf 'jj bookmark track "%s@origin" でトラックしてからやり直す。\n' "$HEAD_REF" >&2 + printf 'git switch --track "origin/%s" で取り込んでからやり直す。\n' "$HEAD_REF" >&2 exit 1 fi if [ "$HEAD_LOCAL_REV" != "$HEAD_REV" ]; then @@ -155,22 +147,21 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms exit 1 # 未 push の変更がある。push の可否をユーザーに確認してから進む fi - jj log -r "$BASE_REV..$HEAD_REV" --no-graph \ - -T 'commit_id.short() ++ " " ++ description.first_line() ++ "\n"' - jj diff --name-only --from "$BASE_REV" --to "$HEAD_REV" + git log --oneline "$BASE_REV..$HEAD_REV" + git diff --name-only "$BASE_REV" "$HEAD_REV" ``` - コミット一覧が空、または `jj diff --name-only` の出力が空の場合は「PR 対象の差分が無い」と報告し、**既存 PR の検索へ進まずに中断する**。 + コミット一覧が空、または `git diff --name-only` の出力が空の場合は「PR 対象の差分が無い」と報告し、**既存 PR の検索へ進まずに中断する**。 - `gh pr list --base "$BASE_REF" --head "$HEAD_REF" --state open --json number,url,body` で既存 open PR を確認。 - **存在しない場合**: 新規作成モード。以降、手順 5 で `gh pr create`。 - **存在する場合**: 更新モード。既存本文を最新差分で再生成する。以降、手順 5 で `gh pr edit`。タイトルは既存を**原則尊重**(ユーザー推論より優先)。ただし手順 5 の整合性チェックで主題が大きくズレていると判断した場合のみ更新案を提示する。 3. **変更の種類を判定** - `@origin..@origin` のコミット件名と変更ファイルを取得: + `origin/..origin/` のコミット件名と変更ファイルを取得: ```bash - jj log -r "$BASE_REV..$HEAD_REV" --no-graph -T 'description.first_line() ++ "\n"' - jj diff --name-only --from "$BASE_REV" --to "$HEAD_REV" + git log --pretty=%s "$BASE_REV..$HEAD_REV" + git diff --name-only "$BASE_REV" "$HEAD_REV" ``` **大原則: 判定はアプリ挙動/データに対する変更かどうかで決める**。下の「コード本体パス」が一切変わっていない場合、「バグ修正」「新機能」「リファクタリング」は OFF(コミット件名に `fix` / `feat` 等の語があっても)。スキル・設定・ドキュメントのメタ変更を「新機能」と誤分類しないための安全弁。「データの修正・追加」は `data/**` の変更を独立に判定する(後述「変更ファイルパスベース」「コミット件名ベース」を参照)。 @@ -261,14 +252,14 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms - 連続した `_` は 1 つに畳み、先頭・末尾の `_` は除去 - 必要なら長さを 100〜200 文字程度に切り詰める - 生のブックマーク名を直結するとサブディレクトリ解釈や制御文字混入で Write/削除が失敗する。バッククォートは **素のまま** 書く。escape しない。 + 生のブランチ名を直結するとサブディレクトリ解釈や制御文字混入で Write/削除が失敗する。バッククォートは **素のまま** 書く。escape しない。 2. 下の `gh` コマンドをサブシェル内で `trap` と一緒に実行する。`gh` の成功・失敗に関わらず `EXIT` / `INT` / `TERM` のどれでも一時ファイルを確実に削除されるようにする(`&&` で `rm` を繋ぐだけだと失敗時に `/tmp` にゴミが残る)。 3. `gh` 呼び出しと `rm`(を含む `trap`)は Bash tool の 1 呼び出し内で完結させる。別呼び出しで後片付けすると、前段の呼び出しがエラー/中断で終わった場合にクリーンアップが実行されない。 **新規作成モード** ```bash - # ref 名(ブックマーク名)をファイル名として安全な集合(A-Za-z0-9._-)にスラッグ化 + # ref 名(ブランチ名)をファイル名として安全な集合(A-Za-z0-9._-)にスラッグ化 REF_SLUG="$(printf '%s' "$HEAD_REF" \ | tr -d '\r\n' \ | tr -c 'A-Za-z0-9._-' '_' \ @@ -311,10 +302,9 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms ## 注意事項 - テンプレの節構成は改変しない。追加・削除はメンテナ承認が必要。 -- バージョン管理の操作は `jj` で行い、書き込み系の `git` コマンドは使わない(colocated リポジトリなので動いてしまうが、jj 側と食い違う)。 -- **push 済みのコミットを勝手に書き換えない。** `jj describe` / `jj squash` / `jj rebase` は履歴をその場で書き換え、次の `jj git push` が force-with-lease 相当でリモートのブックマークを巻き戻す。git の force push と同じ扱いで、必ずユーザーに確認する。 -- 操作をやり直したいときは手作業で戻さず `jj undo`(直前の操作を取り消す)/`jj op log`(操作履歴)を使う。 +- `git push --no-verify` や force push はしない。push が必要ならユーザーに確認。 +- **push 済みのコミットを勝手に書き換えない。** `git commit --amend` / `git rebase` は履歴を書き換え、反映には force push が要る。必ずユーザーに確認する。 - 既存 open PR を上書きしない(重複作成禁止)。 -- ブックマークのプレフィックスは `feature/` / `fix/` / `data/` / `chore/` / `release/` のみ使用(`pr-labeler.yml` のラベル自動付与に直結する)。 +- ブランチプレフィックスは `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` のガイドライン(変更内容と検証コマンドの記録)に沿う。 diff --git a/AGENTS.md b/AGENTS.md index b79d5275..333aff4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,42 +79,40 @@ The Worker is the workspace root package. `stationapi`, `preprocessor`, and `dat - **Connected routes** – `GetConnectedRoutes` performs a bounded breadth-first search across train-type line groups. Transfers join at a shared station group, route order and per-stop pass metadata are preserved, and each returned candidate receives a deterministic virtual line-group ID in the upper half of the `uint32` range. Revisiting station groups and already-used train types is rejected to prevent cycles. Exploration loads only line-group ID, station-station-type ID, station-group ID, and pass metadata; full station rows are fetched after the result set is fixed. The search is additionally capped at eight train types, 4,096 expanded states, 65,536 evaluated candidates, and 32 results to bound computation and result size. - Changes to the published contract require coordinated updates to `schema/public.graphql`, the async-graphql types in `src/graphql/`, and, when the shape of a value changes, `stationapi/src/model.rs` and the DTO conversions. -## Version Control (Jujutsu) -This repository is a **colocated jj/git checkout** — `.jj/` and `.git/` sit side by side. Agents run every version-control operation through `jj`. Do not run a `git` command that writes (`commit`, `switch`, `branch`, `push`, `rebase`, `stash`): jj re-imports the Git refs on its next invocation, so a Git-side change is either abandoned or resurfaces as a divergent change. `gh` remains the tool for pull requests, and GitHub Actions keeps consuming the Git side unchanged. +## Version Control (Git) +Version control is plain Git. `gh` is the tool for pull requests, and GitHub Actions consumes the same refs. -- **`trunk()` resolves to `dev@origin`,** a per-repository revset alias. Prefer it to a hard-coded branch name. jj 0.38.0 and later keep repository config outside the repository, so `.jj/repo/config.toml` is not the file to edit — run `jj config path --repo` to locate it, `jj config list 'revset-aliases."trunk()"'` to check it, and `jj config set --repo 'revset-aliases."trunk()"' dev@origin` to (re-)create it on a fresh clone. -- **There is no staging area and no untracked file.** The working copy is itself a commit, and jj snapshots every file under the root on each command (`snapshot.auto-track = "all()"`), so a scratch file lands in the change unless `.gitignore` covers it. Nothing corresponds to `git add`, so read `jj status` before describing a change and remove what does not belong — `jj restore ` to drop it, `jj split` to move it into a commit of its own. -- **Bookmarks are jj's branches, and they do not follow new commits.** After committing, move the bookmark yourself (`jj bookmark set -r @-`); forgetting it makes the next push a no-op. -- **Never rewrite a pushed commit without asking.** `jj describe`, `jj squash`, and `jj rebase` rewrite history in place, and the next `jj git push` moves the remote bookmark with force-with-lease semantics. Confirm with the user first, exactly as for a Git force push. -- **`jj undo` reverses the last operation** and `jj op log` lists them; prefer both to reconstructing state by hand. +- **`origin/dev` is the base for ordinary work.** Fetch it before branching so a feature branch does not start from a stale `dev`; `master` is release-only. +- **Stage deliberately.** `git add -u` picks up edits to tracked files; add an untracked file by explicit path rather than `git add -A` / `git add .`, so scratch files do not ride along. Read `git status` before committing. +- **Never rewrite a pushed commit without asking.** `git commit --amend`, `git rebase`, and anything that needs `git push --force-with-lease` rewrite published history. Confirm with the user first. +- **The stash stack is shared with every worktree of this repository.** Prefer a temporary WIP commit to set work aside; if you must stash, use `git stash push -u -m ""` and restore with `git stash apply ` rather than a bare `git stash pop`. A typical change: ```bash -jj git fetch # refresh dev@origin and the other remote bookmarks -jj new 'trunk()' # start a new change on top of dev@origin -# ... edit files; jj snapshots them automatically ... -jj status # confirm exactly what the change contains -jj commit -m "日本語の単文" # describe @ and open a fresh empty working copy on top -jj bookmark create feature/ -r @- -jj git push -b feature/ +git fetch origin dev # refresh origin/dev +git switch -c feature/ origin/dev +# ... edit files ... +git status # confirm exactly what the change contains +git add -u # plus explicit paths for new files +git commit -m "日本語の単文" +git push -u origin feature/ ``` -Equivalents for the operations this guide and `.claude/skills/create-pr` rely on: +Commands this guide and `.claude/skills/create-pr` rely on: | Purpose | Command | | --- | --- | -| Repository root | `jj root` | -| Working-copy state | `jj status` | -| History | `jj log` (`jj log -r 'trunk()..@'` for the current change set) | -| Bookmark closest to `@` | `jj log -r 'heads(::@ & bookmarks())' --no-graph -T 'local_bookmarks.map(\|b\| b.name()).join("\n") ++ "\n"'` — the trailing `++ "\n"` is what separates one revision's output from the next; without it several heads print as one run-on line. May print more than one name (several bookmarks on one commit, or several heads above `@`). Count the lines first; on more than one, present the candidates and ask which to use instead of taking the first. | -| Commit subjects on a bookmark | `jj log -r 'dev@origin..@origin' --no-graph -T 'description.first_line() ++ "\n"'` | -| Files changed against a base | `jj diff --name-only --from 'dev@origin' --to '@origin'` | -| Rebase onto the latest `dev` | `jj git fetch && jj rebase -d 'trunk()'` | +| Repository root | `git rev-parse --show-toplevel` | +| Working-tree state | `git status --short` | +| Current branch | `git rev-parse --abbrev-ref HEAD` | +| Commit subjects on a branch | `git log --pretty=%s origin/dev..origin/` | +| Files changed against a base | `git diff --name-only origin/dev..origin/` | +| Rebase onto the latest `dev` | `git fetch origin dev && git rebase origin/dev` | -`CONTRIBUTING.md` still documents the Git workflow, because outside contributors are not required to install jj. Keep the two aligned in intent — base branch, naming convention, and pull-request rules are identical; only the commands differ. +`CONTRIBUTING.md` documents the same workflow for outside contributors. Keep the two aligned — base branch, naming convention, and pull-request rules are identical. ## Contribution Guidelines -- **Git-flow** – Follow Git-flow with `dev` serving as this repository's `develop` branch. Create ordinary work bookmarks from the latest `trunk()` (`dev@origin`) with `jj new 'trunk()'`, use the `feature/` naming convention, and target their pull requests to `dev`. Do not create or target a bookmark named `develop`. **Version Control (Jujutsu)** above has the full command sequence. +- **Git-flow** – Follow Git-flow with `dev` serving as this repository's `develop` branch. Create ordinary work branches from the latest `origin/dev`, use the `feature/` naming convention, and target their pull requests to `dev`. Do not create or target a branch named `develop`. **Version Control (Git)** above has the full command sequence. - **Pull requests** – Assign every pull request to `@TinyKitten` when creating it, open it as ready for review rather than as a draft, and use `.github/pull_request_template.md` without omitting or replacing its sections or checklists. - **Prioritize quality and performance over implementation speed** – Always favor code quality and runtime performance over velocity. Be mindful of algorithmic complexity and look for opportunities to replace O(n×m) linear scans with O(n+m) indexed lookups (e.g., HashMaps). The indexes are rebuilt on every isolate start and every request scans them, so prefer indexed lookups over repeated full scans. When a change affects performance, document the before/after complexity and query plan impact in the pull request. - Document the commands you executed (for example, ``make fmt && make clippy && make test``) and their outcomes in every pull request. From 512e1bdf3e8657a4dd6140635edaec958e7ee63f Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Sat, 12 Sep 2026 00:53:53 +0900 Subject: [PATCH 2/3] =?UTF-8?q?CodeRabbit=20=E3=81=AE=E6=8C=87=E6=91=98?= =?UTF-8?q?=E3=81=AB=E6=B2=BF=E3=81=A3=E3=81=A6=20fetch=20=E3=81=AE=20refs?= =?UTF-8?q?pec=20=E6=98=8E=E7=A4=BA=E3=81=A8=20hard=20reset=20=E5=89=8D?= =?UTF-8?q?=E3=81=AE=E4=BF=9D=E8=AD=B7=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PbzF82KsZBG6gbXqXpPWyr --- .claude/skills/benchmark-gql/SKILL.md | 10 +++++++++- .claude/skills/create-pr/SKILL.md | 2 +- AGENTS.md | 9 +++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.claude/skills/benchmark-gql/SKILL.md b/.claude/skills/benchmark-gql/SKILL.md index ee7148af..bd339dae 100644 --- a/.claude/skills/benchmark-gql/SKILL.md +++ b/.claude/skills/benchmark-gql/SKILL.md @@ -63,9 +63,17 @@ CPU Time はリクエストと `cf-ray` で突き合わせる。`wrangler tail` 3. **レポートの「所見」節を埋める。** ここだけは自動生成しない。差が出たクエリについて、 `src/graphql/query.rs` や `stationapi/src/use_case/interactor/query.rs` の実装、 - および `git diff origin/master..origin/dev` を見て、 + および `origin/master` と `origin/dev` の差分を見て、 **どの変更が効いているか**を書く。差が出なかったこと自体が結論なら、それも明記する。 + ```bash + # remote.origin.fetch の設定に左右されないよう refspec で明示する + git fetch origin \ + "+refs/heads/master:refs/remotes/origin/master" \ + "+refs/heads/dev:refs/remotes/origin/dev" + git diff origin/master..origin/dev + ``` + 仮説を確かめたいときは、一時的なケース定義を作って `--queries` と `--out-dir` を スクラッチ領域へ向けて回す。正式なカタログと `benchmarks/` を汚さずに試せる。 diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md index 2518ccc1..9036350a 100644 --- a/.claude/skills/create-pr/SKILL.md +++ b/.claude/skills/create-pr/SKILL.md @@ -107,7 +107,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms git push -u origin ``` - **`git add -A` / `git add .` は使わない。** `.gitignore` に載っていない一時ファイルまで巻き込む。`git status` の出力を読んでから、追跡済みは `git add -u`、未追跡は明示パスで追加する。関係ないファイルが入ったら `git restore --staged ` で外す。 - - 変更が既にコミット済みでブランチだけが無い(`dev` の上に直接コミットした等)場合は、`git switch -c ` だけでそのコミットを新ブランチへ引き継げる。`dev` 側を元に戻す必要があれば、`git switch dev && git reset --hard origin/dev` の可否をユーザーに確認してから実行する。 + - 変更が既にコミット済みでブランチだけが無い(`dev` の上に直接コミットした等)場合は、`git switch -c ` だけでそのコミットを新ブランチへ引き継げる。`dev` 側を元に戻す必要があれば、**`git status --short` が空であることを確認してから** `git switch dev && git reset --hard origin/dev` の可否をユーザーに確認して実行する。出力があるなら hard reset は追跡済みファイルの staged/unstaged 変更を問答無用で捨てるので、先に WIP コミット(推奨)か名前付き stash(`git stash push -u -m ""`。stash スタックは全 worktree 共有なので `git stash pop` ではなく `git stash apply ` で戻す)へ退避する。 - コミット前に下記の品質チェックを通す(`CONTRIBUTING.md` ルール、手順 3 で定義する「コード本体パス」に変更が無ければ省略可): - `cargo fmt --all -- --check` - `make clippy` diff --git a/AGENTS.md b/AGENTS.md index 333aff4a..5be4b3f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,15 +85,16 @@ Version control is plain Git. `gh` is the tool for pull requests, and GitHub Act - **`origin/dev` is the base for ordinary work.** Fetch it before branching so a feature branch does not start from a stale `dev`; `master` is release-only. - **Stage deliberately.** `git add -u` picks up edits to tracked files; add an untracked file by explicit path rather than `git add -A` / `git add .`, so scratch files do not ride along. Read `git status` before committing. - **Never rewrite a pushed commit without asking.** `git commit --amend`, `git rebase`, and anything that needs `git push --force-with-lease` rewrite published history. Confirm with the user first. +- **Update a remote-tracking ref with an explicit refspec.** `git fetch origin dev` leaves `refs/remotes/origin/dev` to whatever `remote.origin.fetch` happens to be; a narrowed refspec on one machine silently leaves `origin/dev` stale, and every branch, rebase, and diff taken from it is then measured against an old commit. - **The stash stack is shared with every worktree of this repository.** Prefer a temporary WIP commit to set work aside; if you must stash, use `git stash push -u -m ""` and restore with `git stash apply ` rather than a bare `git stash pop`. A typical change: ```bash -git fetch origin dev # refresh origin/dev +git fetch origin "+refs/heads/dev:refs/remotes/origin/dev" # explicit refspec, so origin/dev cannot be stale git switch -c feature/ origin/dev # ... edit files ... -git status # confirm exactly what the change contains -git add -u # plus explicit paths for new files +git status # confirm exactly what the change contains +git add -u # plus explicit paths for new files git commit -m "日本語の単文" git push -u origin feature/ ``` @@ -107,7 +108,7 @@ Commands this guide and `.claude/skills/create-pr` rely on: | Current branch | `git rev-parse --abbrev-ref HEAD` | | Commit subjects on a branch | `git log --pretty=%s origin/dev..origin/` | | Files changed against a base | `git diff --name-only origin/dev..origin/` | -| Rebase onto the latest `dev` | `git fetch origin dev && git rebase origin/dev` | +| Rebase onto the latest `dev` | `git fetch origin "+refs/heads/dev:refs/remotes/origin/dev" && git rebase origin/dev` | `CONTRIBUTING.md` documents the same workflow for outside contributors. Keep the two aligned — base branch, naming convention, and pull-request rules are identical. From 19406e4da382162ae2d5fc093b1c0afea47b643d Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Sat, 12 Sep 2026 00:59:03 +0900 Subject: [PATCH 3/3] =?UTF-8?q?dev=20=E3=81=AE=E5=BE=A9=E6=97=A7=E6=89=8B?= =?UTF-8?q?=E9=A0=86=E3=82=92=20worktree=20=E5=AE=89=E5=85=A8=E3=81=AA=20g?= =?UTF-8?q?it=20branch=20-f=20=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PbzF82KsZBG6gbXqXpPWyr --- .claude/skills/create-pr/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/create-pr/SKILL.md b/.claude/skills/create-pr/SKILL.md index 9036350a..96ceeef9 100644 --- a/.claude/skills/create-pr/SKILL.md +++ b/.claude/skills/create-pr/SKILL.md @@ -107,7 +107,7 @@ description: Create a GitHub pull request for TrainLCD StationAPI that conforms git push -u origin ``` - **`git add -A` / `git add .` は使わない。** `.gitignore` に載っていない一時ファイルまで巻き込む。`git status` の出力を読んでから、追跡済みは `git add -u`、未追跡は明示パスで追加する。関係ないファイルが入ったら `git restore --staged ` で外す。 - - 変更が既にコミット済みでブランチだけが無い(`dev` の上に直接コミットした等)場合は、`git switch -c ` だけでそのコミットを新ブランチへ引き継げる。`dev` 側を元に戻す必要があれば、**`git status --short` が空であることを確認してから** `git switch dev && git reset --hard origin/dev` の可否をユーザーに確認して実行する。出力があるなら hard reset は追跡済みファイルの staged/unstaged 変更を問答無用で捨てるので、先に WIP コミット(推奨)か名前付き stash(`git stash push -u -m ""`。stash スタックは全 worktree 共有なので `git stash pop` ではなく `git stash apply ` で戻す)へ退避する。 + - 変更が既にコミット済みでブランチだけが無い(`dev` の上に直接コミットした等)場合は、`git switch -c ` だけでそのコミットを新ブランチへ引き継げる。`dev` 側を元に戻す必要があれば、**作業ツリーに触れない `git branch -f dev origin/dev`** を使う(実行の可否はユーザーに確認する)。`dev` が別の worktree で checkout 済みなら git 自身がこのコマンドを拒否するので、取り違えも起きない。`git switch dev && git reset --hard origin/dev` は避ける: 追跡済みファイルの staged/unstaged 変更を問答無用で捨てるうえ、`dev` を別の worktree が持っていると `git switch` 自体が失敗する。どうしても checkout して戻すなら、`git worktree list` で `dev` の所在を確認し、その worktree で `git status --short` が空であることを確かめてから実行する(変更があれば先に WIP コミット(推奨)か名前付き stash(`git stash push -u -m ""`。stash スタックは全 worktree 共有なので `git stash pop` ではなく `git stash apply ` で戻す)へ退避する)。 - コミット前に下記の品質チェックを通す(`CONTRIBUTING.md` ルール、手順 3 で定義する「コード本体パス」に変更が無ければ省略可): - `cargo fmt --all -- --check` - `make clippy`