implement-spec-in-workflow について
仕様 Issue と配下のチケットを依存関係グラフにし、サブエージェントのワークフローで実装するスキルです。チケットごとに PR を作り、小さな単位でレビューするチーム向けです。実行前にランナーやスタック方式などを確認します。
主な機能
- 01依存関係に基づいて着手可能なチケットを割り当て
- 02チケットごとに個別 PR を作成
- 03並列または順次実行に対応
- 04ネイティブスタックと連鎖 PR に対応
- 05ワークフロースクリプトでサブエージェントを調整
- 06チケットの検証欄からコマンドを抽出
活用シーン
- 01仕様 Issue をレビュー可能なチケット PR に分割
- 02依存グラフ内の独立タスクを並行して実装
- 03指定ブランチの既存作業を避けながら仕様対応を継続
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ javrasya/ skills --skill implement-spec-in-workflowインストール後、Claude Code で/implement-spec-in-workflowを入力して読み込まれたか確認します。
同梱ファイルworkflow.template.js
詳細
チケットを配下に持つ仕様課題は、サブエージェントによる動的ワークフローでエンドツーエンドに実装される、PR のスタック — チケットごとに 1 つの PRになります。チケットは手順のリストではなく、ブロック関係を持つタスクグラフです。そのため、常に着手可能なチケットのフロンティアがあり、実行はフェーズを順番に進めるのではなく、そのフロンティアに基づいてスケジュールされます。オペレーターがチケット単位の小さな差分を 1 つずつレビューし、スタック全体を 1 回の操作でマージできるようにするのが、このスタックの目的です。
ここでは、あなたのコンテキストが貴重なリソースです。仕様本文、チケット本文、調査結果、差分、レビューでの指摘はすべてワークフロー内にとどまります。あなたが扱うのは、イシュー番号、補間された 14 個の値、そして返される要約だけです。手順 2 で指定された ## Validation セクション以外のチケット本文は読まず、ソースファイルは開かず、検索も実行しないでください。ワークフロー内のディスカバリーエージェントがそれらを行います。コンテキストが満杯の状態よりも、新しいウィンドウで行うほうが適切です。
このファイルと同じ場所にある workflow.template.js がスクリプトです。すべての手順が含まれているので、これをレンダリングして実行します。
agent() 呼び出しを含むスクリプトを実行するランナーが必要です。選択肢は、Workflow ランナー、ハーネスのワークフロー機能(Claude Code の Workflow ツールなど)、または セッションランナー(旧 Orca ランナー。ADR-0011、ADR-0017)です。セッションランナーには Node と crew のインストールが必要です。ランナーは crew パッケージ(npm install -g @javrasya/crew、または javrasya/crew のチェックアウトから npm install -g <crew checkout>)で、各エージェントをセッションホスト上のライブセッションとして実行します。セッションホストは、デフォルトの crew 自体か、Orca ターミナル内でランタイムが準備されたセッションも必要となる Orca です(crew ランナーのリファレンス)。このスクリプトは、4 つのフック agent()、parallel()、phase()、log() を使うプレーンな JavaScript です。レンダリングする値 RUNNER(手順 2)を除き、どちらのランナーでも同じです。ネイティブスタックの登録には、さらに gh-stack 拡張機能が必要です。以下の条件を満たす場合にのみ使用し、自動インストールはしません。
手順
-
スタックのゲート。 2 つのプローブは結果が異なります。ほかのことに手をつける前に実行してください。
gh extension list | grep -q gh-stack— 見つからない場合は停止します。 ユーザーにgh extension install github/gh-stackを実行するよう伝え、返答を待ちます。ユーザーが明示的に依頼した場合に限り、自分でインストールしてください。gh api "repos/<owner>/<repo>/stacks" --silent— 404 の場合は、このリポジトリでスタックが有効になっていません(組織のロールアウトポリシー、またはプレビューに対応していないホストが原因です。何もインストールしても解決しません)。そのことをユーザーに伝えてください。GitHub のネイティブなスタック PR では、すべての PR にスタックマップが表示され、スタック全体をワンクリックでアトミックにマージできます。有効化の方法は https://docs.github.com/en/pull-requests/tutorials/roll-out-stacked-prs に記載されています。または、--baseのみを使う通常のチェーンに切り替え、手動で下から順にマージすることもできます。判断はユーザーに委ね、黙ってフォールバックしてはいけません。HTTP 200([]の場合も含む)なら、ネイティブモードです。
ランナー。 スタックのゲートとあわせて、2 つのプローブから確認します。
- Crew の確認。
crew --helpが 0 で終了すれば、crew は PATH 上にあります。 - Orca の確認。
TERM_PROGRAMがOrcaであり、ORCA_TERMINAL_HANDLEが設定されていて、さらにorca status --jsonがok: true、result.runtime.state: "ready"、result.runtime.reachable: trueを含んで 0 で終了し、Crew の確認にも合格している必要があります。それ以外はすべて失敗です。変数が欠けている場合、終了コードが 0 以外の場合、標準出力が JSON ではない場合、ほかの状態の場合も失敗です。 - Crew の確認に失敗(そのため Orca の確認も失敗):このステップがない場合と同じく、実行は Workflow runner で続行され、何も尋ねません。Crew だけが見つからない場合(Orca の確認のほかの項目には合格している場合)は、セッションランナーには crew のインストールが必要であることと、その方法を一度だけ伝えます。
- Crew の確認に合格 → 尋ねて、回答を待ちます。 選択肢は次の順です。crew 上のセッションランナー — デフォルトなので最初に提示し、デフォルトであることを明示します。すべてのエージェントが、オペレーターが実行コンソールから参加し、見守り、入力し、回答するライブの crew セッションとなり、実行は crew デーモン上で続き、このセッションが終わっても存続します。Orca 上のセッションランナー — Orca の確認に合格した場合のみ提示します。すべてのエージェントがライブの Orca タブとなり、実行は専用の Orca ターミナル上で続きます。または、以前と同じくホストのワークフロープリミティブである Workflow runner です。オペレーターに選んでもらいます。デフォルトであることは crew を最初に提示するというだけです。回答がない場合も含め、デフォルトで選択するなどして、オペレーターに代わって決めてはいけません。
- どちらかのホストでセッションランナーを選択 → ワーカーの実行方法。 Claude ワーカーは、ランナーに渡された権限モード(
--permission-mode)で起動します。指定がなければ、Claude 独自のデフォルト権限モードで起動します。ワーカーは、Workflow サブエージェントと同様に、あなた自身のセッションの権限モードを引き継ぎます。オペレーターに確認せずに進められるモード(auto、acceptEdits、bypassPermissions、dontAsk)の場合は、そのモードを渡し、何も尋ねません(autoはautoのままです)。あなたのモードがdefault(手動)の場合、または現在のモードを確認できない場合に限り、ワーカーはオペレーターがタブごとに回答する必要のある権限プロンプトで停止します。その場合は、ワーカーの実行方法をオペレーターに尋ねます。選択肢はauto、acceptEdits、bypassPermissions、あなた自身のモード、またはフラグなし(Claude 独自のデフォルトモードに従う)です。回答を待ち、その内容を起動時の--permission-modeとして使うか、指定なしにします。crew では起動時に必ずいずれかを指定します(crew startにはその項目があります)。そのため、crew の場合は「フラグなし」を提示せず、defaultを手動モードとして提示します。 - 既存の作業。 これを尋ね、回答を待ちます。この実行より前に、この仕様の作業がすでにブランチに含まれていますか?オペレーターにブランチ名を挙げてもらうか、「なし」と答えてもらいます(ADR-0023)。チェックアウト内のブランチを選択肢として提示します(
git for-each-ref --format='%(refname:short)' refs/heads refs/remotes/origin)。spec/<n>-integrationブランチの横には、以前の実行で行ったスタック全体のレビュー修正であり、チケットの作業ではないことを明記します。そのようなブランチを自分で探してはいけません。また、ワークフローのエージェントにも探させてはいけません。ある実行では、探索エージェントが中断された実行の統合ブランチを見つけ、そのブランチにすでにある作業を含むチケットを再実装し、公開の段階で停止しました。指定されたブランチはスタックのレイヤー 0となります。グラフエージェントはチケットごとに、そのブランチですでに完了しているかを判断します。完了しているチケットは再構築せず、レイヤー 0 の PR でクローズします。 - 実行順序。 ランナーが確定したらこれを尋ね、回答を待ちます。並列(デフォルト)では、いつもどおり、着手可能なすべてのチケットを一度にビルドします。逐次では、チケットを一度に 1 件ずつビルドします。次のチケットを先端から切り出す前に、各チケットをディスパッチ、実装、ゲート確認、公開まで完了するため、公開時に別のチケットへのリベースは発生しません(ADR-0020)。どちらのホストでも、セッションランナーの場合のみ提示します。Workflow runner では並列で実行し、何も尋ねません。オペレーターがそこで逐次を求めた場合は、何も表示する前に拒否し、その理由を伝えます。逐次実行では、エージェントを 1 つのフォルダーに順番に割り当てますが、Workflow runner は 2 つのエージェントを同じフォルダーに割り当てられません。並列を選ぶか、セッションランナーを選んでもらいます。
-
14 個の値を解決します。最初の 10 個には、
gh issue view <n> --json title -q .title、git remote -v、git fetch origin、git rev-parseを使用します。タイトルと参照のみを対象とし、本文は参照しません。2 つのレシピセットのみ例外として、それぞれのチケットの## Validationセクションだけを読み取ります。値 方法 __SPEC__引数から取得した仕様のイシュー番号 __REPO__仕様が属するリポジトリの owner/name__REPO_DIR__そのチェックアウトの絶対パス __BASE_REF__スタックのマージ先となるブランチです。ユーザーがブランチ名を指定していれば、それで確定します。指定がない場合は、 git branch --show-currentの結果をリポジトリのデフォルトブランチと照合します。一致する場合は、何も言わずにデフォルトブランチを使います。チェックアウトが別のブランチにある場合は、スタックのマージ先がデフォルトブランチか、現在のブランチかをユーザーに確認し、回答を待ちます。長期間使われているブランチにチェックアウトされている場合、作業の対象がそのブランチであることがよくあります。ベースを誤るとスタック内のすべての PR の切り出し先を誤るため、推測は決してしません。フィーチャーブランチにすでにある以前の作業は、この値ではありません。それは__START_REF__です。__START_REF__ステップ 1 の以前の作業です。オペレーターが指定したブランチ、または指定がない場合はベース参照そのものです。この場合、ワークフローは「レイヤー 0 なし」として扱います。推測も、エージェントによる探索結果の使用も決してしません(ADR-0023)。 __BASE_SHA__実行時に固定されるベース(ADR-0030)です。 git fetch originを実行してから、__START_REF__の完全な SHA を取得します。取得にはgit rev-parse --verify 'refs/remotes/origin/<start ref>^{commit}'を使います。指定された以前の作業がまだ origin にない場合は、そのローカルブランチであるrefs/heads/<start ref>を使います。実行で切り出す各チケットの起点には、この SHA を使います。実行中に参照先が移動しても、参照先から切り出すことはありません。参照先がコミットに解決されない場合、または fetch に失敗した場合は、実行を拒否します。オペレーターに該当する参照先を伝え、何もレンダリングせず、何も起動しません。__STACK_MODE__ステップ 1 の両方のプローブが成功した場合は native、ユーザーがフォールバックを選んだ場合はchainです。__RUN_ORDER__ステップ 1 で指定された実行順序の parallelまたはsequentialです。Workflow ランナーでは常にparallelです。sequentialがレンダリングされたスクリプトは実行を拒否します。__RUNNER__オペレーターがステップ 1 でセッションランナーを選んだ場合は、どちらのホストでも session、それ以外はworkflowです。以前の値であるorcaを使ってレンダリングされたスクリプトも、そのまま実行および再開できます。この値によって、ワークツリーの表記とスクリプトが回収を行うかどうかが決まります。セッションランナーでは、分離されたエージェントのワークツリーはセッションホストが作成したものであり、スクリプトの回収手順では何も削除しません。オペレーターは実行ビューまたは実行コンソールから回収します。Workflow ランナーでは、レーンと finalize がgit worktree removeで回収します。2 つのレンダリングで異なるのはこの値だけです。__NOTES_DIR__~/.claude/spec-notes/<repo-name>-<spec>の絶対パスです。永続的で、すべてのチェックアウトの外にあり、すべてのエージェントから読み取り可能です。__PER_CHANGE_COMMANDS__仕様のオープン状態の ready-for-agentサブイシューそれぞれの## Validationセクションにある### Run per changeの配下のコマンドです(gh api repos/<repo>/issues/<n>/sub_issues --paginate)。クローズ済みのものとready-for-humanのものは除外します。コマンドは各行の最初のバッククォートで囲まれた部分です。それがない行、または説明文である行(absent: …、not applicable: …、Recipe measured …、Needs: …、Deferred repo gate: …。Label:の後にある場合も含みます)はコマンドなしとして扱います。各コマンドは 1 回だけ含めます。空白だけが異なる 2 つのコマンドは同一とし、最初に記載された表記を維持します。JSON.stringifyで JSON 文字列配列に変換し、そのまま記述します。コマンドを持つチケットがない場合は[]です。Crew も同じ方法で抽出します(crew のarm.mjsにあるrecipeCommandSets)。__AT_REVIEW_COMMANDS__同じ方法で取得する、 ### Run at reviewの配下のコマンドです。__TICKET_RECIPES__上記のルールに従って、各チケット固有のコマンドを、イシュー番号をキーとする {"perChange": [...], "atReview": [...]}の JSON オブジェクトにします。各リストには、上記と同じ方法でコマンドを 1 回ずつ含め、そのまま記述します。該当するチケットがない場合は{}です。スクリプトはチケット固有のリストを実行するため、ロールが実行するすべてのコマンドはベースラインで測定済みのものになります。ディスパッチャー側のコピーは、ここに記載のないチケットに対してのみ使われます。Crew も同じ方法でレンダリングします(arm.mjsのticketRecipes)。__PR_GUIDE__ベンダー提供の prスキルの絶対パスです。このスキルの提供元リポジトリ内にあるskills/engineering/pr/SKILL.mdが対象です。まずこの SKILL.md の実際のパスを解決し(インストールされたスキルはシンボリックリンクであるため、realpathを使います)、そのフォルダーから../pr/SKILL.mdを解決します。パブリッシャーはこれを読み、各チケットの PR 本文を作成します。パスが存在しない場合は実行を拒否します。何もレンダリングせず、何も起動しません。Crew も同じ方法で解決します(arm.mjsのprGuidePath)。 -
スクリプトをレンダリングします:
workflow.template.jsを<notes-dir>/workflow.jsにコピーし、14 個のプレースホルダーを置き換えます。書き換えずに置き換えてください。公開レーンとフロンティアのスケジューリングは、動作を左右する重要な要素です。crew では、この手順をスキップしてください:crew startが、手順 4 で与えられる値と、同じ方法で解決する値(リポジトリ、そのパス、notes ディレクトリ、固定されたベース、2 つのレシピセット)からテンプレートを自動的にレンダリングします。 -
手順 1 の runner が落ち着いたら、レンダリング済みのパスを指定して起動します。実行中は、作業を代わりに行わないでください。
-
Workflow runner。 バックグラウンドで実行され、戻ると通知されるため、ポーリングしないでください。
-
crew の Session runner。 1 つのコマンドで準備と起動を行います。
crew startのフォームの各行をフラグとして指定します。手順 1 の permission mode、手順 2 の__BASE_REF__、__START_REF__、__STACK_MODE__、__RUN_ORDER__、ハーネスとしてclaude、モデルとして実行に使うモデルを指定し、stdin には/dev/nullを渡します:crew start <spec> --harness claude --model <your model id> --base "<base ref>" [--start-ref "<prior work branch>"] --stack-mode <native|chain> --run-order <parallel|sequential> --permission-mode <mode> </dev/null--start-refは、オペレーターが以前の作業を指定した場合にのみ指定します。省略すると、スタックはベースから始まります。端末がない場合、crew はフォームを表示しません。フラグだけから準備を行い、フラグが指定されていない行があると、質問する代わりにその行を示して拒否します。crew startを実行するたびに新しいランが準備されます。以前の起動が実行中かどうかにかかわらず、以前のランが再利用されることはありません。ランごとに独自のフォルダー<notes-dir>/runs/<spec>-<UTC time>-<random>が作成され、そこにworkflow.js、調査メモ、ベースラインのpre-existing-failures.json、ベースライン測定がブロッカーで停止した場合の停止記録baseline-blockers.jsonとbaseline-at-review-blockers.json(再開時は最初にこれらを読み込み、該当するブロッカーを確認します)、および状態ディレクトリorca-run/が格納されます。検証ファイルは存在せず、何も下書きされません。各チケットには独自の検証レシピが含まれます。いずれかのチケットに### Run per changeサブセクションを含む## Validationセクションがない場合、crew startはフォームの表示も準備も行わず、そのチケットを示して spec を拒否します。spec にpreflightを実行して検証レシピを書き込み、その後、再度起動してください。上記の__BASE_SHA__の説明に従ってランのベースを固定します。コミットに解決されない cut-from ref がある場合、準備を行わずに拒否します。スクリプトをそのフォルダーにレンダリングし、runner をimplement-spec #<spec>: <spec title>というタイトルの crew セッションとして起動します。その際、スクリプトのパス、各チケットが cut される ref と sha、セッション ID、およびランのコンソールに入るコマンドcrew view "<run folder>/orca-run"を出力します。終了コードが 0 以外の場合、何も準備されていません。メッセージをオペレーターに伝えて終了してください。その後は待機し、Orca の場合と同様に正確に報告してください。<notes-dir>/orca-runは、crew startが出力した状態ディレクトリとして読み替えます。そこに runner のタブまたはノードのタブが示されている場合、crew ではそれぞれ crew セッションです(halted.jsonのterminalは runner のセッション ID、各ノードのtabはその worker のセッションです)。ランのコンソールcrew view "<run folder>/orca-run"から入ります。runner の行またはノードの行で Enter を押すと、そのセッションに入れます。(crew consoleはデーモンの生セッションを一覧表示するデバッグ用コマンドです。オペレーターに案内しないでください。) -
Orca の Session runner。 notes ディレクトリはランの終了シグナルが残ったままになるため、まず前回のランの終了シグナルを消去します。各
--resumeと同じ spec の再準備では前回のランのファイルを使って起動され、新しい runner がファイルを置き換えるのは node の読み込み後、待機を開始してからかなり後になるためです。rm -f "<notes-dir>/orca-run/summary.json" "<notes-dir>/orca-run/runner.pid" "<notes-dir>/orca-run/halted.json"次に、ログを確認でき、このセッション終了後もランが継続するよう、spec をタイトルにした独自の Orca ターミナルとして runner を起動します:
orca terminal create --title "implement-spec #<spec>: <spec title>" --command "crew run --host orca \"<notes-dir>/workflow.js\" [--permission-mode <mode>]" --json; exitは追加しないでください。スクリプトの終了後も、オペレーターがビューを終了するまで、ランナーは実行ビューを表示したタブにとどまり、そのタブには出力内容が保持されます。result.terminal.handleは保持してください。result.terminal.surfaceがvisibleでない場合は、実行内容を表示するタブがないことをオペレーターに伝えてください。次に、
<notes-dir>/orca-run/summary.jsonが現れるか、ランナーが停止するまで待ちます。ランナーはターミナルを終了しなくなったため、ターミナルの終了を待ってはいけません。また、タブが開いているかどうかを待機条件にしてはいけません。タブはランナーより長く存続します。ランナーは起動時にプロセス ID を状態ディレクトリのrunner.pidに書き込み、スクリプトの終了時に、オペレーターのビュー待ちに入る前にsummary.jsonを書き込みます。そのため、summary.jsonの出現は実行の終了を意味し、存在しなくなったプロセスを示すrunner.pidはランナーの停止を意味します。停止中の実行(チケットが失敗した場合、またはオペレーターにしか判断できない決定が必要な場合。ADR-0016)は終了しておらず、summary.jsonは書き込まれません。代わりにランナーはhalted.jsonを書き込みます。形式は{ at, runId, terminal, nodes: [{ node, title, reason, questions?, tab }] }で、保留中のノードが変わるたびにatが新しくなり、停止状態を抜けると削除されます。1 時間を上限として、ハーネスにバックグラウンドコマンド機能がある場合はバックグラウンドコマンドとして、1 つのコマンドで待機してください。<seen>は最後に報告した停止状態のatとし、初回は空にします。timeout 3600 sh -c 'd="<notes-dir>/orca-run"; seen="<seen>"; until [ -f "$d/summary.json" ]; do a=; [ -f "$d/halted.json" ] && a=$(node -p "require(process.argv[1]).at" "$d/halted.json" 2>/dev/null); [ -n "$a" ] && [ "$a" != "$seen" ] && break; [ -f "$d/runner.pid" ] && ! node -e "process.kill(+process.argv[1],0)" "$(cat "$d/runner.pid")" 2>/dev/null && break; sleep 15; done'nodeプローブは Windows のプロセス ID を確認できるため、生存確認に使います。Git Bash のkill -0では確認できない場合があります。待機が戻ったら、次の順で確認します。summary.jsonが存在する場合:実行は終了しています(確認の間にランナーが終了した可能性があるため、最初に確認します)。それ以外で、halted.jsonが存在し、atが<seen>以外の場合:実行が停止したか、保留中のノードが変わっています。どのノードが、なぜオペレーターの対応を必要としているかを伝えます。各ノードのtitleとreason、あればquestions、およびtabを伝え、ランナーのタブ(terminal)でrを押すと保留中のすべてのノードが再開され、ノードの行を選択した状態で押すとそのノードだけが再開されることも伝えます。その後、<seen>をそのatに設定して、同じ待機を再度実行します。crew では、実行コンソールから入ったランナーのセッションでrを押します。停止しても待機は終了しません。終了するのはsummary.jsonが作成されたとき、またはランナーが停止したときだけです。それ以外で、runner.pidが存在し、同じプローブが失敗する場合:Orca ではランナーが停止しています。crew では、まず復旧させ(下記参照)、ランナーが戻らなかった場合に限り停止したと報告します。それ以外で、1 時間が経過した場合:runner.pidがまだないなら、ランナーは起動していません。その旨を伝え、オペレーターにランナーのタブを確認するよう案内します。プローブがまだ成功する場合は、同じ待機を再度実行します。実行中にターミナルのログを読んではいけません。Orca の停止(アップデート時などの Orca の再起動)でエージェントが失敗することはありません。ランナーは復旧を待ち、10 分を超えると実行を一時停止し、Orca の復旧後は自動で続行するため、ランナーが稼働中でもsummary.jsonがない場合は一時停止している可能性があります。そのまま待機してください。crew の停止は異なります。crew はランナーを含め、セッションを停止します。そのため crew では、summary.jsonがない状態でランナーが停止していても、まだ復旧可能な実行のもとでデーモンが停止した(クラッシュ、強制終了、再起動など)可能性があります。デーモンがこのような実行を復旧するのは起動時だけなので、起動します。crew daemon startを実行し、その後最大 2 分間、runner.pidが再び稼働中のプロセスを示すのを待ちます(復旧したランナーは新しいファイルを書き込みます。同じnodeプローブを使用します)。timeout 120 sh -c 'd="<notes-dir>/orca-run"; until [ -f "$d/summary.json" ] || { [ -f "$d/runner.pid" ] && node -e "process.kill(+process.argv[1],0)" "$(cat "$d/runner.pid")" 2>/dev/null; }; do sleep 5; done'。ランナーが稼働中、またはsummary.jsonが存在する場合は、実行が復旧しています。crew が復旧させたことをオペレーターに伝え、同じ待機を再度実行します。どちらも確認できない場合に限り、ランナーが停止したと判断します。下記のとおり報告してください。summary.jsonが存在したら読み込みます。ok: trueの場合は、ワークフローの戻り値がresultに含まれています。ok: falseの場合は、ランナーのerrorとworktrees_keptが含まれています。worktrees_keptは、エージェントが停止したか起動しなかったために保持された worktree です。ランナーは何も回収せず、何も要求しません。すべてのエージェントのセッションと worktree は、オペレーターが回収するまでそのまま残ります。Orca では実行ビューから(ランナーのタブでCtrl+Rを押すか、スタンドアロンビューを使用)、crew では実行コンソールから(crew view)回収します。詳細はcrew ランナーのリファレンスを参照してください。その旨を伝えてください。回収されるのを待ってはいけません。summary.jsonがない状態でランナーが停止している場合は、ファイルを書き込む前に停止しています。強制終了、メモリ不足、またはクラッシュが原因です。その旨を伝え、ランナーが出力したすべての行を記録した<notes-dir>/orca-run/runner.logと、その隣にあるjournal.jsonlをオペレーターに案内してください。journal.jsonlには、各エージェントの起動、結果、または失敗とその理由が記録されています。
-
-
返された内容を報告し、実行に使用されたランナーを明記してください。Workflow ランナーの場合はその旨を、セッションランナーの場合はホスト(crew または Orca)とセッションまたはターミナルのタイトルも報告してください。スタックは下から上へ各 PR の URL と状態を添えて記載し、実行が終了したモードのマージ手順も報告してください。実行が停止した場合は、
reasonと未公開チケットそれぞれの状態および詳細を報告し、needs decisionのチケットに残された質問があればそれも記載してください。人間に委ねられたチケットとその理由、unblock セッションが解消したブロッカー(Unblock で停止した場合は、解消が必要なまま残っているブロッカー)、実行が保持したものとその理由も報告してください。セッションランナーでは、各エージェントのセッション(Orca ではタブ)とワークツリーを記載します。これらは、オペレーターが実行ビューまたは実行コンソールから回収するまで残ります。また、エージェントが停止した、または起動しなかったため保持された、結果のworktrees_keptも記載してください。Workflow ランナーでは、結果のworktrees_keptと、その保持理由を報告してください。さらに、ノートディレクトリも報告してください。ワークフローの戻り値はすでに要約になっているため、PR から再度導き出さず、そのまま伝えてください。
セッションランナーの実行が終了、一時停止、または停止した後、ランナーが動作していない状態で後から戻るには、crew ホストの実行は crew ls に一覧表示され、実行コンソールで crew view を使って開けます。Orca ホストの実行は orca-runs で開けます。これは単独で動作する実行ビューで、マシン上のすべての Orca ホストの実行を一覧表示し、実行のエージェントを回収するとともに、ランナーが停止した実行を再開します。
失敗後に反復するには、<notes-dir>/workflow.js(crew では、crew start が出力したスクリプトである <run folder>/workflow.js)を編集し、ランナーの再開ハンドルを使って再起動してください。Workflow ランナーでは resumeFromRunId、セッションランナーでは --resume を使います(Orca では、手順 4 と同じ起動コマンドに --resume を追加します。crew では crew start の代わりに crew run "<run folder>/workflow.js" --resume [--permission-mode <mode>] を使います。crew start はスクリプトを新たに生成するためです。まず summary.json、halted.json、runner.pid を削除してください。失敗した実行のこれらのファイルが残っているためです。同様に待機し、新しい summary.json が生成されるのを待ちます)。これにより、<notes-dir>/orca-run/journal.jsonl(crew では <run folder>/orca-run/journal.jsonl)のジャーナルから再生されます。変更されていない agent() 呼び出しのプレフィックスはキャッシュから返され、編集した呼び出し以降だけが再実行されます。
ワークフローの動作
- Graph — 1つのエージェントが仕様とすべてのチケットを読み、タスクグラフを返します。ブロッキングエッジ、人間の対応が必要なチケット、そしてブロッカー、つまり人が一度だけ補う、このマシンに不足しているもの(認証情報、署名 ID やプロファイル、セットアップが必要なデバイスやサービス)を、それぞれの根拠と確認コマンドとともに示します。ブロッカーは人間の対応が必要なチケットではありません。それを必要とするチケットも自動化されたままです(ADR-0021)。以前の作業を含むブランチを探すことはありません。それはオペレーターが指定する
__START_REF__です(ADR-0023)。ブランチが指定された場合、グラフエージェントがそれについて行うのは、チケットごとに根拠を示し、そのブランチがすでにチケットを完了させているかどうかを伝えることだけです。そのようなチケットは包含済みとなり、エージェントは実行されず、何もブロックせず、レイヤー 0 の PR がクローズします。ブロッキングエッジは、まずGitHub のネイティブ依存関係(blocked_by、チケットごとに 1 回の API 呼び出し)から取得し、API から何も返されない場合に限り、「Blocked by」セクションの文章から取得します。依存関係がネイティブに記録されているチケットでは、文章を読む必要はありません。 - Explore — 並列の調査エージェントが、リポジトリ外の notes ディレクトリに Markdown を書き込みます。ノートには上限があり、引用ではなく参照を示します。ノートは後続エージェントの検索を省くためのものであり、読む作業を省くものではありません。また、後続エージェントが読むのは、自分のチケットに関係する内容のノートだけで、すべてのノートではありません。調査エージェントもブロッカーを返します。グラフが調査を求めた内容に加えて、スクリプトは常にベースラインエージェント(ADR-0030)を起動します。これは Explore 配下の独立したノードであり、独自のワークツリーでセットアップフックを実行した後、
__BASE_SHA__に切り替え、すべての__PER_CHANGE_COMMANDS__コマンドを 1 回ずつ実行し、各コマンドの終了コードと失敗内容(テストなら ID、その他のチェックならツール、ルール、ファイル、5〜6 行のスニペットとメッセージ。行番号だけでは不可)を返します。また、ツールでテストの選択を解除できる場合は、マスク済みコマンドも返します。submitはスキーマに一致しない記録を拒否します。スクリプトは受け付けられた記録を、notes ディレクトリ(crew では run フォルダー)のpre-existing-failures.jsonに書き込みます。コミットされることはありません。記録にマスク済みコマンドが含まれる場合、per-change のレシピを実行するすべてのロール(実装担当、修正担当、ゲートレビュアー、公開担当)には、元のコマンドの代わりにマスク済みコマンドが伝えられます。ゲートのクロスチェックで追加されるコマンドも対象です。どちらの形式でチェックしても準備完了と見なされます。これらのロールには記録も示され、マスクされた失敗を修正する役割のロールは、マスクされていないコマンドを実行し、その旨を伝えます。これが成功するまでは何もディスパッチされず、再開時も再測定せず、その記録を引き継ぎます。Unblock の完了後、2 つ目のベースラインノードbaseline:at-reviewが、per-change のセットに含まれないすべての__AT_REVIEW_COMMANDS__コマンドを同じ方法でバックグラウンド測定します。ディスパッチと各チケットはこれを待たずに進み、成功を待つのはスタック全体のレビューだけです。このノードの記録はpre-existing-failures.json内の per-change の記録に加わり、マスク済みコマンドも同様に加わります。再開時は、成功済みの 2 つのノードのうち該当するものを引き継ぎます。この段階のブロッカーには、per-change の測定時と同じように対処します。ただし、すべてのチケットの公開後、レビューの前に実行を停止し、Workflow runner 上の記録をbaseline-at-review-blockers.jsonに残します。実行して失敗したコマンドは既存の失敗です。まったく実行できないコマンド(ツールが起動しない、または終了コードがチェックの失敗ではなく、出力から何も読み取れない場合)は、マスクすると隠れてしまう環境の破損なので、ベースラインエージェントは、発見時に使う形式(何が不足しているか、根拠、確認コマンド)でブロッカーとして報告します。crew ホストではneeds_youを呼び出します。その行には「対応が必要」と表示され、その間はリマインドされません。オペレーターはそのセッションに入り、エージェントとともに環境を修正します。コマンドが実行できるようになると、エージェントは測定を続けて送信します。それまではディスパッチが保留され、以後は他のベースライン結果と同様に実行が続きます。このツールがなければ、ノードのdecisions_neededとしてブロッカーを返します。ノードは保留され、再開時にはそのセッションを引き継ぎます。Workflow runner にはセッションがないため、そこで実行を停止し、各ブロッカーの内容、根拠、確認コマンドを明記して、notes ディレクトリのbaseline-blockers.jsonに停止を記録します。次回のベースラインプロンプトにはその停止が示されるため、再開とは runner がまだ行っていない呼び出しを行うことであり、ベースラインノードが再度実行され、各ブロッカーを先に確認します。Unblock — 発見されたブロッカーがある場合、それらが解消されるまで実行は何もビルドしません。セッション runner では、開始時からneeds youとなる立ち会い付きセッションを 1 つ開き、オペレーターは run コンソールから参加します。エージェントは各ブロッカーの確認コマンドを使って案内し、解消を確認しますが、自らインストール、署名、設定を行うことはありません。また、待機中にリマインドされたり、停滞や失敗として数えられたりすることもありません。すべての確認が通ると終了します。オペレーターが解消を諦めたブロッカーがある場合は実行を停止し、再開時は同じセッションを続けます。Workflow runner にはセッションがないため、ブロッカーを一覧表示してそこで実行を停止します。 - Setup — オペレーターが開始時に以前の作業を指定した場合に限り、そのブランチをスタックの最下部であるレイヤー 0とし、独自のドラフト PR を作成します。PR の本文には、この実行でゲートを通していない既存の作業を含むことを明記し、グラフエージェントがそのブランチですでに完了していると判断した各チケットについて
Closes #<n>を記載します。ローカル ref はまずorigin からミラーされます。これは、実行で作成するのではなく引き継いだスタック内で唯一のブランチであり、その後は各gh stack linkがそのローカル名で push します(スタックを参照)。 - Dispatch — チケットごとのディスパッチャーは、プランナーではなくルーターです。チケットを1 回だけ読み、コード構造(ファイル一覧、grep のヒット。ツール呼び出しは約 5 回)をざっと確認し、2 つの問いに答えます。スライスの数と、各スライスが担当する受け入れ基準です。仕様、調査ノートの本文、実装は読みません。また、抜けや逸脱も探しません。チケットは人間が仕様から切り出したもので、記載どおり信頼できるためです。各スライスのブリーフは 3K 文字未満で、次の 4 つのセクションがあります。担当する基準(原文のまま)、想定されるファイル、読むべきノート、対象外。設計は一切含みません。実際に観測された例では、「自己完結型」のブリーフを書くよう指示されたディスパッチャーは、最初のツール呼び出しまで 40 分間考え、$17 を費やしてプランを作成しましたが、実装担当はコードを開いてそのプランを作り直しました。通常の判定はスライス 1 つです。ディスパッチャーはスライス分割に慎重です。分割が不足していても自己修正されます(処理しきれなかった部分が戻され、再分割されます)が、過剰に分割すると、修正手段がないまま、スライスごとに新しいエージェントのウォームアップが必要になるためです。また、後の修正エージェントが issue の代わりに使う
ticket_briefを出力し、チケットの## Validationセクションから検証レシピをそのままコピーします。### Run per changeはvalidationに、### Run at reviewはreview_validationに入れ、書き換えません(ADR-0029)。ディスパッチャーだけが、この実行で行うあらゆる作業への経路です。レビュー指摘を修正スライスに分割するための第 2 の入口も備えているため、実行中に見積もりを経ていないエージェントは存在しません(ADR-0004)。 - Implement — スライスはチケットブランチ上で順次実行され、それぞれが独自のワークツリーでコンテキストを新たにしたエージェントとして動作します(下記の、実行における 1 つのチェーンワークツリーで、順次実行)。チケットはブロッカーのブランチから、その先端として指定された状態で切り出します。スタック最下部では、固定された
__BASE_SHA__から切り出します。実行中に動く ref から切り出すことはありません。チケットは依存関係が許す限り早く開始します。実装担当は受け入れ基準の文言とブリーフに指定されたノートを確認するためにチケットを読みますが、仕様は読みません。コードを目の前にして、変更を自ら設計します。実装担当が守るのは受け入れ契約です。チケットの基準、それに関わる仕様の決定、そして仕様自体が作成または修正する ADR が含まれます。既存の ADR は指針であり、基準ではありません(ADR-0008)。コードを実行した結果、チケットに記載がない、矛盾がある、または仕様と矛盾することが分かった場合、修正がチケットで未規定の範囲内に収まるなら、実装担当が判断し、その判断をdecidedに返します。公開担当はこれを PR に記載します。修正によって、チケット、仕様、または ADR が明示的に維持するよう求めているものが壊れる場合、その基準の作業を停止し、コードを残さず、制約と根拠を示してdecisions_neededに返します。unmetには入れません。並列実行では、ワークツリーはチケットごとではなくエージェントごとに用意され、各エージェントは終了時に自身のパスを示し、後でレーンが回収できるようにします(CONTEXT.mdの Worktree reclaim を参照)。順次実行では、すべてのコードエージェントが実行の 1 つのチェーンワークツリー<runId>-chainで順番に作業し、直前のエージェントが残したビルドキャッシュを引き継ぎます(ADR-0020)。doctor は引き続き独自のワークツリーを取得します。チェーンエージェントの結果が返る前に、runner の残存物チェックがチェーンワークツリーのgit status --porcelainを、そのエージェントがそこにあると伝えられていた内容と比較します。それ以外のものがあれば、エージェントに 1 回だけ返し、独自のセッションでコミットまたは削除させます。それでも残ったものは、後続のすべてのチェーンエージェントに、コミットしてはならないファイルとして通知されます。各スライスは最終コミットでチケットのper-change コマンドを実行し、コマンドごとに{command, passed, exit_code}の結果と、検証したコミットの SHA をvalidated_shaとして返します。終了コードが 0 なら判定なしで成功です。0 以外の場合は、スライスが実行内のすべての失敗をベースライン記録と照合して既存の失敗だと判断した場合に限り成功となり、これを免除されたチェックと呼びます(ADR-0030)。反復作業中は、ビルドツールが対応する最小範囲で実行し、最後の編集後に1 回だけ実行します(ADR-0009)。各チェックは記載どおりフォアグラウンドで実行し、バックグラウンドで実行したり、ポーリングしたりしません。また、ビルドキャッシュをクリーンするエージェントはいません。コンテキストを使い切ったスライス(ツール呼び出し約 70 回)は、動作する内容をコミットして残りを返します。ディスパッチャーはそれを新しいエージェント用に再分割しますが、上限はMAX_DISPATCH_ROUNDSです。未完了の作業は常に残作業であり、抜けではありません。スライスが書かなかった必須テストは、ゲートや人間には進まず、ディスパッチに戻されます。人間が解決すべき矛盾だけがdecisions_neededで引き継がれ、チケットのスライス反復はそこで終了します。スライスが同時にそれをunmetに記録していても、その基準が再ディスパッチされることはありません。判断が必要なもの、または上限後も未達として残る作業は、ゲートも公開もされず、実行を停止します(ステップ 10 を参照)。 - Gate — 各チケットは PR になる前に、未公開のままの独自ブランチ上で、切り出し元のベースと照らしてレビューされます。レビュアーが最初に行うのは準備状況の確認です。ブランチは実装担当(または最後の修正担当)が指定した SHA でグリーン検証済みなので、レビュアーは
git rev-parse HEADを実行し、SHA が一致していて何も編集していなければ、その結果を引き継ぎ、異なる場合にだけ再実行します(ADR-0009)。ただし免除されたチェックは常に再実行し、レビュアー自身が判定します(ADR-0030)。ここで失敗した場合、指摘として修正担当に送るのではなく、残作業としてディスパッチに戻されます(MAX_DISPATCH_ROUNDSの 1 回分を消費します)。また、プロンプト内の per-change コマンドとチケットの### Run per changeを比較し、プロンプトにないコマンドがあればmissing_validationに返します。スクリプトはそれらをチケットのレシピに原文のまま、各コマンド 1 回ずつ追加し、同様にブランチをディスパッチへ戻します。次のラウンドではレシピ全体が実行されるため、行の抜けは実行全体ではなく、ラウンド 1 回のコストになります。これは指摘にはなりません。レシピを変更できる修正担当はいないので、指摘にすると毎ラウンド再び指摘されるためです。その後、レビューでブロッキング項目がなくなるまで(軽微な問題は合格)、レビューと修正を交互に行います。ラウンドごとに新しいレビュアーを起用し、上限はGATE_MAX_ROUNDSです。上限に達した場合は、未解決の内容を添えて公開し、ログと要約にその内容を記載します。後続のすべてのチケットを停止させることはありません。ブロッカーとなり得るものは限定的です。未達の基準、検証コマンドの失敗、または正確性のバグ(panic、クラッシュ、リリース時のサイレントなデータ損失)です。ADR への適合性、アーキテクチャ、スタイルはゲートではminorです。仕様の設計時に解決済みであり、チケットごとに再検証すると、観測された実行ではゲートのラウンドの大部分を費やしてしまいました(ADR-0008)。第 1 ラウンドでは diff 全体をレビューし、それ以降は修正の主張と修正担当が触れた行を確認します。そのため、ラウンド数は新たな問題の発見ではなく、修正に要した量を測ります。すべてのラウンドの修正部分はディスパッチャーを経由します。ディスパッチャーは issue ではなく、ticket_briefと対象ファイルの構造からブロッキング指摘を見積もり、指摘を個別に担当する新しい修正スライスにブリーフを作成します。各スライスはチケットの per-change コマンドを実行し、指摘ごとの判定を返します。スクリプトは判定と指摘を照合し、判定がないものはすべてログに記録し、次のレビュアーに明示的な確認を依頼します。終了したレビュアーをクリーンとして数えることはありません。 - Publish lane — 直列レーンはグラフをチェーンにします。チケットブランチを現在のスタック先端にリベースし、その先端をベースにドラフト PR を開くエージェントは、常に 1 つだけです。PR の本文には
Closes #<ticket>を記載します。先端が動いたかどうかは名前ではなく SHAで判定します。スタック最下部のチケットは固定された__BASE_SHA__から切り出されているため、公開担当はgit rev-parse origin/<start ref>を実行し、それが固定 SHA ではなくなっていたら、PR を開く前に、固定 SHA からチケットをそのブランチにリプレイします(ADR-0030)。順次実行では、チケットの作業中に実行の先端が動くことはないため、公開担当が別のチケットにリプレイすることはありません。また、スクリプトは先端が動いた場合の公開を拒否します(ADR-0020)。リベースできるのは最下部のチェックだけです。リベースすると未検証のツリーになるため、公開担当はその上でチケットの per-change コマンドを 1 回実行します。先端が動いていない場合は、SHA に基づいてゲートの結果を引き継ぎます。ただし免除されたチェックは再実行して自ら判定し、免除されたチェックが引き継がれることはありません(ADR-0030)。いずれの場合も、PR 本文には由来を示す行を 1 つ記載します。Validated green at <sha> by <role>。免除されたチェックがある場合は、その下にWaived: <command> exited <code>と記載します(なければ追加記載はありません)。これにより、人間とスタック全体のレビュアーは、どのツリーを誰が検証したかを確認できます。この時点までブランチは共有クローン内にだけ存在しているため、リベースによって書き換えるのはそこから出ていないコミットだけです。レーンの push は、origin のブランチを上書きするのではなく作成します。これを一度だけ公開と呼びます。ブランチが origin に到達するのは 1 回だけで、origin 上の ref が書き換えられることはなく、エージェントに強制 push を指示することもありません。公開担当はまず origin を確認します(git ls-remote --exit-code --heads)。そこに既存のticket/<n>がある場合(以前の実行で作成されたもの)は停止し、オペレーターの判断を仰します。早送りでそのブランチを更新することはありません。切り出し元の先端にコミットを追加していないブランチは公開するものがありません。公開担当はその旨を伝え、実行を停止するのではなく、チケットを包含済みとして記録します。先端はそのままで、依存チケットは続行し、finalize でそのチケットが未クローズとして記載されます。エージェントが 2 つ同時に公開すると同じ先端をベースにしてしまいます。これを防ぐためにレーンがあります。レーンはチケットのワークツリーも回収します。チケットがレーンに到達すると、それ以降ブランチに触れるものはないため、公開担当はチケットのエージェントが報告したすべてのワークツリーに加えて、前の公開担当自身のワークツリーも削除します。後者はワークツリー内からは削除できないためです。削除はリベースの前に行います。リベースすると、各スライスのワークツリーの HEAD がビルド元のブランチから切り離されるためです。回収のための唯一のガードは、HEAD がそのブランチ上にあることです。レーンはスタックの成長に合わせて登録も行います。PR を開いた後、スタック全体を最下部から最上部までgh stack linkで処理します。そのため、実行終了後だけでなく、第 2 層以降から実際のスタックマップが存在します(ADR-0006)。各 PR 本文の先頭にはLayer k of N plannedと記載され、finalize まで各レイヤーはドラフトのままです。これにより、実行がまだレイヤーを追加中であることを示します。 - Frontier — フェーズの区切りはありません。各チケットは、依存チケットの公開を待つメモ化されたプロミス 1 つです。ブロッカーの PR が存在した瞬間にチケットが開始し、実行全体にかかる時間はフェーズの合計ではなく最長のチェーンで決まります。これが並列実行です。順次実行(ADR-0020)では、フロンティアをチケットごとに進みます。着手可能なチケットのうち、仕様自体のマップにある順序(グラフの
map_position)が先のものを選び、次に issue 番号が最も小さいものを選びます。そのチケットの公開が完了してから次のチケットをディスパッチします。そのため、チケットの作業中に先端が動くことはなく、公開担当がリベースすることもありません。また Explore 以外では、同時に実行中のエージェントは常に 1 つだけです。 - Review — 1 つのエージェントがスタック全体(
base...tip)をレビューし、公開済みチケットのすべての### Run at reviewコマンドを重複排除した和集合を、スタック先端で 1 回実行します。これは各チケットのロールが実行しないフルスイートです。at-review ベースラインにマスク済みコマンドがある場合は、その形式で伝えられます。{command, passed, exit_code}のチェック結果を返し、他のロールと同様に、失敗を記録と照合して判定します。passed: falseのチェックはblocker指摘となり、免除されたチェックは指摘になりません。その指摘も他の作業と同じくディスパッチャーを経由し、修正はスタック上部に統合 PR 1 つとして反映されます。下のレイヤーには触れません。修正担当はチケットをまたいでコードを変更するため、その per-change レシピは、公開済みチケットすべての### Run per changeコマンドと at-review の和集合を重複排除したものです。どのチケットのレシピよりも狭くなることはなく、コマンドを指定するチケットがある限り空になることもありません。別の、労力を抑えたエージェントが PR を開き、コードは変更せず、本文に、最後の統合修正担当が返した免除済みチェックと、レビューが返したうち最後の修正担当がグリーンで実行しなかったものをすべてWaived: <command> exited <exit_code>として記載します(どちらの場合も、実行結果ではレビュー側のものをreview_waivedとして記録します)。公開はこのフェーズで唯一取り消せない操作であり、最後に実行した修正担当が行うべきものではありません。ゲートでこのレビューを代替することはできません。チケット単位のレビューでは構造上見えないのが、チケット間の相互作用です。1 つのヘルパーに対する 2 つの実装、互いに矛盾する抽象化、あるチケットが依存する契約を別のチケットが気付かれないまま変更するといった問題です。 - Finalize — ネイティブモードでは、1 回の
gh stack link呼び出しが、レーンがこれまで維持してきた登録を整合させます。レーン外で公開されるためこれまでリンクされていない統合 PR を追加し、レーン内で失敗した呼び出しがあれば修復します。その後、公開済みの PR をすべて ready にします。ドラフトはスタックのマージを妨げるため、この手順まではドラフトによって、実行がレイヤーを追加中であることをオペレーターに伝えます。finalize に進むのは、自動化されたチケットがすべて実装、ゲート通過、公開済みの場合だけです(ADR-0016)。失敗したチケット、未達のまま残ったチケット、判断が必要なチケットがある場合は実行を停止します。その後、新しいチケットは開始せず、実行中のチケットも、そのディスパッチでスライスが 1 つだけ返された場合を除いて、新たなエージェント呼び出しを行いません。その場合はそのスライス、ゲート、公開を完了します。停止した実行では、スタック全体のレビューも finalize も行われず、GitHub に残るのはすでに公開済みの PR だけです。実行中の at-review ベースラインがある場合は、その完了を待ってから停止するため、終了後にセッションが残ることはなく、再開時にも再測定せずに結果を再利用します。{ halted: true, reason, tickets: [{ ticket, state, detail }], published, ... }を返し、未公開の各チケットはfailed、unmet、needs decision、stopped(途中で停止に達したもの)、またはnot startedのいずれかとなります。再開されるまで待機します。完全に完了した実行では、最上位の PR にCloses #<spec>を追加し、スタック全体をマージすればすべてが一度にクローズするようにします。統合 PR に記載する項目がない場合、finalize はスタック全体のレビューで免除されたチェックを、最上位の PR の本文にWaived: <command> exited <exit_code>として追加します。いずれの場合も GitHub 上に各項目を残します。完了判定はフェイルクローズです。保留中のチケット、未修正のゲート指摘、判定がない統合指摘、または未実施のスタック全体のレビューが 1 つでもあれば、仕様は未クローズのままです。公開済みであっても完了とはなりません。その場合 finalize は仕様のクローズをどこにも追加せず、最上位の PR と仕様の issue に、残っている内容をコメントします。ワークフローはマージしません。マージはオペレーターが最上位から 1 回だけ行います。 - Cleanup — finalize は、レーンが回収しなかったものだけを回収します。最後の公開担当自身のワークツリーと、終了した回収担当が保持していたものが対象です。ルールは同じです。残ったものは理由とともに
worktrees_keptとして返します。対象となるのは、HEAD がブランチから外れているワークツリー、ファイルロック、パスを報告する前に終了したエージェントのワークツリーです。最後のものは削除せず、名前を記録するだけです。セッション runner では、スクリプトはレーンでもここでも何も回収しません。すべてのエージェントが実行全体を通じてタブとワークツリーを保持し、runner が終了時に何かを要求することもありません。オペレーターは必要なときに run ビューから回収します(ADR-0012、crew runner reference)。順次実行のチェーンワークツリーは実行のものであり、個々のエージェントのものではありません。エージェントの回収後も残り、実行のエージェントが保持中でなく、そこで実行中のエージェントもいなくなってから、最後に Reclaim All だけが削除します。実行の停止中にチェーンが回収された場合、再開時に必要になれば実行のベースから再作成されます(ADR-0020)。停止した実行では何も回収しません。再開時に引き継ぐのはそのワークツリーです。ブランチ、PR、ユーザー自身のチェックアウトには触れません。
スタック
チケットを1件ずつ処理しながら、レーンは通常のチェーンを構築します。各PRは下のブランチをベースにし、構築と並行してネイティブの GitHub スタックとして登録します。そのため、2層目以降のすべてのPRでオペレーターはスタックマップを確認でき、スタック全体を 最上位のPRから1回のアトミック操作でマージできます(または PUT .../pulls/<top>/merge-async)。
この実行で使う gh stack コマンドは gh stack link だけです。これはステートレスで、状態を調整し、強制プッシュせずにプッシュします。ただし、指定したすべてのブランチをローカル参照から1回のアトミックなプッシュでプッシュします。この点が、このセクションの残りの内容の前提となります。このコマンドの呼び出しには3つのルールがあります(ADR-0006)。実行がエージェント間で状態を持ち越すことになるスタック番号のショートカットは使わず、毎回スタック全体を再リストすること。link が実行の制御外でPRを作成することがないよう、PRがまだ存在しないブランチは指定しないこと。そして、下書き状態を解除して進行中のシグナルを消してしまうため、--open は渡さないこと。プッシュが拒否されないようにする4つ目のルール(ADR-0007)は、link 呼び出しで指定するすべてのブランチについて、事前にローカル参照を origin から同期することです。レーン自身のブランチは、構築時点でローカル == origin です。一方、レイヤー0はそうではありません。実行前に origin に到達しており、以前の実行で残った古いローカル参照がそのブランチを隠しているためです。また、継承したブランチは実行中に origin 上で更新されることがあります。公開済みのレイヤーは実行によって書き換えられることがないため(公開は1回のみ)、origin が正であり、同期によって取り除けるのは古いシャドウ参照だけです。ただし、ワークツリーでチェックアウトされているブランチはそのままにします。スタックの最小層数は2つなので、PRが1つだけのスタックは登録されません。これは失敗ではなく、正しい動作です。link の失敗によって公開が失敗することはありませんが、実行では2種類の失敗を区別します。一時的な失敗は、次の公開時にスタックを再リストすることで自動的に解消されます。一方、プッシュの non-fast-forward による拒否は、origin とまだ一致しないローカル参照が指定されていることを示す確定的な問題です。その旨をログに記録し、解消を待つのではなくブリーフに記載します。ハード終了コード9の場合に限りラッチされ、実行はプレーンなチェーンに切り替わります。その場合は脚注ではなく、ブリーフの専用フィールドに記載します。この拡張機能の残りは並列実行に適さないため、引き続き使用禁止です。submit はすべてのブランチを強制プッシュします。init、add、sync、rebase、checkout はワークツリーごとに状態を保持する(.git/gh-stack は --git-dir 経由で解決される)か、スタックですでに公開済みの履歴を書き換えます。
マージ前にオペレーターが把握しておくべき事項は、ブリーフにも記載されます。
- 各レイヤーは個別にすべての条件を満たす必要があります。必須チェック、必須レビュー、CODEOWNERS は、スタック内の各PRについて、スタックのベースブランチを対象として評価されます。最上位のPRだけを承認しても不十分です。
- CIは各レイヤーでベースブランチを対象とする場合と同様に実行されるため、スタックが深いほどCIの使用量が増加します。
- スタックのマージに失敗した後は、ベースブランチの実際の状態を読み直してください。GitHubの公式ドキュメントでは、マージ失敗時に完全にロールバックされるかどうかについて記述が一致していないため、何も前提にしないでください。
- チェーンモードではスタックオブジェクトが存在しません。手動で下から順に、マージコミットを使ってマージしてください。各マージ後にヘッドブランチを削除すると、GitHubが次のPRのベースを切り替えます。スカッシュマージとリベースマージではSHAが書き換えられ、子PRすべてに見せかけの差分が生じます。
却下台帳
ゲートループの危険は収束が遅いことではなく、堂々巡りです。修正担当者が指摘を誤りだと判断してコードを変更せず、次のレビュアーが再び指摘し、上限に達するまで同じ指摘をやり取りします。そのたびにエージェントが2つ稼働しますが、何も変わりません。
そのため、却下は第一級の結果として扱います。各修正スライスは、担当する各指摘について、fixed または 具体的で検証可能な理由を添えた rejected のいずれかの判定を返し、却下はすべて台帳に蓄積され、後続のすべてのラウンドに引き継がれます。後のレビュアーが却下済みの指摘を再び挙げられるのは、記載された理由が誤りであることを反証する場合に限られます。つまり、どの部分がなぜ誤りなのかを示す必要があります。それがなければ、意見が分かれる指摘についてループが終わらなくなります。
台帳が機能するには、すべての指摘に何らかの結果が返ってくる必要があります。そのため、ワークフローでは、プロンプトではなくスクリプト内で指摘と判定を照合します。場所と問題が完全に一致するか、そうでなければ、その場所を主張する判定が1つだけである必要があります。判定のない指摘が修正済みと見なされることはありません。ログに記録され、次のレビュアーに明示され、そのレビュアーがブランチ上で確認します。エージェントに未実施の作業を尋ねて照合すれば、ADR-0004が取り除こうとしている失敗を再び招くことになります。
各ラウンドでレビュアーを新しくすることで、「問題なし」という判定が、1人のレビュアーの忍耐が尽きた結果ではなくなります。
レビュー
ゲートと最終パスの両方で、リポジトリ内の code-review スキルを実行します。このスキルは、固定点からの差分を2つの軸、すなわち 標準(このリポジトリの文書化された標準に従っているか)と 仕様(元となった課題で求められたことを実行しているか)で、並列サブエージェントを使ってレビューします。
両者の違いは、基準として固定する対象だけです。ゲートでは、チケットブランチの作成元となったベースを固定点として、チケットを仕様として固定するため、チケットは独自の受け入れ基準に照らして評価されます。最終パスでは、スタック全体を対象に、ベース参照と 仕様の課題を固定します。
さらにゲートでは、スキルの重大度を上書きします。ブロックできるのは、未達の基準、失敗した検証コマンド、または正確性のバグだけです。標準の軸も引き続き実行して報告しますが、重大度は minor として扱われ、合格となります。そのため、ADRからの逸脱はラウンドを消費することなくPR上で確認できます(ADR-0008)。スタック全体のレビューでは、両方の軸を最大限の重みで扱います。
帰属
仕様とチケットをタスクグラフとして扱うこと、実装サブエージェントがそれぞれ独自のワークツリーを使うこと、共有の作業系列への公開、そしてレビュー後に準備完了とすることから成る9段階の構成は、Anthropicの implement-spec スキルに基づいています。このスキルが追加するのは、それを1つの決定的なスクリプトに落とし込むこと(フロンティアスケジューリング、直列の公開レーン)、ネイティブなGitHubスタックとして登録されるチケットごとの積み上げ式PR出力、却下台帳を備えたチケット単位のゲートループ、人による承認を必要とするチケットの除外、そしてオーケストレーターのコンテキストを空に保つ規律です。