Communityコーディング&開発github.com

Who Changed

コードを書いた人、チケット、ブランチ、プルリクエストをローカルのgit履歴のみから特定します。

Who Changed とは?

このスキルは、ローカルgit履歴のみを使用して、コードの変更者、チケット番号、ブランチ名、プルリクエストを特定するためのものです。ユーザーが「このコードを書いたのは誰?」「この関数を変更したのは誰?」などと尋ねたときに、git blameやログ解析を自動化して迅速に答えを提供します。 サポートするプラットフォームは、Bitbucket、GitHub、GitLabの主要なコードホストで、リモートのURLから自動的に検出します。スクリプトはファイル全体、行範囲、関数名による指定に対応し、結果には著者名、コミットハッシュ、チケット番号、ブランチ名、日付、PR番号、クリック可能なコミットURLとPR URLが含まれます。 また、ホワイトスペースの変更やコードの移動を無視するスマートなblameモードがデフォルトで有効で、実際の動作変更者を正確に特定します。結果はTSV形式で出力され、AIが整形してユーザーにわかりやすく表示します。 このスキルは、コードレビューやデバッグ、履歴調査の効率を大幅に向上させ、手動で行っていたファイルを開いてblameビューに移動し、ハッシュをコピーしてコードホストに貼り付けるという一連の作業を1つのコマンドで完結させます。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Daniyal12333/claude-code-skills/tree/HEAD/who-changed

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

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 で失敗します。自分で範囲を解決してください:

  1. 宣言をgrepします。例:^\\s*(public|private|protected)?\s*(async\s+)?<name>\s*[(<]、アロー関数プロパティの場合は <name>\s*[:=]\s*(function|\()。HTMLやCSSの場合は、リテラルセレクター、キー、またはブロックをgrepします。
  2. その行から読み取り、最初の { からブレースマッチを行い、閉じ行を見つけます。
  3. 名前が複数の場所に一致する場合は、一致をリストアップしてどれかを尋ねてください。 最初のものを黙って選択しないでください。一般的な名前はしばしば複数のファイルに存在します。

行番号はワーキングツリーからのものであり、ユーザーがエディタで見ているものと一致します。

明示的な範囲(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マージがないメインラインに直接コミットされたか、マージサブジェクトが規則に従っていない
squashPR番号が (#123) サフィックスから読み取られたスカッシュマージされたため、そのPR内の行ごとの履歴は平らになっている
low-no-ticketメッセージにもブランチにもチケットがないチケットが記録されていないことを明記してください。 推測しないでください
low-integration-branch名前にチケットがないブランチ経由でマージされたPRは統合ブランチが着地した場所であり、コードが書かれた場所ではありません。信頼度は低いです
uncommittedNot 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-...)、チケット正規表現を文字列の先頭に固定しないでください。

関連スキル