stacked-prs について
Stacked PRs は、依存関係のあるプルリクエストのスタックを作成、公開、同期、検証するスキルです。ブランチの祖先関係とプロバイダー上のレビュー基準を分けて扱い、最初のアダプターとして GitHub の gh を記載し、マージとクリーンアップを ship-workflow に委任します。
主な機能
- 01ブランチと PR スタックの構成確認
- 02PR の公開とベースブランチ修正
- 03期待する旧値のリースによるリスタック
- 04レビュー可能なスタック単位の検証
- 05コミットトレーラーでスタック識別子を管理
- 06マージとクリーンアップを ship-workflow に委任
活用シーン
- 01依存ブランチを PR スタックとして公開
- 02親ブランチ変更後のスタック同期
- 03スタック構成とレビュー準備状況の確認
- 04承認済みスタックの委任マージを準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mathews-tom/ armory --skill stacked-prsインストール後、Claude Code で/stacked-prsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/cases.yamlreferences/github-native.mdmerge-discipline.mdmetadata-format.mdprovenance.mdprovider-adapters.mdstack-model.mdsync-algorithm.md詳細
スタック PR
ブランチのトポロジーを損なうことなく、スタックされたプルリクエストを作成、公開、同期、検証し、既存のスタックをマージする際に必要となるトポロジー、出自、ネイティブなメンバーシップを判断します。
パッケージの識別情報はプロバイダーに依存しません。ブランチの祖先関係の正本は Git であり、レビューのベースに関する正本はプロバイダーの PR メタデータです。最初に文書化されているプロバイダーアダプターは、gh を介した GitHub です。
既存の手動スタックまたは GitHub ネイティブスタックのマージ、マージ後の昇格とリターゲット、クリーンアップを実行するのは、ship-workflow スキルのスタックグラフ実行のみです。このスキルは明示的なスタック判断パケットを作成し、§5 でその実行に委任します。独自の手動マージやクリーンアップ手順はありません。
参照ファイル
| ファイル | 内容 | 読み込むタイミング |
|---|---|---|
references/stack-model.md | スタックの推論、明示的な順序付け、ネイティブスタックの検出、明示的なスタック判断パケット | スタックの確認、公開、検証、または委任を行う場合 |
references/provider-adapters.md | プロバイダーアダプターの契約と、PR の一覧表示、作成、リターゲット、確認に使う GitHub gh コマンド | PR の作成、リターゲット、確認を行う場合 |
references/sync-algorithm.md | スライスの証明、期待される旧値のリース、スタンドアロンのリスタック手順 | 親ブランチまたはベースが移動した後にスタックを同期する場合 |
references/merge-discipline.md | 親優先のマージ規則、マージの証明、保持、および委任された実行が適用するエンドポイント | 選択対象と結果の意味を判断する場合 |
references/metadata-format.md | 任意のメタデータスキーマと検証規則 | .stack-prs.yaml が存在する場合、または推論が曖昧な場合 |
references/provenance.md | コミットトレーラーによるスタック識別情報、スタンプ付与、検証、マージ方式との連動 | 作成、分割、同期、またはマージ方式の選択を行う場合 |
references/github-native.md | ネイティブスタックの検出、リンクの適格性、ネイティブ同期、ネイティブプレフィックスのポリシー | GitHub ネイティブスタックが要求または検出された場合 |
使用する場面
| このスキルを使用する | 別のパッケージを使用する |
|---|---|
| 依存関係のある複数のブランチで、親ブランチをベースとした PR が必要な場合 | 独立した PR が 1 件の場合は ship-workflow |
| 機能ブランチを、レビュー可能な依存関係のあるブランチに分割する必要がある場合 | コードが存在する前にタスクを計画する場合は task-decomposer |
| 既存のスタックで、リベース、正しい親ブランチへのリターゲット、検証、またはマージ範囲の判断が必要な場合 | 1 件の PR の差分をレビューする場合は pr-review |
| 既存のスタックをマージするか、マージ後にクリーンアップする必要がある場合(§5 で委任) | 無関係なブランチのクリーンアップには一般的な Git コマンド |
コアのルール
- どのワークフローを実行する場合も、最初に
git rev-parse --show-toplevelを実行してください。 - リベース、プッシュ、PR の作成、PR のターゲット変更を行う前に、
git status --porcelainを実行してください。 - ユーザーが操作を調査のみに明示的に限定していない限り、作業ツリーに変更がある場合は停止してください。
- 推測した祖先関係より、既存 PR の
baseRefNameを優先してください。 <base>はorigin/HEADではなく、ルート PR のbaseRefName(gh pr view <root-pr> --json baseRefName --jq .baseRefName)から解決してください。スタックの幹ブランチは、リポジトリのデフォルトブランチとは限りません。- 親ブランチの推定が曖昧な場合は、明示的なブランチ順序または
.stack-prs.yamlを使用してください。 git push --forceを単独で使ったり、--force-with-leaseを単独で使ったりしないでください。書き換えたヘッドは、--force-with-lease=refs/heads/<branch>:<expected-old-oid>を指定してプッシュしてください。- スタックブランチを手動で削除しないでください。証拠に基づくクリーンアップは委任された実行(§5)で行います。
- このスキルが作成または分割するすべてのコミットに、
Stack-IdとStack-Positionのトレーラーを付与してください。ID は.stack-prs.yamlからコピーするか、存在しない場合は一度だけ生成してください。 - このスキルが作成したスタックのランディングを委任する前に、トレーラーが存在し、一貫していることを確認してください。
- ランディングを判断する前に、Inspect 中にすべてのオープンなスタック PR についてネイティブスタックへの所属を確認してください(
references/stack-model.md§ Native Stack Detection)。 - ノードのマージ方法として
squashを選ぶ前に、プロバイダーの squash メッセージポリシー(squash_merge_commit_message)を確認してください(references/provenance.md§ Merge Mode Coupling)。
GitHub ネイティブのスタックモード
GitHub ネイティブのスタックは、同一リポジトリ内でのみ使用できます。手動の Armory スタックを拡張するものであり、来歴トレーラー、.stack-prs.yaml、またはプロバイダーに依存しないワークフローに取って代わるものではありません。
ネイティブスタックへの所属は、プルリクエストに対するプロバイダー側の属性であり、このスキルが選択するモードではありません。github-native モードが要求されているかどうかにかかわらず、ランディングを判断する前に Inspect 中にすべてのスタック PR について所属を確認してください。GET repos/{owner}/{repo}/pulls/<number> から .stack を読み取り、GET repos/{owner}/{repo}/stacks/<stack-number> から順序を含む完全な所属情報を取得してください(references/github-native.md § Detecting an already-native stack)。
ネイティブ状態の作成(オプトイン)
手動のスタックを gh stack link でネイティブに変換することは、プロバイダーへの変更です。次の適格性チェックをすべて通過した後にのみ実行してください。
- GitHub CLI が利用可能で、
gh stack --helpが成功すること。利用できない場合はgithub/gh-stackをインストールしてください。 - リポジトリでネイティブスタックのサポートが有効になっていること。ネイティブコマンドの終了コードが 9 の場合、スタックは手動のままにしてください。
- 提案するすべてのヘッド/ベースブランチとプルリクエストが、同じ GitHub リポジトリに属していること。フォークとリポジトリをまたぐスタックは拒否してください。
- 既存 PR のベース、ローカルのブランチ順序、
Stack-Id/Stack-Positionの来歴が一致していること。一致しない場合は停止してください。 - ユーザーがネイティブモードを明示的に要求していること。
チェックに失敗した場合、スタックは手動のままにしてください。どちらの方向にも、自動で変換したり、スタックの途中で変換したりしないでください。
すでにネイティブなスタックに対する操作(必須)
Inspect によって PR がすでにネイティブだと判明した場合、ネイティブな方法での処理は選択肢ではありません。プロバイダーはスタックのメンバーに対する同期マージを拒否し、委任された実行では、選択された最上位のメンバーに対するガード付きの非同期リクエストを 1 回だけ使って、ネイティブなメンバーをマージします。上記の条件 1〜4 は引き続き安全確認として適用されます。条件 5 の同意は適用されません。プロバイダーがすでにスタックを登録しているためです。
gh stack link、gh stack sync、gh stack rebase、gh stack pushを使ってリンクと同期を行います(references/github-native.md)。非対話型の同期を行うたびに、gh stack viewを再実行してください。- マージするには、ユーザーが承認した正確な最上位 PR を指定し、その下にあるスタック内のすべてのオープンメンバーをスタック判断パケットで選択する必要があります。プロバイダーは、その PR までのオープンなプレフィックス全体をマージします。そのため、承認範囲外にある下位のオープンメンバーについては、暗黙に範囲を拡大するのではなく、ユーザーに確認してください。その上にあるオープンメンバーは、パケット内の未選択ノードです。
- トレーラー保持ポリシー(
references/github-native.md§ マージ方法の選択とトレーラーの保持)に従って、選択した各メンバーのマージ方法を選択します。マージキューを使う場合はキューのルールによって方法が選ばれるため、パケットの方法は送信されません。 - マージ処理を委任します(§5)。メンバーごとのリクエスト、通常のマージ、ネイティブなメンバーに対する手動のフォールバックはありません。また、ガード付きリクエストが対象とするのは、選択された最上位のヘッドのみです(
references/github-native.md§ マージポリシー)。 squash_merge_commit_title: COMMIT_OR_PR_TITLE(GitHub のデフォルト)の場合、単一のコミットを含む PR をスカッシュすると、PR のタイトルではなく、そのコミットの見出しがベースブランチ上の件名になります。これはマージ後に、プッシュ済みの履歴を書き換えずに修正することはできません。表示される件名が重要な場合は、委任する前にマージされていないコミットの見出しを PR のタイトルに合わせてください(git commit --amend -m "<pr-title>"を実行してから、git push --force-with-lease=refs/heads/<branch>:<old-head> origin <branch>を実行します)。そして、新しいヘッドをパケットに記録してください。
ワークフロー
1. 調査
何も変更せずにスタックのモデルを作成します。
git rev-parse --show-toplevel
git status --porcelain
git branch --show-current
git for-each-ref --format='%(refname:short)' refs/heads
git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'
gh pr list --state open --json number,title,headRefName,headRefOid,baseRefName,state,url
スタックブランチごとに 1 行を含む表を作成します。この表を記録する前に、すべての PR に対してネイティブスタックの確認を実行してください(references/stack-model.md § ネイティブスタックの検出)。
| 順序 | ブランチ | 親 | PR | 状態 | チェック | ネイティブ |
|---|---|---|---|---|---|---|
| 1 | feat/parser-core | main | #101 | オープン | 保留中 | いいえ |
| 2 | feat/parser-cache | feat/parser-core | #102 | オープン | 保留中 | いいえ |
プロバイダーアダプターが利用できない場合、要求されたスタックに一致するローカルブランチがない場合、または PR のベース、明示的な順序、メタデータから親の順序を推定できない場合は、停止します。
2. 公開
不足している PR を作成し、誤ったベースを付け替えます。
git status --porcelain
git push -u origin <branch>
gh pr create \
--base <parent-branch> \
--head <branch> \
--title "<title>" \
--body-file <generated-body-file>
gh pr edit <number> --base <parent-branch>
生成する PR の本文には、スタックの順序と検証状態を含める必要があります。
## Stack
Stack-Id: `auth-refactor-a1b2c3`
Base: `main`
Position: 1/3
1. `feat/parser-core` -> this PR
2. `feat/parser-cache` -> #102
3. `feat/parser-cli` -> #103
Depends on: (none - root)
Upstack: #102
## Validation
- Pending: commands not run yet
ルート以外の PR では、Depends on: に親 PR の番号を記載します。子が判明している場合は、直下の子 PR の番号を Upstack: に記載します。
ブランチに親を超えるコミットがない場合、既存の PR がクローズされていてマージされていない場合、またはプロバイダーがベースの付け替えを拒否した場合は、停止します。
3. 同期
<base> または親ブランチが更新された後、何かがマージされる前に、references/sync-algorithm.md にある単独の手順を使ってオープンなブランチを再スタックします。各子ブランチの古いヘッド C とフォークポイント F を記録し、切り離されたスクラッチ作業ツリーで F..C を新しい先端 L にリプレイします。結果を検証し、明示的に期待する旧ヘッドを指定したリースを使って公開します。
git status --porcelain
git fetch origin
git merge-tree --write-tree --merge-base=<F> <L> <C>
git push --force-with-lease=refs/heads/<branch>:<C> origin <N>:refs/heads/<branch>
ベースの直上にある最初のブランチから開始し、末端に向かって続けます。競合、リモートリースの失敗、または親 PR がマージされずにクローズされた場合は停止します。親がマージされた後に子ブランチを昇格させてベースを付け替える処理は、委任されたライフサイクル(§5)の一部であり、この手順には含まれません。
4. 検証
レビュー可能なスライスとしてスタックを検証します。実施可能な場合は、すべてのブランチでコストの低いチェックを実行してください。ブランチごとの検証が現実的でない場合は、末端でコストの高い完全なチェックを実行してください。各 PR の本文に、実際に実行した内容を正確に記録してください。
対象リポジトリで検出されたローカルゲートとプロバイダーのチェックを使用してください。別のリポジトリのスタックを操作している場合は、armory パッケージ評価コマンドを実行しないでください。
この armory パッケージの実装に限り、次を使用してください。
uv run python scripts/validate_evals.py
uv run python scripts/generate_manifest.py
uv run python scripts/evaluate_package.py --path skills/stacked-prs
5. マージとクリーンアップ(委任)
マージ、マージ後のプロモーションとターゲット変更、マージ後の受け入れ、および既存の手動またはネイティブなスタック実行のクリーンアップは、ship-workflow スキルの明示的なスタックグラフ契約に従って、一度だけ実行します。
-
ネイティブメンバーシップも含めて§1の検査を行い、来歴を確認します(
references/provenance.md§ Trailers の検証)。 -
スタック決定パケットを1つ作成します(
references/stack-model.md§ 明示的なスタック決定パケット)。ユーザーが承認した PR だけにselect: trueを指定し、選択したノードのすべての祖先と、その他すべての関連メンバーは、コンテキストとして未選択にします。各選択ノードには、引用元とマージ方法を含め、リビジョンを含まないdecisionを指定します。また、検査した対象を明記したprovenance: {"disposition": "preserve", ...}を指定します。これをリポジトリ外の非公開一時ファイルに書き込みます。 -
登録済みの
ship-workflowスキルを読み込みます。クライアントにそのスキルがない場合は、ship-workflowを明記してBLOCKEDで停止してください。手動のマージ、ターゲット変更、プロモーションのプッシュ、削除に切り替えてはいけません。 -
対象リポジトリのチェックアウトから、読み込んだスキルの絶対ベースディレクトリにある実行ファイルを、まず読み取り専用で実行し、その後、承認済みのエンドポイントを指定して実行します。
uv run --no-project --python 3.12 python "<ship-workflow-dir>/scripts/ship.py" --stack-plan "/tmp/stack.X/stack.json" --dry-run uv run --no-project --python 3.12 python "<ship-workflow-dir>/scripts/ship.py" --stack-plan "/tmp/stack.X/stack.json" --gates "/tmp/stack.X/gates.json" --until merged「スタックをマージする」は
--until merged、「マージしてクリーンアップする」は既定のcleanです。「クリーンアップする」のみを指定すると、プロバイダーがすでにマージ済みと報告しているノードだけが選択されるため、新たなマージは行わず、実行内容を検証してクリーンアップします。 -
コントローラーが保存済みの実行を返した場合に限り、
--resume RUN_IDで再開します。解決済みの決定についてのみ、同じトポロジーの更新済みパケットを追加します。ジャーナル記録前の受付または検査で停止した場合、run_id: nullまたは ID なしとなることがあります。その場合は、範囲が限定された入力を解決し、元の範囲を明示した呼び出しをそのまま再実行してください。ID を捏造したり、引数なしの--resumeで無関係な実行を選択したりしてはいけません。 -
実行のステータス、確認済みのマージ、保持された依存ノード、クリーンアップの処置、次のアクションを伝えます。
NEEDS_JUDGMENTは、ここで判断すべきトポロジー、来歴、範囲、またはゲートに関する決定を示します。BLOCKEDは、不足している前提条件を示します。
この実行では、親を先に処理する順序、スライスを維持したプロモーション、期待される旧値のリース、ノードごとの新しいゲート、ベースブランチがオープンな状態での保持、ランディング証明(references/merge-discipline.md)が適用されます。また、ネイティブ選択は、保護された1回のリクエストでマージされます(references/github-native.md § ランディングポリシー)。
通常の委任によるリリースでは、リポジトリで現在使用されているプロバイダーチェック、レビュー、保護、マージポリシーを使用します。選択したノードでは、assurance: "high" を明示的に要求できます。通常のノードでは、チェックアウトログの固定や CI ジョブ名の変更は必要ありません。ローカル検証を要求する場合は --gates を指定します。リプレイされるすべてのコミットには per_commit: true を指定し、各ゲートにはそれぞれのタイムアウトを設定してください。検証はプッシュより前に行われ、既存のフックも維持されます。入力が変更されていない状態で拒否が確定した場合は、検証済みのリプレイオブジェクトをそのまま再利用します。変更が不確かな場合は、再試行の前に整合性を確認します。保存された保証設定と検証結果は再開後も維持されます。互換性のない従来の権限設定がある場合は、明示的な判断を求めて停止します。
1 つのブランチをスタックに分割する
1 つのソースブランチに、レビュー可能な依存関係のある PR に分割する必要がある機能が含まれている場合は、分割モードを使用します。ユーザーから、または曖昧さのないコミット名から、ソースブランチ、ベースブランチ、ターゲットブランチの順序を確認してください。
まず調査します。
git status --porcelain
git fetch origin --prune
git merge-base <base> <source-branch>
git log --oneline --reverse <base>..<source-branch>
git diff --stat <base>...<source-branch>
git diff --name-status <base>...<source-branch>
最も安全な分割モードを選択します。
| モード | 使用する状況 | 動作 |
|---|---|---|
| コミット範囲による分割 | 連続するコミットを明確に区分できる場合 | 依存ブランチを作成し、範囲をチェリーピックします |
| コミットリストによる分割 | 連続しないコミットを明確に区分できる場合 | スタック順に、指定したコミットリストをチェリーピックします |
| パッチを基準にした分割 | ファイルまたはハンクの境界は明確だが、コミットが混在している場合 | 変更を加える前に、ユーザーが承認した分割マップを求めます |
| 自動分割を拒否 | 必要な境界をまたいで変更が複雑に絡み合っている場合 | 分割が安全でない理由を報告します |
ブランチを作成したら、公開する前にリーフをソースブランチと比較します。
git commit --amend --no-edit \
--trailer "Stack-Id: <stack-id>" \
--trailer "Stack-Position: <n>/<total>"
git diff --stat <source-branch>...<leaf-branch>
git diff --exit-code <source-branch>...<leaf-branch>
公開する前に、references/provenance.md に従って各スライスのコミットにトレーラーを付与します。トレーラーの付与ではコミットメッセージを修正しますが、ツリーの内容は変わらないため、リーフとソースの比較は引き続き git diff によるツリー比較として行い、必ず合格する必要があります。
リーフがソースブランチと異なる場合は、公開しないでください。
エラー処理
| 条件 | 対応 |
|---|---|
| 変更を加える前にワークツリーに未コミットの変更がある | 停止し、変更されたパスを報告します |
| 親ブランチの順序が曖昧 | 明示的なブランチ順序または .stack-prs.yaml を求めます |
| マージされていない既存のクローズ済み PR がある | 置き換えを作成する前に停止します |
| 親ブランチの削除後に、マージされていない子ブランチがクローズされた | references/merge-discipline.md § Recovery に従い、その後、残りのマージを委任します |
| リベースまたはチェリーピックで競合が発生した | 停止し、ブランチ名と競合ファイルを報告します。子ブランチの処理は続行しません |
| フェッチ後にリモートブランチが変更された | 停止します。再調査なしに再試行しないでください |
| 検証に失敗した | 失敗したコマンドを記録し、公開または委任を停止します |
| 分割後の最上位ブランチがソースと異なる | PR の作成前に停止し、残りの差分を報告します |
作成したスタック内のコミットに Stack-Id トレーラーがない | 停止します。委任する前に、未公開のコミットに references/provenance.md に従ってトレーラーを付与します |
トレーラーの Stack-Id が .stack-prs.yaml と異なる | 停止します。変更を加える前に、正式な ID を解決します |
| 開いている下位のネイティブメンバーが、許可されたプレフィックスの範囲外にある | スコープについて判断を求めます。選択範囲を広げてはいけません |
マージまたはクリーンアップが要求されたときに ship-workflow がインストールされていない | ship-workflow の名前を挙げて BLOCKED で停止します |
委任の結果が NEEDS_JUDGMENT または BLOCKED | 確認済みの事実をそのまま伝え、ここで指定されたトポロジー/来歴/スコープの入力を解決します。返された保存済み run のみ再開してください。該当する run がない場合は、解決済みの情報を添えて元のスコープ指定を繰り返します。--resume のみで再開してはいけません。 |
出力形式
次の内容を報告します。
- ブランチ、親、PR 番号または URL、状態、ネイティブメンバーシップを含むスタック順序。
- PR の作成、ベースの編集、リベース、プッシュなど、このスキルが実行した変更。
- 検証コマンドと、正確な合格/不合格の状態。
- マージまたはクリーンアップを委任した場合は、実際の状態、確認済みのマージ、維持された依存ブランチ、クリーンアップの処置、次のアクション。
run_idは返され、かつ null でない場合にのみ含めます。 - 次に安全に行えるアクション:検証の修正、競合の解決、明示的なブランチ順序/スコープの提示、または返された保存済み run の再開。レシートがない場合は、解決後に元のスコープ指定を繰り返します。