Who Changed は何をしますか?
「誰が、どのチケットで、いつこれを書いたのか」をターミナルから離れずに答えられます。必要なものはすべてローカルのgit履歴にあります。コードホストが追加するのはPRの説明だけであり、出力はそのリンクを提供します。
これにより、ファイルを開いて機能を見つけ、blameビューを開いてハッシュをコピーし、コードホストに貼り付けてPRに移動し、チケットと著者を読み取るという手動ループを置き換えます。
ユーザーが得られるもの
著者名、コミットハッシュ、チケット番号、ブランチ名、日付、PR番号、そしてクリック可能なコミットリンクとPRリンク。範囲に対する1つの主要な判定と、その範囲に触れたすべてのコミットのテーブルです。
スクリプトの実行
bash ~/.claude/skills/who-changed/scripts/who-changed.sh <file> [start end] [raw]
プロジェクトごとにスキルがインストールされている場合、パスは .claude/skills/who-changed/scripts/who-changed.sh になります。
<file>のみ:ファイル全体モード。トップコントリビューターと最新12件のコミット。<file> <start> <end>(または1行番号):行範囲モード。コミットごとの行所有権。- 末尾に
rawを追加:-w -C -Mなしのblame。ユーザーがバイトレベルの真実を求めている場合に使用。
環境変数のオーバーライド:WHO_CHANGED_BASE はPR検索が進む参照を設定します(デフォルト:origin/HEAD、次に origin/main、origin/master、main、master)。WHO_CHANGED_TICKET_REGEX はチケットパターンを変更します(デフォルト:[A-Z][A-Z0-9]+-[0-9]+)。
スクリプトはタブ区切りのレポートを出力し、リポジトリやネットワークに書き込むことはありません。ユーザー向けに整形してください。生のTSVをそのまま貼り付けないでください。
ステップ1:ターゲットを行範囲に解決する
スクリプトは数値を受け取るため、関数名は最初に範囲に変換する必要があります。Gitの -L :functionName: ショートカットは言語固有の関数パターンに依存し、多くの言語(特にTypeScriptのクラスメンバー)では no match で失敗します。自分で範囲を解決してください:
- 宣言をgrepします。例:
^\\s*(public|private|protected)?\s*(async\s+)?<name>\s*[(<]、アロー関数プロパティの場合は<name>\s*[:=]\s*(function|\()。HTMLやCSSの場合は、リテラルセレクター、キー、またはブロックをgrepします。 - その行から読み取り、最初の
{からブレースマッチを行い、閉じ行を見つけます。 - 名前が複数の場所に一致する場合は、一致をリストアップしてどれかを尋ねてください。 最初のものを黙って選択しないでください。一般的な名前はしばしば複数のファイルに存在します。
行番号はワーキングツリーからのものであり、ユーザーがエディタで見ているものと一致します。
明示的な範囲(285-300、285、L285-L300)が指定された場合はそのまま使用します。ファイルパスのみの場合はファイル全体モードを使用します。
ステップ2:出力を読む
## meta ブロックには、base(PR検索が進む参照)、base_tip_date、web と host(git remote から派生したリンクベース)、blameモード、およびファイルに未コミットの変更があるかどうかが含まれます。
## commits の行は所有行数でソートされています。判定は最初の行です。範囲の大部分を所有するコミットです。範囲が本当に共有されている場合(たとえば3年間に4人で構築された関数)は、ランナーアップにも言及してください。
スクリプトはすでにBitbucket、GitHub、GitLabのリモートに対して commit_url と pr_url を構築しています。これらが - の場合は、認識可能なリモートがありません。その旨を伝え、ホストをでっち上げずにリンクを省略してください。
以下のように表示します(以下の名前は架空のものです):
### calculateTotal - src/cart/cart.service.ts:42-61
**Alice Moreno** · `a1b2c3d` · 2023-02-08 · 20行中13行を所有
チケット SHOP-88 · ブランチ `SHOP-88-cart-totals` · PR #41
| Lines | Author | Commit | Date | Ticket | Branch | PR |
|---|---|---|---|---|---|---|
| 13 | Alice Moreno | a1b2c3d | 2023-02-08 | SHOP-88 | SHOP-88-cart-totals | #41 |
| 6 | Ben Carter | 9f8e7d6 | 2026-03-11 | SHOP-310 | SHOP-310-rounding-fix | not merged |
| 1 | Chen Wei | 4c5d6e7 | 2024-06-20 | — | release-2024-06 | #112 |
次にリンク、そして信頼フラグが発動した場合は警告行を表示します。
信頼ラベルとそれぞれについて伝えること
| Label | 意味 | ユーザーに伝えること |
|---|---|---|
ok | チケットブランチがPR経由でマージされた | 何も不要 |
local-only | コミットがまだ base にない | 「まだベースブランチにマージされていません」— PRはありません |
no-pr-merge | メインライン上にあるが、それを運ぶPRマージがない | メインラインに直接コミットされたか、マージサブジェクトが規則に従っていない |
squash | PR番号が (#123) サフィックスから読み取られた | スカッシュマージされたため、そのPR内の行ごとの履歴は平らになっている |
low-no-ticket | メッセージにもブランチにもチケットがない | チケットが記録されていないことを明記してください。 推測しないでください |
low-integration-branch | 名前にチケットがないブランチ経由でマージされた | PRは統合ブランチが着地した場所であり、コードが書かれた場所ではありません。信頼度は低いです |
uncommitted | Not Committed Yet | ローカルの未コミット編集。判定から除外 |
古いコードは長期存続の統合ブランチ(develop、release-*、ui-dev)を通じて到着することがよくあります。これらに対して正直な答えは「PR #112 を介してメインラインにマージされましたが、そのPRは統合ブランチです。これはコードが書かれたPRではありません。」です。
なぜ -w -C -M がデフォルトなのか
-w はホワイトスペースを無視し、-M はファイル内で移動した行を追跡し、-C は別のファイルからコピーされた行を追跡します。これは表面的なものではありません。2023年に書かれ2026年に再インデントされただけのブロックを考えてみてください(例示の数値):
- スマートモード(
-w -C -M):9行中9行が元の著者、2023年 — 正しい - rawモード:9行中8行が再インデントした人、2026年 — 古いコードをその人に帰属させる
したがって、名前付きの著者は動作を最後に変更した人であり、バイトを最後に触った人ではありません。それが重要な場合はそのように伝え、フォローアップとして raw を提案してください。
正直さのルール
- チケットIDをでっち上げないでください。 コミットメッセージにもブランチにもチケットがない場合:
—と報告し、チケットが記録されていないと言ってください。 - 著者をでっち上げないでください。 blameが決定的でない場合は、どのコミットが候補かを伝えてください。
- 「経由でマージされた」と「その中で作成された」を区別してください、低信頼フラグが発動するたびに。
base_tip_dateが古そうに見える場合は、ベース参照が最近フェッチされていないため、最近のマージが見逃されている可能性があることを指摘してください。git fetch originを提案してください。許可なく実行しないでください。
必要に応じて述べるべき制限
- 「なぜ」はgitにありません。 PRの説明、レビュアー、チケットのタイトルにはコードホストまたはトラッカーのAPIと認証情報が必要です。PRリンクが引き継ぎポイントです。
- スカッシュマージは履歴を平坦にします。 PRがスカッシュされた場合、1つのコミットが機能全体を所有し、その内部の行ごとの帰属は失われます。
--ancestry-pathはコミットをメインラインに運んだ最初のマージを選びます。 ネストされたフィーチャーブランチでは、これはチケット自身のPRではなく、中間のマージになる可能性があります。- ファイル全体のコントリビューターは名前変更を追跡しません。
git shortlogには--followがありません。コミットリストは名前変更を追跡しますが、コントリビューターの集計は追跡しません。 -Cは時々驚かせます。 コピー&ペーストされたコードを、そのコピー元のファイルに帰属させることがあります。- PR検出は3つのマージメッセージスタイルを認識します: Bitbucket(
Merged in <branch> (pull request #N))、GitHub(Merge pull request #N from ...)、スカッシュ(... (#N))。その他のスタイルはno-pr-mergeにフォールスルーします。
スクリプトが利用できない場合のフォールバック
git blame -w -C -M -L <start>,<end> --date=short -- <file>
git log -1 --date=short --pretty=format:"%H%n%an%n%ad%n%s%n%b" <sha>
git merge-base --is-ancestor <sha> origin/main # not an ancestor -> unmerged
git log --merges --ancestry-path --reverse --pretty=format:"%h|%s" <sha>..origin/main | grep -m1 "pull request"
git branch --contains <sha> # unmerged case
マージサブジェクトからブランチとPR番号を解析します。2つの落とし穴:git shortlog は明示的な HEAD が必要で、そうしないと静かに標準入力を読み取って何も返しません。また、ブランチ名にはプレフィックスが付くことがあるため(feature/SHOP-88-...)、チケット正規表現を文字列の先頭に固定しないでください。