my-interactive-review
agent-review-kit を使って、ユーザーとブラウザ経由のレビューループを回す。
このスキルを実行しているメインセッションは受付とオーケストレーションに徹する: コメントを待ち、読み、トリアージし、質問に回答し、修正結果を検証して resolve する。コード修正そのものはサブエージェントに委譲する(「修正の委譲」参照)。
**コメントごとに別の Codex / Claude Code セッションを起動しない。**レビュー開始から終了まで、スキルを起動した同じメインセッションを継続する。
前提
agent-review-kit コマンドが使えること。このスキルは agent-review-kit リポジトリに同梱されているため、基本はそのローカルクローンの CLI を使う(node <repo>/dist/cli.js <command>)。グローバルインストール済みなら agent-review-kit で直接呼べる。npm 未公開のため npx agent-review-kit は使えない(未確認の同名パッケージを npx で取得しない)。
- 対象プロジェクトの作業ディレクトリ(git リポジトリ)で実行すること。
.agent-review/ がそこに作られる。
Codex でのバックグラウンド実行
Codex では serve と wait-comments をそれぞれ長時間実行できるターミナルセッションとして起動する。exec_command を短い yield_time_ms で呼び、返された session_id を保持する。完了通知がない環境では、空入力の write_stdin を最大30秒程度の yield_time_ms で呼んで出力を回収する。
- シェルの
& で切り離さない。Codex が追跡できるターミナルセッションを使う。
serve 用と wait-comments 用の session_id を混同しない。
activeWaitSessionId を1つだけ管理する。値がある間は新しい waiter を起動しない。セッション結果を回収したら値をクリアし、received ならコメントを処理キューへ追加してから次の通常 waiter を1本だけ起動する。
wait-comments が1バッチを返すと、そのターミナルセッションは正常終了する。出力を回収してから次の待機を新しいセッションで開始する。
- レビュー中は最終回答を返してタスクを閉じない。短い進捗をユーザーへ伝えながら、終了シグナルまで待機・対応を続ける。
- Claude Code では同じ役割をバックグラウンドタスクと完了通知で実現する。以降のレビューロジックは共通とする。
設定(settings)の扱い
設定はユーザーがブラウザの歯車メニューからレビュー中いつでも切り替えられる。現在の設定は wait-comments の受信出力(received)に毎回同乗してくるので、そのバッチの処理はその settings に従う。別途 status で確認しにいく必要はない(受信より前に把握したい場合のみ agent-review-kit status を使う):
settings.readOnlyMode: true — 読み取り専用モード。他人の MR を閲覧するだけのレビューなど、コードを変更してはいけないモード。修正指摘が来てもコードを変更せず、調査結果・修正案を --status answered で回答するだけにする。サブエージェント委譲もしない。received の settings を見ずに修正へ進むことを禁止する。
settings.snapshotsEnabled: false — 修正スナップショット(後述)を保存しない設定。snapshot create は {"status":"skipped"} を返すので、コマンド手順は変えなくてよい(resolve-comment に --snapshot を付けないだけ)。
settings.deliveryNoteEnabled / settings.deliveryNoteText — wait-comments の received 出力に note フィールドを同乗させる設定。deliveryNoteEnabled: true のとき deliveryNoteText がそのまま note になる(既定は OFF。テキストの既定値は「修正はサブエージェントに委譲する」という指示で、ユーザーが自由に書き換えられる。空なら note なし)。note が付いていたら、そのバッチの処理でその指示に従うこと。 ただし readOnlyMode: true のときは修正禁止が常に優先で、note に修正系の指示があっても修正しない。
各設定の初期値は、ユーザーが ~/.agent-review/.env に ARK_* 形式で定義していることがある(README「設定のデフォルト(.env)」参照)。エージェント側の挙動は変わらない — 従うべき値は常に received の settings(および status 出力)に解決済みで入ってくる。
コメント本文の書き方(Markdown)
add-comment --body と resolve-comment --message に渡した本文は、ブラウザ上で Markdown としてレンダリングされて表示される(プレーンテキストではない)。指摘や回答は読みやすさを意識して Markdown で書くこと。
使える記法:
- 見出し(
#〜######)
- 箇条書き(
- / * / +)・番号付きリスト(1.)・ネストしたリスト(半角スペース2つ以上のインデント)
- チェックリスト(
- [ ] / - [x])
- インラインコード(
`foo()`)とコードブロック(で囲む)。コードブロックは ````ts ```` のように言語を付けるとシンタックスハイライトされる(ts / py / rs 等の拡張子形式でよい。言語指定なし・不明な言語はプレーン表示)
- 強調(
**太字** / *斜体* / ~~打ち消し~~)
- 引用(
>)、水平線(---)、リンク([文言](https://...))と裸のURL
- 表(GFM形式。ヘッダ行の直下に区切り行
| --- | --- | を置く。揃えは :-: で中央、--: で右)
書き方の指針:
- 指摘は結論を1行目に置く。理由・影響・修正案はその後に続ける。
- ファイルパス・識別子・型名・コマンドは必ずインラインコードで囲む(
`src/foo.ts`、`user_id`)。地の文に裸で書かない。
- 修正案のコードは必ずコードブロックにし、言語を必ず付ける(付けないとハイライトされない)。差分を見せたいときは「現状」「修正案」の2ブロックに分ける。
- 複数の論点があるコメントは箇条書きにする。長い説明を1段落に詰め込まない。
** や _ を強調のつもりなく使わない(_ は前後が英数字なら強調にならないので snake_case はそのまま表示される)。
改行の渡し方に注意する。--message に実際の改行を含む文字列をそのまま渡してよい(シェルのヒアドキュメントや引用符で複数行を渡す)。実改行が渡せない場合は \n という2文字で書けば表示時に改行として扱われる。
添付画像(imagePaths)の扱い
ユーザーはコメントフォームにスクリーンショット等をペーストして添付できる。添付があるコメントは received で imagePaths(画像ファイルの絶対パスの配列)を持って届く(バッチには説明の imagesNote も同乗する)。内容の確認が必要なコメントについてのみ、そのパスを Read ツールで読み込んで画像として参照すること(全コメントの画像を無条件に読まない — トークン節約のためのパス渡しである)。サブエージェントに修正を委譲する場合は、委譲プロンプトに imagePaths のパスをそのまま含め、サブエージェント側で Read させる。
コメント本文中に [画像: img_xxx.png] 形式のマーカーが入っていることがある。これはユーザーが画像をペーストした位置に自動挿入されたもので、imagePaths の各パスの末尾のファイル名(id)と突き合わせれば、本文中のどの位置について貼られた画像かを特定できる。
コメント種別(intent)の扱い
ユーザーはコメント投稿時に「修正依頼」か「質問(回答のみ・修正しない)」を選べる。選択結果は各コメントの intent フィールドとして wait-comments の出力に入ってくる。コメント本文を読む前に intent を見て、そのコメントの扱いを決める:
intent: "question" — そのコメント1件だけの読み取り専用モード。コードを調査して回答するだけにし、コードを変更しない(サブエージェント委譲もしない)。修正が必要だと判断した場合も、修正案を提示するだけにとどめ、--status answered で返す。ユーザーが修正を望むなら改めて「修正依頼」のコメントが届く。
intent: "fix" — 本文で判断する(修正指摘なら修正、実質的に質問なら回答)。
intent が無いコメント(add-comment で投稿した AI 指摘、古い comments.json)も本文で判断する。
- 本文が
【push依頼】 で始まる全体コメント(file: null、ブラウザ右上の「push を依頼」ボタンから投稿される) — 現在のブランチを push してほしいという依頼。コード修正ではないのでサブエージェントに委譲せず、メインセッションが次の順で対応する:
git status --short で未コミットの変更を確認する。未コミットの変更がある場合は、まだコミットせず、コミット案を --status answered の返信で示して止まる。コミット案は「コミット指示への対応」の既定ルール(未 push のブランチなら既存のローカルコミットも含めて 1 コミットにまとめる / push 済みなら指摘単位)で組み立て、作るコミットごとにメッセージと含めるファイル(指摘単位ならどの指摘か)を並べる。指摘単位のときは「スナップショット外の変更が残れば最後に別コミットにする」ことも書いておく。.env・ログ・一時ファイルなど明らかにコミット対象外のものは除外案として別に書く。ユーザーの返信は同じスレッドの parentId 付きコメントとして届く。了承ならその案どおりにコミットして 2. へ進み、修正の指示があれば案を直して従う(修正後の案を再提示するかは指示の内容で判断する)。未コミットの変更が無ければ確認せずに 2. へ進む。
- push する(
git push -u origin <ブランチ>。upstream 設定済みなら git push)。force push はユーザーが明示した場合以外は行わない(1 コミットにまとめるのは未 push のブランチだけなので force push は要らない)。
- 成功したら
resolve-comment <id> --status answered --message "push しました: <ブランチ名> @ <短い sha>(<作ったコミットの一覧>)" で返信する(スレッドを閉じるのはユーザー)。失敗したら出力を添えて同じく --status answered で返す。
ブラウザは readOnlyMode: true の間このボタンを無効化する(サーバーがこのモード中の投稿を intent: question に強制するため)。したがって push 依頼が届くのは読み取り専用 OFF のときだけで、届いた依頼の intent は常に fix になる。
- 本文が
【PRコメント取込依頼】 で始まる全体コメント(file: null、intent: "question"。ヘッダの「PRコメント取込」ボタンから投稿される) — PR/MR のレビューコメントを取り込み、各スレッドに見解を返してほしいという依頼。コードは変更しない(だから question で届く)。サブエージェントに委譲せず、メインセッションが「PR/MR コメントの取込」の手順で対応する。
- 本文が
【PR Pending コメント依頼】 で始まるコメント(intent: "question"。設定 prPendingCommentEnabled が ON で PR/MR を登録している間だけ出る「PR に下書き投稿」ボタンから投稿される。コメントカードからなら返信、行コメントフォームからならトップレベルのコメントとして届く) — 他人の PR/MR をレビューするとき(my-interactive-review-peer)の投稿依頼で、接頭辞の次の行以降の文面を、そのコメントの位置(file / side / startLine / endLine)に PR/MR の Pending(未公開)レビューコメントとして投稿してほしいというもの。コードは変更しない(readOnlyMode 中でも実行してよい操作)。サブエージェントに委譲せず、メインセッションが対応する。手順の詳細は my-interactive-review-peer の「PR への Pending コメント投稿依頼」を参照。
manualEdit: true のコメント(本文が 【手動修正】 で始まる) — ユーザーがブラウザの手動修正機能で直接書き換えた通知。修正は適用済みで、コード対応は不要(サブエージェント委譲もしない)。内容を確認したら --status answered にするだけでよい(snapshot は作らない。このコメントはブラウザには表示されないので、返信メッセージも不要)。対象が2種類ある:
- diff レビュー(
file あり)— working tree のファイルが変わっているので、以降そのファイルを扱うときは必ず読み直す。
- HTML レビュー(
documentId あり)— 保存済み文書(.agent-review/branches/<ブランチ>/documents/<id>.html)が変わっている。この文書を publish-html で再公開すると手動編集が上書きされるため、以後この文書を更新する場合は保存済みの現在の内容を読み込み、それを基に編集して publish する。
settings.readOnlyMode: true の間は、ブラウザの選択肢が「質問」に固定され、サーバー側でも投稿されたコメントの intent が強制的に question になる。したがって読み取り専用モードでは全コメントが intent: "question" で届く。仮に intent: "fix" を見ても readOnlyMode: true なら修正しない(修正しない側が常に勝つ)。
手順
-
対象 git リポジトリのルートを確認し、以降の全コマンドをそのディレクトリで実行する。Codex の各ツール呼び出しでも、このパスを workdir に指定する。
git rev-parse --show-toplevel
サブディレクトリのまま実行すると .agent-review/ の位置とレビュー対象がずれるため、そのまま進めない。リポジトリルートへ移動したらレビューHTMLを生成する。
agent-review-kit generate
ブランチ全体をレビュー対象にする場合は、比較先ブランチ名をそのまま指定せず、git fetch origin したうえで origin/main(リポジトリの既定ブランチが master なら origin/master)との merge-base を base にする。ローカルの main は古いことがあり、それとの merge-base では他チケットのファイルが差分に混ざる:
git fetch origin
BASE_REF=$(git merge-base origin/main HEAD) # 既定ブランチが master なら origin/master
agent-review-kit generate --base "$BASE_REF"
agent-review-kit generate --base main は main...HEAD ではなく main..現在のworking tree 相当になり、現在ブランチに未取り込みの main 側変更が逆向きの差分として混ざることがある。未コミット差分だけをレビュー対象にする場合は、base を指定せず agent-review-kit generate を使う。
-
serve を起動する。ポートは固定ではなく、.agent-review/server.json に記録された実ポートを使う。
まず、このプロジェクトの serve が既に生きているか確認する。server.json があり、そのポートの /api/status が返す projectDir が現在のプロジェクトディレクトリと一致すれば起動済み:
PORT=$(node -p "String(require('./.agent-review/server.json').port)" 2>/dev/null)
[ -n "$PORT" ] && curl -s "http://localhost:$PORT/api/status" | grep -F "\"projectDir\": \"$(pwd)\""
# → マッチすれば起動済み。それ以外(server.json なし・curl 失敗・projectDir 不一致)は未起動扱い
(jq が無い環境でも動くよう node で読む。node -p の数値出力は色コードが混ざることがあるため String() で包む)
未起動なら、agent-review-kit serve をバックグラウンドで起動する。Codex では「Codex でのバックグラウンド実行」に従い、短く yield して返された session_id を serve 用として保持する。Claude Code では run_in_background を使わず、setsid nohup で切り離して起動する(Claude Code 2.1.285 以降、run_in_background の Bash は timeout(最大 2 時間)で強制停止され、レビューページが開けなくなるため。コメントの配達は serve ではなく wait-comments が行うので、切り離しても届く):
mkdir -p ~/.review-hub/logs
setsid nohup agent-review-kit serve > ~/.review-hub/logs/ark-$(basename "$(pwd)").log 2>&1 < /dev/null &
ポートは 5179 から空きを自動選択して server.json に記録される。起動出力(切り離した場合はログ)または server.json から実ポートを取得する。
注意: projectDir の一致確認を省略しない。 別プロジェクトの serve が同じポートで生きていると、curl が成功してしまい、ユーザーが別プロジェクトの diff にコメントを書く事故になる。
-
ユーザーに「http://localhost:<実ポート> を開いてレビューしてください」と伝える(<実ポート> は server.json の値)。
行番号クリックで単一行コメント、Shift+クリックまたはドラッグで行範囲コメントができることも添える。
手順2で serve を新しく起動した場合だけ、その URL をブラウザで開く(cmd.exe が無い環境では何もしない)。起動済みの serve を再利用した場合は開かない(スキル実行のたびにタブが増えるため):
command -v cmd.exe >/dev/null && cmd.exe /c start "" "http://localhost:<実ポート>" >/dev/null 2>&1
-
コメントを待つ。レビュー開始後の最初の1回だけ --resume を付ける。前のエージェントセッションが受信済みのまま中断した seen コメントも、新規 open コメントと一緒に回収できる:
agent-review-kit wait-comments --timeout 6600 --resume
Codex ではこのコマンドを追跡可能なターミナルセッションとして起動し、wait-comments 用の session_id を保持する。出力を回収するときは、そのセッションへ空入力の write_stdin を送る。受信すると {"status": "received", "note": "...", "settings": {...}, "comments": [...]} が stdout に返り、該当する open コメントは seen になる。settings は受信時点の設定で、このバッチの処理方針はここに従う(「設定(settings)の扱い」参照)。note は設定に応じて同乗する処理指示(無いこともある)。付いていたら必ず従う。
受信した waiter の終了結果を回収して activeWaitSessionId をクリアし、コメントを処理キューへ追加する。その後、--resume を外した新しい waiter を1本だけ開始し、その新しい session id を保存する:
agent-review-kit wait-comments --timeout 6600
--resume を2回目以降にも付けると、現在対応中の seen コメントが重複配達されるため禁止する。新しい待機はサブエージェントの作業中も生かしておき、完了通知またはセッション出力を後で回収する。CLI が another wait-comments process is already running を返したら再試行で増やさない。現在のタスクが保持する既存 session id を使い、所有していない waiter なら PID を kill せず、前のタスクの終了を確認してから最初の --resume をやり直す。
ユーザーがブラウザの「レビュー終了」ボタンを押すと {"status": "finished", "comments": []} が返る。finishing = true として新しい waiter は起動しない。すでに受信済み・対応中の処理キューは破棄せず、検証・resolve まで完了してから手順12へ進む(AI指摘の見送りとサーバー停止自体はサーバー側で完了済み)。
{"status": "interrupted", ...} が返った場合(または serve が「シグナルにより停止」と出力して終了した場合)は、ユーザーが中断ボタン等で意図的に止めている。障害ではないので、wait-comments・serve を自動で再起動しない。ユーザーの明示指示(「再開して」等)を待つ。
--resume が前回セッションの取りこぼし回収を兼ねるため、ブランチ別の comments.json を手作業で探索しない。
-
受け取った各コメントを読む。intent / file / side / startLine〜endLine / body を確認する。
intent: "question" のコメントは修正せず回答だけする(「コメント種別(intent)の扱い」参照)。手順6へ進み、手順7には進まない。
file: null のコメントは特定の行ではなくレビュー全体への指摘・質問(side / 各行番号も null)。本文が 【push依頼】 で始まるものは push 依頼(「コメント種別(intent)の扱い」の項を参照)。
- 行範囲コメント(startLine ≠ endLine)の場合は、範囲全体のコードを読んで指摘の意図を解釈する。1行だけ見て対応しない。
code(before / lines / after)はそのコメントが書かれた時点のコードのスナップショット。修正が進むと startLine〜endLine は現在のファイルの別の場所を指すようになるので、行番号と code が食い違うときは code の方が指摘対象である。code.lines の内容を現在のファイルから検索して本当の対象行を特定してから対応すること(code が無いコメントは行番号で辿る)。
side: "old" は変更前(削除側)の行に対するコメント。削除やリファクタへの指摘であることが多い。
parentId を持つコメントは既存コメントへの返信。親コメント・その agentResponse・同じスレッドの他の返信を含むスレッド全体を読んで文脈を解釈すること(agent の回答への追撃質問であることが多い)。返信のアンカー(file/side/各行番号)は親からコピーされ、parentId は常にトップレベルの親を指す(スレッドは1段ネスト)。
- 本文が
**修正差分 #N(snap_xxx)** \<ファイル>` 変更後 L10-L12 とコードの引用で始まる返信は、ユーザーが**修正差分ページ(/snapshot/<id>`)の行を選んで書いた**もの。指摘対象はアンカー(親からのコピー)ではなく本文に書かれた箇所で、行番号はその修正を記録した時点のもの。後の修正でずれていることがあるので、引用されたコードを現在のファイルから検索して対象行を特定する。
- 対応前に
git diff で現在の変更状態を確認する。
-
コメントが質問(intent: "question"、または intent 未指定で本文が質問)なら、コードを調査して回答をまとめ、次で返す:
agent-review-kit resolve-comment <id> --status answered --message "回答内容"
回答は一文目で質問に答える。
-
コメントが修正指摘なら、まずそのコメントの intent とそのバッチの received.settings.readOnlyMode を確認する(intent: "question" または readOnlyMode: true なら修正せず、修正案を answered で回答する)。修正する場合、メインセッションは自分でコードを編集せず、サブエージェントに委譲する(1件だけでも委譲する。方針は「修正の委譲」)。
修正はコミットせず、スナップショットとして記録する。 git 履歴を汚さずに「この指摘に対する修正だけ」の差分ページをユーザーに見せられる。流れ:
# (1) shared working tree ならサブエージェントを起動する【直前】、
# isolated worktree / patch ならメインへ取り込む【直前】に現状を記録
agent-review-kit snapshot begin
# (2) 変更を取り込み、メイン側で型チェック/テストを通す
# (3) この修正の patch を保存し、スナップショット id を得る
agent-review-kit snapshot create --comment <コメントid> --title "対応内容の短い説明"
# → {"status":"created","snapshot":{"id":"snap_xxx",...}}
# (4) スナップショットリンク付きで resolve する
agent-review-kit resolve-comment <コメントid> --status fixed --message "対応内容の説明" --snapshot snap_xxx
--snapshot を付けると返信に「📄 修正差分」リンクが表示され、ユーザーがクリックするとその修正だけの差分を新しいタブで確認できる(コミット不要・ローカル完結)。--snapshot には --message が必須。テスト・型チェックの実行は必須で、失敗したまま fixed にしない。
- サブエージェントが worktree 内でコミット済みの場合は、begin/create の代わりに
snapshot create --comment <id> --commit <そのsha> でそのコミットの差分を patch として取り込める(worktree のコミットは共有 object DB 経由で解決される)。
snapshot create が {"status":"skipped"} を返したら設定でスナップショットが OFF。--snapshot を付けずに resolve する。
- メインブランチへのコミットはユーザーが明示的に指示した時だけ行う(粒度は「コミット指示への対応」参照)。
-
対応すべきか判断できない場合は、理由を書いて返す:
agent-review-kit resolve-comment <id> --status answered --message "判断できない理由と選択肢"
対応しない判断をユーザーと合意済みの場合も --status answered で返し、本文に「対応しない」ことと理由を書く。
-
コードを修正し、まだ finishing = false の場合は、レビューHTMLを再生成する(コメントは保持される):
agent-review-kit generate --preserve-finished
--base を省略しても前回 generate の base が引き継がれるので、ブランチ全体レビューの再生成でもそのまま実行してよい。--preserve-finished は、再生成と同時にユーザーが終了ボタンを押しても終了シグナルを消さないために必須。ブラウザ側は自動でリロードされる。finishing = true なら再生成せず、受信済みコメントの resolve と最終検証だけを終える。
コードを直したら、ユーザーへ報告する前に毎回再生成し、修正分のスナップショットを付けてスレッドに返信する(手順7)。backend と frontend の差分を別々に見せている場合、backend の修正は backend の差分タブ側のスレッドに付ける。
-
現在の状態を確認する:
agent-review-kit status
unresolved(open + seen)が未解決コメント数=エージェント側の作業待ち。wontfix / dismissed は解決済みには数えないが(解決済みは resolved だけ)、待っているのはユーザーの確認なので画面上は「要確認」として残るだけで unresolved には含まれない。
-
未解決コメントが残っている、またはユーザーのレビューが続いている間は、すでに保持している activeWaitSessionId の結果を回収する。値がない場合に限り、手順4の通常待機(--resume なし)を1本起動する。received を回収したら処理キューへ追加して再び1本だけ待機を起動する。finished を回収したら finishing = true とし、待機を再起動せず処理キューを drain する。
-
次のいずれかでループを終了し、対応内容をまとめて完了報告する:
wait-comments が {"status":"finished"} を返し、受信済み・対応中の処理キューが空になった(ユーザーがブラウザの「レビュー終了」ボタンを押した)
unresolved が 0 になり、ユーザーが会話でレビュー完了を示した
会話上でレビュー完了を示した場合は、最終回答の前に実ポートへ POST /api/finish を送り、wait-comments が finished で終了し serve も停止したことを確認する。これによりバックグラウンドセッションを残さない。ブラウザ側ですでに終了済みなら再送しない。
curl -s -X POST "http://localhost:<実ポート>/api/finish"
コミットの指示があればここで対応する(「コミット指示への対応」参照)。
コミット指示への対応
レビュー中の修正は working tree に未コミットのまま蓄積されている。ユーザーからコミットの指示を受けたら、指定された粒度でコミットする。粒度の指定が無いとき(push 依頼ボタンでコミット案を作るときを含む)は、ブランチが push 済みかで決める:
git fetch origin
BRANCH=$(git branch --show-current)
git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null && echo pushed || echo unpushed
- 未 push(リモートにブランチが無い)→ まとめて1コミット。ローカルにしか無い既存コミットもまとめて 1 つにする(下記)
- push 済み → コメント単位でコミットを分ける(MR 上で指摘ごとの修正を追えるようにする)。リモートにあるコミットは書き換えない
コミットメッセージは日本語で、リポジトリの慣習(git log --oneline -20)に合わせる。Co-Authored-By などのトレーラーは付けない。
-
まとめて1コミット: 未 push のブランチなら、既存のローカルコミットも含めて 1 つにまとめる。リモートに無いブランチの履歴なので書き換えても誰にも影響しない。メッセージはブランチ全体の変更を要約する:
BASE=$(git merge-base origin/main HEAD) # 既定ブランチが master なら origin/master
git reset --soft "$BASE" # 既存コミットの内容は index に残る
git add <未コミットのファイル> # 対象外ファイルを除いて明示的に add
git commit -m "<ブランチ全体の要約>"
# snapshot があれば、全 patch をコミット済みとして印を付ける(後でコメント単位に切り替えたとき再生し直さないため)
echo <index.json の最大 seq> > "$(agent-review-kit snapshot path)/committed-seq"
ユーザーが明示的に 1 コミットを指示した場合で、ブランチが push 済みなら、既存コミットはまとめず working tree の変更だけを 1 コミットにする。
-
コメント単位でコミットを分ける: agent-review-kit snapshot path が返す現在ブランチのディレクトリにある patch を、時系列(ファイル名の連番)順に再生する。各 patch は「その時点の working tree」を前提に作られているため、先に最初の snapshot begin 時点の状態(index.json の baselineTree)を復元してから再生する。レビュー開始時に未コミット変更があった場合、この復元を飛ばすと patch #1 が適用できない。
2 回目以降のコミット(push 依頼を複数回押した場合など)では、前回コミットした patch を再生し直さない。前回どの seq までコミットしたかは snapshot ディレクトリの committed-seq に記録しておき、それより後の patch だけを再生する。このときベースラインの復元は行わない(HEAD がすでにベースラインより先にあり、復元すると前回のコミットを打ち消してしまう):
# (0) 最終状態を tree として記録してから退避(安全ネット兼、再生後の検証基準。
# stash@{0} との diff は untracked ファイルが stash^3 に分かれて正しく比較
# できないため、必ずこの tree と比較する)
SNAP_DIR=$(agent-review-kit snapshot path)
git add -A && FINAL_TREE=$(git write-tree)
git stash -u
# (1) ベースライン(最初の snapshot begin 時点)を復元する。初回(committed-seq が無い)だけ。
# HEAD と一致していれば diff は空で、このコミットはスキップされる
DONE_SEQ=$(cat "$SNAP_DIR/committed-seq" 2>/dev/null || echo 0)
if [ "$DONE_SEQ" -eq 0 ]; then
BASE_TREE=$(node -e 'const fs=require("fs"); console.log(JSON.parse(fs.readFileSync(process.argv[1])).baselineTree)' "$SNAP_DIR/index.json")
git diff HEAD "$BASE_TREE" > /tmp/ark-baseline.patch
if [ -s /tmp/ark-baseline.patch ]; then
git apply --index /tmp/ark-baseline.patch
git commit -m "<レビュー開始時点の変更(実装本体など)の要約>"
fi
fi
# (2) 連番順に、未コミットの patch だけ 適用 → コミット を繰り返す
for p in "$SNAP_DIR"/0*.patch; do
SEQ=$((10#$(basename "$p" | cut -d_ -f1)))
[ "$SEQ" -le "$DONE_SEQ" ] && continue
git apply --index "$p"
git commit -m "<index.json の該当エントリの title / commentId の指摘内容から要約>"
echo "$SEQ" > "$SNAP_DIR/committed-seq"
done
# (3) 最終状態との差分 = スナップショットの外で行われた変更(手動編集など)。
# 残っていれば取り込んで最後の別コミットにする(コミット案などで了承を得ていなければ
# ユーザーに確認してから)。取り込まずに stash を捨てると失われる
if [ -n "$(git diff "$FINAL_TREE")" ]; then
git diff --binary HEAD "$FINAL_TREE" | git apply --index
git commit -m "<残りの変更の要約>"
fi
git diff "$FINAL_TREE" # 空なら再生完了(最終状態を完全再現)。退避を破棄:
git stash drop
patch 適用が失敗した場合は git apply --3way --index を試し、それでも駄目ならそこで停止し、stash を保持したまま状況を報告する。ユーザーの明示承認なしに git reset --hard しない。ベースラインのコミットを履歴に残したくない場合は、再生後に rebase 等での整理をユーザーと相談する。
AI レビューモード
ユーザーが「AI にレビューさせたい」「まず自分(エージェント)でレビューして」と指示した場合の変形モード。ループの仕組み(serve / wait-comments / resolve / generate)は通常と同一で、ループ開始前に自分が diff をレビューして指摘をコメントとして投稿する前段だけが追加される。
-
手順1〜2(generate / serve)まで通常どおり実行する。
-
レビュー対象の diff を読み、観点(バグ・セキュリティ・性能・可読性・規約)ごとにレビューする。指摘は1件ずつコメントとして投稿する。本文は Markdown で書く(「コメント本文の書き方(Markdown)」参照):
# 行に紐づく指摘
agent-review-kit add-comment --file src/foo.ts --line 42 --body "指摘内容"
# 行範囲
agent-review-kit add-comment --file src/foo.ts --start-line 10 --end-line 20 --body "指摘内容"
# ファイル全体(どの行にも属さない指摘: 構成・責務・そもそも触る必要があるか)
agent-review-kit add-comment --file src/foo.ts --body "指摘内容"
# レビュー全体への所感
agent-review-kit add-comment --body "全体の所感"
指摘は現在の diff に含まれる行に対して行う(diff 外の行は「現在の差分に位置づけできないコメント」として画面下部に落ちる)。--side old で削除側の行にも付けられる。
-
投稿を終えたら、ユーザーに件数と URL を伝えて手順3〜4(案内・wait-comments)に進む。このとき「対応不要と判断した指摘は放置してよい(レビュー終了時に自動で見送りになる)」ことを一言添える。
-
自分が投稿した指摘(AI コメント)はこの時点では処理しない。 wait-comments にも流れてこない(配達されるのはユーザー名義の open コメントだけ)。ユーザーが AI 指摘に返信したら、その返信が通常のコメントとして届くので、スレッド全体(親=自分の指摘)を読んで修正または回答する。修正の流れは手順7と同じ。返信の intent が question なら、親が自分の修正指摘であっても修正せず回答だけする(画面の「🔧 修正を依頼」ボタンからの返信は常に intent: "fix"。このボタンは AI 指摘だけでなく未解決のコメントカードすべて(通常コメント・返信を含む)に出るので、通常コメントのスレッドにも、スレッド途中からも定型の修正依頼返信が届きうる)。
- 修正完了時は2つ resolve する: 届いた返信コメントを
--status fixed --message ... [--snapshot ...] で resolve し、親の AI 指摘も --status fixed で resolve する(親が open のまま残ると未解決カウントに残り続ける)。
-
ユーザーが返信しなかった AI 指摘は、「レビュー終了」ボタンの押下時にサーバーが一括で dismissed(見送り)にする。エージェント側での後始末は不要。
PR/MR コメントの取込
PR/MR の登録
レビュー中のブランチで PR/MR を作成したら、その URL を登録する。差分ページのヘッダにリンクと「PRコメント取込」ボタンが出る:
agent-review-kit set-pr --url <作成した PR/MR の URL>
取り込み依頼への対応
【PRコメント取込依頼】 が届いたら、次の順で対応する。どの段階でもコードは変更しない。
-
取得する。PR/MR の URL は依頼の本文か agent-review-kit status の pr にある。
- GitHub:
- 行コメント:
gh api --paginate repos/<owner>/<repo>/pulls/<番号>/comments。in_reply_to_id の無いコメントがスレッドの先頭で、返信は in_reply_to_id で先頭にぶら下がる。
- スレッドの解決状態:
gh api graphql で pullRequest.reviewThreads(isResolved と先頭コメントの databaseId)を取る。
- レビュー本文:
gh api repos/<owner>/<repo>/pulls/<番号>/reviews の body が空でないもの。
- 会話コメント:
gh api repos/<owner>/<repo>/issues/<番号>/comments。
- GitLab:
glab api --paginate projects/:id/merge_requests/<番号>/discussions。system: true のノートは除く。
-
絞る。まず操作しているアカウントを調べ(GitHub: gh api user --jq .login、GitLab: glab api user の username)、先頭コメントをそのアカウントが書いたスレッドは取り込まない。そのアカウントが他人のスレッドに書いた返信は、そのスレッドの本文に文脈として残す。GitHub / GitLab 上で resolve 済みのスレッドも取り込まない。bot のコメントの扱い(取り込むか・どこまで絞るか)は利用者のルールがあればそれに従い、無ければ取り込む。
-
取り込む。1 スレッドを 1 コメントにする。本文はスレッドの発言を時系列に **@投稿者**: 本文 の形で並べる。
- 位置: GitHub は
path と line(side が RIGHT なら new、LEFT なら old)。line が null(古い差分へのコメント)なら --file だけのファイル全体コメントにする。GitLab は position.new_line(new)か position.old_line(old)。レビュー本文・会話コメント・位置の無いディスカッションは --file なしの全体コメントにする。
- 出典:
--external-author <先頭の投稿者>、--external-url <先頭コメントの URL>、--external-id <ID> を付ける。ID は GitHub の行コメントなら先頭コメントの id、レビュー本文なら review-<id>、会話コメントなら issue-<id>、GitLab はディスカッションの id。
agent-review-kit add-comment --file src/foo.ts --line 42 --body "**@alice**: ..." \
--external-author alice --external-url "<先頭コメントの URL>" --external-id 456
出力の status が exists なら前回取り込み済みなので、見解も返さず次へ進む。
-
見解を返す。新しく取り込んだコメントごとにコードを調べ、resolve-comment <id> --status answered --message "<見解>" で返す。見解は結論を 1〜2 文で先に書く(妥当なので直すべき / 過去の決定どおりなので直さない / 判断が必要、など)。その後に根拠を書く。
- bot の指摘には、まず対応要否と理由だけを返す。ユーザーがクローズの基準を示したら、基準に該当するコメントは全件閉じる。
-
依頼に返信する。依頼コメントに --status answered --message "新規 N 件を取り込み、見解を返しました(取り込み済み M 件・自分のスレッド K 件は対象外)" で返信する(スレッドを閉じるのはユーザー)。
取り込んだコメントは AI 指摘と同じ扱いで、wait-comments には配達されない。ユーザーが見解を読んで「🔧 修正を依頼」を押すか返信すると、その返信が通常のコメントとして届く。以降は「AI レビューモード」の手順 4 と同じで、修正したら返信と親(取り込んだコメント)の両方を resolve する。
PR/MR のスレッドへの返信は外部に公開される操作なので、ユーザーが明示的に指示したときだけ行う。
HTMLレビューモード
diff ではなく、実装プラン・設計書・調査結果などの任意HTMLをブラウザでレンダリング済みのまま見せてレビューさせたい場合の変形モード。ユーザーは要素クリック(要素選択モード)または文章のドラッグ選択でコメントする。ループの仕組み(serve / wait-comments / resolve)は通常と同一で、generate の代わりに publish-html でHTMLを登録・更新する。
-
レビュー対象のHTML(実装プラン等)を生成する。自己完結HTML(インラインCSS、外部リソース参照なし、スクリプト不要)にすること。HTMLは加工されずそのまま保存されるが、表示は iframe + CSP のためスクリプトは実行されず、外部CDN画像・外部CSS 等の外部リソースは読み込まれない(「セキュリティ」参照)。
-
登録する:
agent-review-kit publish-html --input <path> --document-id <id> --title "<タイトル>"
-
サーバーが未起動なら手順2と同じ確認方法で agent-review-kit serve をバックグラウンド起動する(Claude Code では手順2と同じく setsid nohup で切り離す)。
-
ユーザーに http://localhost:<実ポート>/doc/<id> を案内する。「要素を選択してコメント」ボタンで要素クリック、または文章をドラッグ選択してコメントできることを添える。
直前の 3. で serve を新しく起動した場合だけ、この URL を「手順」の手順3と同じコマンドで開く。起動済みの serve を再利用した場合は開かない(タブが増えるため)。
-
コメントを待つ:
agent-review-kit wait-comments --document-id <id> --timeout 6600
常にバックグラウンドで常駐させ、完了通知(バックグラウンドタスクの終了)で受信を判定する(ps/pgrep での生存確認はしない)。二重起動もしない(「注意」の項目と同じ)。
-
届いたコメントの htmlTarget(label / selectedText / contextBefore / contextAfter)でどこへの指摘か特定し、documentId に対応するHTMLの本体に対応する。
-
必要に応じて元のプラン・設計・HTML・関連ファイルを修正する。
-
更新したHTMLを同じ --document-id と --preserve-finished で publish-html し直す(無印だと並行して届いた終了シグナルを消してしまう)。開いているブラウザは自動リロードし、既存コメントは可能な範囲で元の位置に再配置される(再解決できないものは「位置を特定できないコメント」に残る)。
-
対応を記録する:
agent-review-kit resolve-comment <id> --status fixed --message "..."
agent-review-kit resolve-comment <id> --status answered --message "..."
# 対応しない判断も answered で返し、本文に対応しない理由を書く
agent-review-kit resolve-comment <id> --status answered --message "対応しません: <理由>"
-
未解決コメントが無くなるか、wait-comments が finished を返すまで手順5〜9を繰り返す。
HTMLレビューでも、修正はサブエージェントに委譲し、メインセッションは受付・トリアージ・回答・resolve に徹する役割分担は通常のdiffレビューと同じ(「修正の委譲」参照)。コメントの intent と settings.readOnlyMode も同様に尊重する(intent: "question" ならHTMLも元ファイルも書き換えず、回答だけ返す)。
修正の委譲
メインセッションは受付・トリアージ・回答・取り込み・検証・resolve・generate に集中し、コード修正そのものはコメント1件でもサブエージェントに委譲する。
- 直し方が指摘から一意に決まる機械的な修正(typo・命名・定型の書き換えなど)は、サブエージェントにセッションより軽いモデルを相対指定する。どう直すかの判断が要る修正は、セッションと同等のモデルを使う(特定のモデル名をスキルにハードコードしない)。
- 一度に複数コメントを受け取った場合は「対応で触るファイル・領域」でグルーピングする。同じファイルを触るコメント群は1つのサブエージェントにまとめて渡し、グループ間は並列実行する。
- コメントが1件ずつ届く場合も同じ規則で振り分ける: 修正コメントを受信したら、実行中のサブエージェントの担当ファイル/クレートと重なる場合のみそのエージェントに追送する。重ならない場合は直列キューに入れず、必ず worktree エージェントを即時起動して並列化する。「実行中のエージェントに追送する方が楽」を理由に独立な修正を直列化しない。
- スナップショット都合で直列化しない: 並列 worktree の修正は worktree 内でコミットさせ、
snapshot create --comment <id> --commit <sha> でコメント単位のスナップショットを作れる(手順7参照)。並列でもコメントごとの修正差分リンクは成立する。
- 1 エージェントに複数コメントの修正を任せるときも、スナップショットはコメント単位を崩さない: メイン working tree で連続修正させて最後に 1 つの合算スナップショットにすると、各コメントの「修正差分」リンクが全修正混在になり読めない。複数件を任せる場合は worktree で修正ごとにコミットさせ、
snapshot create --comment <id> --commit <sha> をコメントごとに作ってから resolve する。メイン tree 直接編集で進めてしまった場合は、修正間で main 側から snapshot begin/create を挟む。
- 並列実行するサブエージェントは git worktree で隔離する(Claude Code の Agent ツールなら
isolation: "worktree")。同一 working tree 上での並列編集はしない。サブエージェントが1体だけならメインの working tree を直接編集させてよいが、その実行中に新たな修正コメントが来て追加のサブエージェントを起動する場合、後発は worktree で隔離する。
- クライアントが worktree 隔離を提供しない場合(Codex 等)、同じ working tree を共有するサブエージェントのコード編集は完全に直列化する(レビュー中に snapshotsEnabled が切り替わる可能性があり、別エージェントの変更が snapshot に混ざるため)。コード編集の並列化は isolated worktree / 独立 patch を使える場合だけ行う。
- 各サブエージェントには、担当コメントの全文・対象ファイル・検証コマンド(型チェック/テスト)を明示し、検証まで通させ、変更内容(worktree の場合はそのパスまたはブランチ)を報告させる。
- 完了後、メインセッションが変更をメインの working tree に取り込む。コンフリクトしたらその時点でメインセッションが解決する(並列化を諦める理由にしない)。
snapshot begin から snapshot create までの pending 状態は現在ブランチに1つだけなので、複数修正の取り込みとスナップショット作成は必ず1コメント(または同じ原子的変更で解決するコメント群)ずつ直列に行う。並列エージェントの成果を一度に混ぜてから snapshot を作らない。
resolve-comment --status fixed は、変更をメインに取り込み、メイン側で型チェック/テストが通ってから実行する。worktree 内で通っただけでは fixed にしない。
- 全グループの取り込みが終わったら
generate --preserve-finished を再実行してレビューHTMLを更新する。
注意
- コメントごとに別のメインエージェントセッションを起動しない。このセッションがループの主体。受信・トリアージ・回答・resolve はメインセッションが行い、コード修正はサブエージェントが行う。
- 修正前に必ず現在の git diff を確認し、修正後に必ずテストまたは型チェックを実行する。
wait-comments --timeout 6600 は常にバックグラウンドで起動しておく(Claude Code では run_in_background の Bash で、Bash の timeout は 7200000)。サブエージェントの完了待ちの間もコメント受信を止めない。受信して返ってきたら(内容のトリアージ後に)すぐ再度バックグラウンドで起動し直し、レビュー中は監視が途切れないようにする。110 分で自分から終わるので、{"status":"timeout"} が返ったら起動し直す(失敗ではない)。--timeout 0 は使わない(Claude Code 2.1.285 以降は 2 時間で強制停止され、以降コメントが黙って配達されなくなる)。
- wait-comments を二重に起動しない。 再起動時は前のプロセスが終了していることを確認する。2本生きていると、新規コメントが stdout を誰も読まない側に消費され(
seen 化だけされて)握り潰される。
- waiter の起動コマンドには必ず監視対象ディレクトリへの明示的な
cd を含める。 ark のコメントスコープは実行ディレクトリで決まるため、直前のコマンドの cwd(別リポジトリでの git 操作など)を暗黙に継承すると、無関係なスコープを監視する waiter ができてコメントを取りこぼす。起動後に readlink /proc/<pid>/cwd で監視先を確認するとより確実。
- wait-comments の終了判定はバックグラウンドタスクの完了通知、または Codex のターミナルセッション結果で行う。
ps/pgrep でプロセスの生存確認をしない。 wait-comments は open コメントを1バッチ受信すると stdout に書いて即終了する設計で、この「終了」がバックグラウンドタスクの完了通知として届く。したがって「バックグラウンドで起動 → 完了通知が来たら受信済み=終了済み → トリアージ → 再起動」というイベント駆動のサイクルで回せばよく、プロセス一覧での生存確認は不要。むしろ LLM の推論ループでプロセス状態をポーリングするのは高コスト(毎回コンテキスト再読込+推論)で誤りやすく、避ける。どうしても確認する場合は pgrep -af "[w]ait-comments" のように先頭文字をブラケットで囲って自己マッチを除外すること。pgrep -f wait-comments は検索コマンド自身のシェルプロセス(コマンドライン文字列に "wait-comments" を含む)にヒットし、実際には動いていないのに「動いている」と誤検出する。
- 初回の
generate は前回の終了マーカーを消し、レビュー中の generate --preserve-finished は保持する。終了直後に新しいレビューを始める場合は、所有している wait-comments と serve の終了を先に確認してから初回用の generate を実行する。PID だけを見て別プロセスを kill しない。
- ユーザーのブラウザには、
seen のまま応答が滞留したコメントを open に戻す「エージェントに再送」ボタンがある。再送されたコメントは同じ id で再度 wait-comments に届くので、既に対応済み・対応中の id なら重複として扱い、現在の状況(対応中/検証中など)を --message で返す。
- 受信したら即トリアージする: 質問はサブエージェントの完了を待たずメインセッションがその場で回答(
answered)し、実装が必要なものは「修正の委譲」の方針に従って委譲またはキューイングする。
- レビュー対象の diff を変えたい場合(例: コミット後に base を変える)は
generate --base <ref> --preserve-finished を再実行する。
- ユーザーはコメントを論理削除できる(削除済みは wait-comments に配達されず、未解決数にも入らない)。対応中だったコメントが消えていたら、対応を中止してよい。
unresolved(open + seen)にはユーザーが未対応の AI 指摘も含まれる。AI レビューモードでは「unresolved が 0 になるまで」を終了条件にせず、finished シグナルまたはユーザーの完了宣言で終了する。
wontfix / dismissed は解決済みにはならず画面上は「要確認」として残るが、unresolved には含まれない(待っているのはユーザーの確認でエージェントの作業ではない)。wait-comments にも再配達されないので、これらを待ち続けても届かない。
- サイドバー下部にレビュー対象(base..HEAD)のコミット一覧があり、ユーザーはコミット単体の差分ページを開ける。エージェント側の操作は不要。
- agent-review-kit 本体を更新した後は、進行中レビューなら
generate --preserve-finished を再実行する(.agent-review/ の client/*.js / style.css は generate 時にコピーされるため、古いままだと新 UI・新 API が動かない)。