guided-review について
guided-reviewは、コミット、PR、作業ツリーをレビューするローカル配信のHTMLページを作るスキルです。ファイルをテーマ別に並べ、差分、編集可能なテキスト、画像比較、メモ、状態を保持するチェックボックスを一つのページにまとめます。
主な機能
- 01テーマ別のファイル閲覧順を作成
- 02単一のHTMLページに差分を埋め込み
- 03編集可能なテキストと画像比較を提供
- 04レビューのメモとチェック状態を保持
- 05PRスタックとレビューコメントに対応
活用シーン
- 01多数のファイルにまたがる変更のレビュー
- 02ローカルにチェックアウトせずGitHub PRを確認
- 03マージやリベースの競合解決をレビュー
- 04PRスタック内のコメントと編集を調整
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jimhigson/ guided-review --skill guided-reviewインストール後、Claude Code で/guided-reviewを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitignore.npmrcackNote.tsawaitNotes.tsbuild.tsbuildPage.tsbuildStack.tschangedFiles.shconflict.tsconflictFiles.tseditPlace.tsgithub.tsheld.tsmechanical.tsmechanicalChanges.tsmoveDetection.tsmoves.tspackage.jsonpnpm-lock.yamlpnpm-workspace.yamlprComments.tsremotePr.tsreply.tsresolvePr.shresolveStack.tsrestack.tsfixtures/commitStack.shconflictRepo.shlocalStack.shmonorepo.sh詳細
ガイド付きレビュー
単一の HTML ページを生成し、ローカルで配信します。コミット/PR/作業ツリー内のすべてのファイルを、テーマごとにまとめて順に読み進められます(厳密なトポロジカルソートではありません。依存関係の順序を多少崩してでも、関連する作業をまとめます)。各ファイルには短いメモが付き、差分がインラインで埋め込まれます。テキストには編集可能な Monaco エディターを、画像にはパン/ズーム対応の比較ビューアーを使用します。また、チェックボックスの状態は、配信時にレビュー専用ファイルへ保存されるため、そのファイルも読めます。このページだけでレビューが完結し、変更を読むためにほかの場所を開く必要はありません。
公開するのではなく、配信してください(ステップ 8)。配信したページは編集して作業ツリーに保存でき、メモもファイルに保存できます。一方、公開された成果物は、localhost を含むあらゆるエンドポイントへのアクセスを遮断する CSP によって隔離されるため、読むことしかできません。ローカルポートを開く方法がない場合(サンドボックス内や、ほかの人のマシンなど)に限り、公開してください。
変更規模が大きく(数十ファイル)、差分順やアルファベット順に読むと全体の流れを見失いそうな場合に使用します。
ほぼすべてがスクリプト化されています。 判断が必要なのは、途中にある次の部分だけです。
| file | 役割 |
|---|---|
resolvePr.sh | PR 番号/ブランチ/URL → 差分を取るローカル参照を取得します(フォークも含みます) |
remotePr.ts | PR の URL(または owner/name#n)→ 同じ参照を取得します。このマシンにリポジトリがない場合は、リポジトリをベアかつ blobless でクローンします |
resolveStack.ts | 同じ対象、または gh stack の --stack(プッシュ済みかどうかは問いません)→ 対象が属するスタック全体を取得します。ページのスタックバーに使います |
buildStack.ts | スタックに対する作成済みのレビューをすべて(エージェントの数にかかわらず、PR ごとに 1 つの groups json)→ ページ内で切り替えられるレビューを備えた 1 つのページ |
changedFiles.sh | 対象範囲のファイル一覧を STATUS<TAB>PATH として出力します。バイナリは除外します。ただし、比較ビューアーを表示するラスター画像は除きます |
mechanical.ts | 対象範囲にある機械的な変更のみのファイル(インポートのみ、再フォーマット、旧名が残っていない名前変更、コードのそのままの移動)を抽出し、あらかじめチェックを入れる候補にします |
mechanicalChanges.ts | mechanical.ts の判定処理です。build.ts も実行して、ページ上でこれらのファイルに印を付けます |
moveDetection.ts | moves.ts の照合処理です。build.ts も実行して、ページ上で移動されたコードを囲みます |
moves.ts | 対象範囲の移動されたコードを出力します。削除された行のブロックが別の場所にそのまま再登場する場合や、移動中に数行編集されて再登場する場合を示し、各ファイルで移動された部分と新たに書かれた部分の割合も示します |
| (あなた) | groups json を作成します。グループ分け、順序、ファイルごとのメモを記述します |
snapshotGroups.ts | または、スナップショットのみのレビューで、その json を機械的に生成します。作成作業は不要です |
build.ts | すべての差分と各ファイルの変更前・変更後全体を収集し、削除/切り詰め/エスケープ処理を行います。変更された各画像のすべてのバージョンを取得し、GitHub のアンカーを計算して、フォントとビルド済み UI を埋め込み、完成した HTML を書き出します |
src/ | ページ本体です。tsx で書かれた Preact アプリで、配色、レイアウト、目次サイドバー、差分表示、画像比較ビューアー、メモ、チェックボックスを備えています |
buildPage.ts | src/ を、build.ts が埋め込む 1 つのスクリプトと 1 つのスタイルシートにバンドルします |
serve.ts | ビルド済みページを配信します。通常のレビュー配信方法です。ページ上の編集欄が編集可能になり、変更はワーキングツリーに保存されます |
test/e2e.mjs | ヘッドレス Chromium で、使い捨てリポジトリを使って配信中のレビューをエンドツーエンドで確認します(pnpm test:e2e)。このスキルへの変更をコミットする前に実行してください |
fixtures/commitStack.sh | コミットごとのレビューを試すための、使い捨てスタック(PR 2 件 × 各 5 コミット) |
prComments.ts | 配信中のページ → GitHub から PR のコメントを取得し、ページに表示できるよう各レビューの隣に書き出します |
awaitNotes.ts | レビュアーのメモが書き込まれるたびに通知します。ストリーミング、またはメモごとの --once に対応しており、ホストが最も適切な方法で起動できるようにします(「メモ用チャネルを開いたままにする」を参照)。これにより、レビュアーがまだ読んでいる間に対応できます |
ackNote.ts | メモへの回答を用意する前に、確認したことを伝えます。「送信済み、エージェント待ち」から「エージェントが対応中」へと、実態に即して状態を変えます |
reply.ts | メモのスレッド内で回答します。実施したこと、または回答に必要な唯一の質問を伝えます |
stackEdit.ts | 保留中の編集と、そのコミットまたは修正、および上位 PR のリベース処理を行います。Git の内部操作のみで、チェックアウトはしません |
editPlace.ts | メモへの編集先(ディスク、または保留中の編集を持つ PR)を示します。held.ts と reply.ts --edited で共有されます |
held.ts | メモに対応するエージェント向けのツールです。レビュアー自身が編集するはずの場所に、メモで求められた編集を行います。まず open(ディスク、または PR の保留中の編集を含むスクラッチワークツリー)を実行し、次に save を実行します |
restack.ts | ページで発生したコンフリクトの解決を依頼されたエージェント向けのツールです。status、resolve(スクラッチワークツリーで解決できるよう準備)、resolved(修正を記録)を実行します |
スクリプトの動作を推測し直すのではなく、スクリプト自身の --help とヘッダーコメントを読んでください。HTML を手作業で組み立てたり、差分テキストを貼り付けたりしないでください。ページを変更する必要がある場合は、src/ を変更してください。そうすれば、次のレビューにも変更が反映されます。
すべて Node 上で、このディレクトリ自体のpackage.jsonを使って実行します。レビュー対象のリポジトリのpackage.jsonではありません。そちらには Node がまったく含まれていない場合もあります。インストールではmonaco-editorも導入します。これはserve.tsがページに提供するもので、ないと起動できません。クリーンなチェックアウトで最初にビルドすると、これがインストールされます:
cd .claude/skills/guided-review && pnpm install --ignore-workspace
--ignore-workspaceとローカルのpnpm-workspace.yamlの両方により、pnpmが親ディレクトリをたどって、このスキルを配置先のリポジトリのパッケージとして扱うことを防ぎます。node_modules/はビルド状態であり、gitignore対象です。
1. 対象範囲を選ぶ:コミット、PR、またはワーキングツリー
ここでの処理は特定のリポジトリに限定されません。ワーキングディレクトリとなっている任意のリポジトリで実行できます。「PR 34をガイド付きでレビュー」「guided review #23」、PRのURL、「自分のブランチ」、「未コミットの変更を確認して」など、どのような依頼にも対応できます。
サポートするエントリーポイントは4つあります。後続のすべてのgitコマンドが変わるため、ほかの作業を始める前にユーザーがどれを意図しているか判断してください。
-
単一のコミット。 SHAがある場合(またはユーザーが「コミット<sha>」と言った場合)です。差分は
git show <sha>そのものです。 -
GitHub上のPR(この場所にリポジトリがある場合もない場合も)。 URL、または「このURLをレビューして」という依頼があれば十分です。
remotePr.tsが解決します。そのリポジトリをチェックアウトしている場合は、resolvePr.shと同様にPRの参照をそこへ取得します。それ以外の場所(別のリポジトリ、ホームディレクトリ、/tmp)では、キャッシュにbareかつbloblessでリポジトリをクローンして、そこから読み込みます。ワーキングツリーはなく、チェックアウトも行われません。レビューで開いたファイルのバージョンだけが取得されます。チェックアウト済みの状態で数百メガバイトあるリポジトリでも、この方法なら数百キロバイトで済み、2回目のレビューではクローンではなくフェッチになります。node .claude/skills/guided-review/remotePr.ts https://github.com/owner/name/pull/34
{number, title, url, base, head, repo}が出力されます。そのrepoを、serve.tsを含む以降のすべてのコマンドに--repoとして渡してください。キャッシュからクローンして配信する場合、レビューは読み取り専用です。編集を保存するチェックアウトはありませんが、メモ、チェック、差分、PR自体のコメントはすべて機能します。チェックアウトのないPRをユーザーが指定した場合は、必ずこの方法を使ってください。先にクローンするよう求めてはいけません。
- PR全体。 PR番号、ブランチ名がある場合、またはユーザーが「このPRをレビューして」「自分のブランチをレビューして」と言った場合です。通常、PRは複数のコミットで構成されます。差分はベースから分岐して以降のすべての変更です。これはGitHubの「Files changed」タブに表示されるもので、レビュー担当者が実際に必要とする内容です。PRがたまたま現在は単一のコミットであっても、必ずこのモードで扱ってください。作業中にコミットが追加されたとき、ワークフローでコミットが漏れないようにするためです。
- 未コミットの作業。 ブランチに独自のコミットがまだなく(進行中の作業を自分でレビューするときによくあります)、
git log origin/main..HEADが空で、すべての変更がgit statusにある状態です。差分は追跡対象ファイルのgit diff <base>と、未追跡ファイルそれぞれの全内容です。リンクできるGitHubページはありません。手順3を参照してください。 - コンフリクト解消。 マージ、リベース、チェリーピック、またはリバートがコンフリクト解消済みの状態で中断している場合(ステージ済みかどうかは問いません)、またはユーザーが完了済みのマージコミットを指定した場合です。
--mode conflictを指定してビルドします(完了済みの場合は--ref <merge>も指定します)。詳しくは以下の「コンフリクトレビュー」を参照してください。MERGE_HEAD/REBASE_HEAD/CHERRY_PICK_HEAD/REVERT_HEAD、またはマージコミットの親から、現在の変更、取り込み元の変更、祖先を自動的に特定します。
未コミットの作業を含むブランチも、PR全体として扱います。--mode pr --base <base> --head <branch name>を指定してビルドし、チェックアウトから配信してください。配信ページの右側はワーキングディレクトリそのものです。未コミットの編集はすべて、変更のたびに表示されます。ディスク上にしかないファイルも、ページのライブチャプターに反映されます。レビューはコミットやリベースのたびに再構築されます。
レビュー用のコミットは絶対に作成しないでください。 未コミットの作業を範囲に含めるために、
git commit-tree、git stash create、WIP コミット、「作業ツリーのスナップショット」を使用してはなりません。また、そのようなコミット(またはどのブランチも指していない SHA)を--headに渡してはなりません。作業ディレクトリのコピーを基に作成されたレビューは、コピーした時点で固定されます。それは読者のコードではないため、サーバーはチェックアウトと関連付けられません。エディターは読み取り専用になり、読者の編集内容は反映されず、再ビルドもされません。読者にはレビューが壊れているように見えます。--headには必ずブランチ名を渡してください(HEADでも構いません。ビルドでは、そのブランチ名が記録されます)。build.tsは、どのブランチにも属していないヘッドを拒否します。
該当するものを特定し、具体的な参照に解決します:
# whole-PR mode - takes a number, a branch, or a pr url; fetches what it needs
.claude/skills/guided-review/resolvePr.sh 34
git log -1 <sha> --format="%H%n%s%n%b" # single-commit mode
git status -sb && git log --oneline origin/main..HEAD # to detect working-tree mode
resolvePr.sh は {number, title, url, base, head} を出力します。ヘッドは refs/pull/<n>/head を参照するため、フォークからの PR も origin のブランチからの PR と同じように解決されます。以降のすべての処理に base/head を渡し、ファイルごとのリンクが正しいページに移動するよう、build.ts --pr には number を渡してください。
PR 全体を対象とするモードでは、後続の各ステップで「差分」とは、3 点範囲 <base>...<head>(マージベースとの差分。GitHub の表示と一致します)を意味します。git log で単一コミットの SHA が見つかった場合でも、その SHA は決して使わないでください。
SHA から始める場合は、PR がその SHA を対象としているか確認する価値があります(gh pr list --search "<sha or distinctive title words>" --state all --json number,title,url)。該当する PR があれば、コミットではなく PR をレビューしてください。レビュー担当者が求められているのは PR のレビューだからです。
コンフリクトのレビュー
解決後に提案してください。 自分(または実行したエージェント)がマージ/リベース/チェリーピック/リバートのコンフリクトを解決した直後で、その解決が単純でない場合は、--continue を実行したりコミットしたりする前に、コンフリクトレビューを提案してください。強制せず、1行で提案します。たとえば「解決で4ファイルに手を加えました。リベースを続ける前に3-wayレビューを行いますか?」のようにします。単純でない解決とは、空白のみのコンフリクト、再生成されたロックファイル、または意図が明らかな一方の変更を丸ごと採用するケース以外を指します。タイミングが重要です。操作が一時停止している間は、git がすべての側の内容を保持しており、レビューによって解決を変更できます。リベースはコンフリクトのあるコミットごとに停止し、停止のたびに個別のレビューを行います。
レビューの内容は、自分ではなく git から取得します。
.claude/skills/guided-review/changedFiles.sh conflict # paused operation
.claude/skills/guided-review/changedFiles.sh conflict <merge> # finished merge commit
git merge-tree(git 2.40 以降)で git のマージを再実行し、解決結果が git 自身の試みと異なるファイルをすべて列挙します。まず git がマージできなかったファイル(コンフリクトあり)、次に git は問題なくマージしたものの、解決担当者が変更したファイル(マージ後に編集。たとえば、名前変更後に修正された呼び出し箇所や、解決担当者が作成した新しいファイル。ステージ済みかどうかは問いません)です。git が問題なくマージし、誰も手を加えていないファイルは除外されます。これらは解決担当者の判断によるものではありません。各ファイルの分類は標準エラー出力とページ上の行のチップに表示されます。解決結果をステージしても、これらの情報は失われません。
ページでは、インライン表示や左右並列表示に加えて、3-way 表示が選べるようになり、次の順に並べて表示されます。current | resolution | incoming。resolution ペインは編集可能で、メモもここに表示されます。ancestor に専用のペインはありませんが、色分けを決めることでレビューに役立ちます。
- 解決結果内の、current からの行(青緑)またはincoming からの行(青)。
- どちらの側にもない、解決担当者が独自に加えた行(琥珀色)—すべて確認してください。
- 解決結果に含まれなかった側の行:その側独自の変更だった場合は削除(赤)となります。意図的でなければ作業の消失を意味します。もう一方の側がすでに削除していた ancestor の行だった場合は、薄い色で表示されます。
片側でファイルが移動している場合は、その側のペインに git の名前変更検出で見つけた移動元のコピーが表示されます。ペインの見出しには以前のパスが表示され、行は追加ではなく名前変更として表示されます。ancestor のコピーには、古いパスを保持している側のコピーが使われます。編集後は、git が ancestor のコピーを解決結果の名前変更として認識できない場合があるためです。
2-way 表示では current → resolution の差分が表示されます。メモでは、それぞれの判断でどちら側を採用したか、その理由を記載し、解決担当者が独自に作成した内容があれば明記してください。
実際のコンフリクトがなくても一連の流れを試すには、fixtures/conflictRepo.sh <new dir> [merge|rebase] を実行します。一時的なリポジトリを作成し、マージ中(またはリベース中)に一時停止した状態で、解決結果も書き込まれます。先頭のコメントには各ファイルに対する解決内容が記載されているため、ページに何が表示されるかを確認できます。
フォージ上、またはローカルのみのスタック
変更がスタックに属しているかどうかも確認してください。スタックとは、各ブランチがその下のブランチを基点とするブランチの連なりです。スタックを解決する方法は2つあり、レビューするのにフォージ上にある必要はありません。
node .claude/skills/guided-review/resolveStack.ts 34 > stack.json # from a PR
node .claude/skills/guided-review/resolveStack.ts --stack > stack.json # from gh stack
-
PR から(番号、ブランチ、または URL):オープン中の PR の連なりをたどります。それぞれの PR は、ひとつ下の PR のヘッドブランチを基点とします。
-
--stack:このチェックアウトのスタックにあるブランチについて、gh stack view --jsonを読み込みます。プッシュ済みかどうかは問いません。リポジトリでgh stackを使っている場合は必ず使用してください。また、まだ PR のないレイヤーを持つスタックでは常に使用してください。スタックをレイヤーごとに構築する目的の多くは、提出前にレビューすることにあります。PR ができるまで待つと、事後にレビューすることになります。PR を一部だけ提出済みのスタックもまとめて解決できます。PR があるレイヤーには、既存の番号とリンクがそのまま表示されます。{current, entries}をスタック順(trunk側の末尾が先)に出力し、スタックが見つかったかどうかをstderrに表示します。各エントリーには**key**があり、PR番号、またはPRがない場合はブランチ名をスラッグにしたものです。このキーはスタックディレクトリ内のファイル名、ページのURLでの識別子、スタックバーでのラベルに使われます。推測せず、stack.jsonからキーを読み取ってください。 エントリーが1つの場合は何も変わりません。2つ以上の場合、ページには次の2つの形式のいずれかでスタックを表示できます。 -
自分のPRだけをレビューする場合:通常どおり
build.ts --stack stack.jsonを実行します。ページ上部にバーが追加され、スタック全体の中でこのPRが強調表示され、未レビューの兄弟PRはグレー表示され、forgeへの↗リンクが付きます。(差分範囲に特別な処理は不要です。スタック途中のPRのベースはスタック親のheadそのものなので、resolvePr.shはすでにGitHubに表示されるものとまったく同じ差分を表示します。) -
スタック全体を1ページに表示する場合:スタックディレクトリを作成し、
buildStack.tsでビルドします。このディレクトリが複数のエージェント間の連携ポイントになります。PRごとに1ファイルにすることで、誰かのファイルを上書きすることがありません。<stackDir>/stack.json resolveStack.ts output <stackDir>/<key>.groups.json that layer's authored groups, one author each <stackDir>/<key>.instructions.md optional: a request for another agent to author the groups json above node .claude/skills/guided-review/buildStack.ts --dir <stackDir> --out review.html [--current <key|pr|branch>]ディレクトリは、リポジトリのどのワークツリーからでも、参加するすべてのエージェントが見つけられる場所に置いてください。慣例では
$(git rev-parse --path-format=absolute --git-common-dir)/guided-review/stack-<trunk-most pr>を使います。buildStack.tsは各PRをrefs/pull/<n>/headから取得し、ローカルのみのレイヤーはチェックアウト内のブランチから直接読み取ります(その唯一のコピーがそこにあります)。各レイヤーとその1つ下のレイヤーとの差分を取り、1つのページを出力します。作成済みのレビューはすべてバーから切り替えられ、groups jsonがない兄弟PRはグレー表示されます(指示ファイルがある場合はレビュー待ちと表示されます)。ローカルスタックにはリモートは不要です。originがない場合はローカルブランチをtrunkとして使い、何も取得しません。 参加するには、自分のPRのgroups jsonをディレクトリに書き込み、ビルドを再実行します。配信プロセスは再起動なしでHTMLを読み直し、新しいレビューのストアを作成します。開いているページには新しいレビューが反映され、ページが再読み込みされます。配信中もスタックの状態は追跡されます。
resolveStack.tsはスタックの解決方法をstack.jsonのresolvedWithに記録し、サーバーも同じ方法でスタックを再解決します。gh stackの場合は10秒ごと、PRスタックの場合は1分ごとにforgeへ問い合わせます。PRがスタックに加わる、スタックから抜ける、または順序が変わると、stack.jsonが更新され、ページが再ビルドされます。開いているページは再読み込みされます(編集内容が未保存の場合は確認を求めます)。また、サーバーの出力には、まだレビューがないまま加わったPRが表示されます。上記のとおり、そのPRのレビュー作成を申し出るか、担当エージェント向けの指示を残してください。resolvedWithが記録される前のstack.jsonは追跡されません。追跡を開始するには、もう一度解決してください。
読んでいないものは、読んでいないと伝えてください。 スタックレビューを始めるエージェントは、通常、自分のPRしか調べていません。そのことを伝え、残りを補う方法を2つ提示してください。今すぐレビュー担当エージェントを派遣して兄弟PRのgroups jsonを作成するか、またはそのPRの担当エージェント向けに<pr>.instructions.md(範囲、参照先、jsonの保存場所、再ビルド方法)を書きます。PRの作者のほうが、何も知らない読み手よりも質の高いメモを書けます。読んでいないPRについて、内容の浅いレビューを黙って作成してはいけません。
すべてのレイヤーを一度に確認できます。 作成済みレイヤーが2つ以上ある場合、buildStack.ts
はすべてのレビューも作成します。これはスタックバーの最後にボタンとして表示されます。スタックを、適用される変更全体として読み取るレビューです。trunk から最上位の作成済みレイヤーまでを基準にするため、各ファイルの差分は1つのレイヤーの一部分ではなく、累積した変更全体になります。各ファイルは、最初にそのファイルを変更した PR の下に一度だけ表示され、行にはそのファイルを変更した PR が記載されます。これは、そのファイルに触れた PR が1つだけの場合に「誰がこれを変更したのか?」への答えになります。複数の PR がそのファイルについてメモを書いている場合、行にはそれらすべてが個別のラベル付きで表示されます。ページにはブランチ名ではなく、「PR 2/3」(すべての PR にレビューがあるかどうかにかかわらず、3つのスタックのうち2番目の PR)と表示されます。変更がスタックのどこにあるかが読者に必要な情報であり、スタックがプッシュ済みかどうかにかかわらず同じ表示になります。ブランチ名または PR 番号はツールチップに表示されます。独自のチェックマークとメモを持ち、各レイヤーのものとは別に管理されるため(独立したレビューです)、スタック全体を読む進捗とレイヤーごとに読む進捗が互いに上書きされることはありません。
ローカルスタックの場合、バーには PR 番号の代わりにブランチ名が表示され、PR がないレイヤーは存在しないページへのリンクではなく、通常のテキストとして表示されます。fixtures/localStack.sh <new dir> を実行すると、試用できる3層のローカルスタックが作成されます。
提供されているページのすべての PR を編集できます。そのうち1つの変更はディスクに保存されます。 提供された --repo のチェックアウトがディスク上に持つ head ブランチのレビュー(コミットまたは作業ツリーのレビューはブランチ名を持たないため、定義上それが対象です)は、PR 全体の行と、未コミット/作業中の停止位置を、常にどおりディスク上のファイルに保存します。それ以外のすべての PR のすべての行(別のブランチのもの、または任意のコミットのもの)は、その PR の保留中の編集に保存されます(手順9の「任意の PR の編集」を参照)。サーバーが参照として保持し、読者の指示に応じてコミットバーからコミットまたは修正できます。ステージ済みの変更と、全 PR のレビュー全体を読み取る表示は、引き続き読み取り専用です。メモ、返信、チェックマークはすべてのレビューで利用できます。それぞれが固有 ID の下に独自の notes.json/ticks.json を持つため、awaitNotes.ts/reply.ts はメモが属するレビューのストアを参照します。提供されたスタックの各 PR の横にあるバーには、どれがチェックアウト済みかが表示され、各 PR のピルの末尾には … メニュー(閲覧ではなく PR 自体に対する操作用のポップオーバー)があります。その Switch working copy to here を選ぶと、提供されたチェックアウトで gh stack checkout <branch> が実行され、ページが再読み込みされます(git が拒否した場合、たとえば未コミットの変更が妨げになった場合は、トーストで通知されます)。ターミナルやエージェントなど、別の場所で行われた切り替えも検出されます。各ポーリングでチェックアウト済みのブランチが確認され(git がリベースまたはマージの途中で、切り離された状態の手順がある場合を除きます。これらは切り替えではありません)、ページが再読み込みされます。ただし、エディターが未保存の編集を保持している間は、トーストに再読み込みボタンが表示されます(checkoutSync.ts)。
2. ファイルの正確な一覧とステータスを取得する
.claude/skills/guided-review/changedFiles.sh worktree
.claude/skills/guided-review/changedFiles.sh commit <sha>
.claude/skills/guided-review/changedFiles.sh pr origin/<base> origin/<head>
.claude/skills/guided-review/changedFiles.sh conflict [<merge commit>]
STATUS<TAB>PATH を出力します。PR モードでは三点範囲を使って(マージベースとの差分を取り、GitHub の表示と一致させます。二点範囲では、フォーク後にベースブランチに取り込まれた無関係なコミットまで含まれてしまいます)、追跡されていない ?? を A に変換し、レビュー可能な差分がないバイナリ(フォント、音声、アーカイブ)を除外します。ラスター画像(png/jpg/gif/webp/avif)は一覧に残ります。build.ts はそれぞれにテキスト差分の代わりに比較ビューアーを表示します。
ファイルごとのステータス列を保持してください。これは「新規/変更済み/削除済み/名前変更済み」のバッジを決め、どのファイルを /dev/null と比較して差分を取る必要があるかを build.ts に伝えます。
変更ではなく移動したコードを見つける
ファイルが数個を超えるすべてのレビューでこれを実行してください。処理は軽量で(数百ファイルでも1秒未満)、レビュー内でコードの抽出を見つけられるのはこれだけです。
node .claude/skills/guided-review/moves.ts commit <sha>
node .claude/skills/guided-review/moves.ts pr <base> <head>
node .claude/skills/guided-review/moves.ts worktree
ファイルを分割したり、コードを新しいモジュールに移したりするリファクタリングは、git では数百行の削除と数百行の追加として表示されます。同じコードだと知らされていない読者は、すべてをもう一度読むことになります。moves.ts はそれらを対応付け、移動した各ブロックを old path:lines → new path:lines の形式で出力します。行ではなくトークンを比較するため、インデントの変更、prettier による呼び出しの折り返し、prettier が追加または削除する末尾のカンマがあっても、移動として認識されます。
各ブロックは そのまま か、移動中に変わった数行を古いものから新しいものの順に並べた 編集箇所 が付いています。編集箇所は通常、形が変わった宣言(bubbles: render( が export const bubblesAppearance = render( になるなど)か、新しい配置場所に合わせて書き換えられた呼び出しです。移動したブロックのうち、読む必要があるのはこの部分だけです。編集された行の一部が移動している場合もあります。たとえば、4 行の addItemToRoom({ … loadFloatingText(["+2", "LIVES"]) }) が this.addFloatingText(pickup, room, ["+2", "LIVES"]) に置き換わった場合、配列は移動した編集箇所です。ページではその部分が網掛けで示されます。また、各ファイルについて、ファイル内のどれだけのコードが移動したかをまとめて表示します。大半が移動したコードであるファイルについては、import を除いてほかに何が新しく追加されたかを列挙するか、「import 以外に新しいものはありません」と表示します。同じファイルの同じ hunk 内で削除された行と追加された行は、その場での編集であり、移動として扱われることはありません。--json を指定すると、同じデータが構造化出力で得られます。
以下の手順で出力を使用します。
- 読み込み(ステップ 5): 各読み込みエージェントに、担当するチャンクの
moves.ts出力を渡し、移動したブロックはすでに考慮済みであることを伝えます。ブロックの本体を読み直すのではなく、編集箇所と、それ以外に新しく追加されたものを読むよう指示してください。 - グループ化(ステップ 6): 各移動ブロックの移動元と移動先を、削除されたものの後に新しいものが続く形で、同じグループに入れます。そうすれば、抽出がひとまとまりとして読めます。
- 注記(ステップ 7): 移動されたことと移動元を記し、続けて編集箇所を挙げます。たとえば、"
bubblesAppearanceはappearanceForItem.tsのテーブルから切り出されました。名前付きエクスポートになった点を除き、変更はありません"。または、"handleItemTouchingSlidingItem.tsの本体は、現在onTouchedByです。変更されたのは実体チェックだけで、!isSolid(x)がx[itemBehaviourKey].isNonSolid(x)になりました"。読者は本体をざっと確認し、指定された行だけを詳しく読むことができます。同じ方法で多数のファイルが移動している場合(テーブルをエントリーごとに 1 ファイルへ分割した場合など)は、グループ全体に対する説明を 1 つ用意し、ファイルごとの注記は各 1 行にできます。
仕組み:削除されたコードは、2 つ以上の名前またはリテラルを含む 4 トークンのウィンドウごとに、また 20 文字以上のコメントや文字列はそれぞれ単独で、1 つのトークンストリームとしてインデックス化されます。追加されたコードの各ウィンドウを検索し、一致した箇所はトークンが一致する限り両方向に延長されます。最長の一致から順に採用し、各トークンは 1 度しか使いません。
コードが削除された箇所に 1 度だけ現れ、かつ 3 つ以上の名前またはリテラルにまたがって 24 文字以上ある場合、その一致は単独で有効です(--min-chars、--min-words)。削除箇所で一意だったため、ほかの場所から移動した可能性がなく、1 行だけでも十分です。よくあるコード(false,、return;)は、このような一致をアンカーとするブロック内でのみ有効です。ただし、偶然とは考えにくいほど長い場合(120 文字、--min-chars-common)は例外です。同じ 2 つのファイル間で、順番を保ち、4 行以内の間隔で見つかった一致は 1 つのブロックにまとめられます(--max-gap)。import は対象になりません。
「削除箇所に 1 度だけ現れる」かどうかは、古いリビジョンの全ファイルではなく、削除された行だけを対象に確認します。別の場所に変更されずに残っているコードは移動ではなくコピーなので、検索対象になりません。build.ts は同じ検出を自ら実行するため、この処理を実行するかどうかにかかわらず、ページでは移動したコードが囲まれ、移動元がラベルで示されます。この処理を実行する目的は、注記を作成することです。
移動され、かつ名前が全体を通して変更されたコードは一致しません。これは問題ありません。この場合、コードは新しいコードとしてレビューされるため、この手順がない場合と同じように扱えます。
機械的な変更を既読にするか、あらかじめ確認します
読み込みを始める前に、moves.ts を指定して実行します。
node .claude/skills/guided-review/mechanical.ts pr <base> <head> # or commit <sha> / worktree
変更が機械的なものだけであるファイルを、その理由とともに一覧表示します。
- imports: 変更された行がすべてインポートです。
- reformatted: 空白と末尾のカンマを除けば、コードが同じです。
- rename
old → new: 1つの名前が別の名前に置き換わっている以外はトークンが同じで、ブランチ内に古い名前は残っていません。 - moved: コードがそのまま移動されています。
そのファイル内のすべての変更がこれらのいずれかに該当する場合のみ、対象となります。
該当するファイルがある場合は、質問ツールを使って、最初から既読にするか読者に確認してください。件数と内容を引用します。たとえば、「258ファイル中54ファイルはインポートパスのみ変更されています(そのうち1ファイルはコードもそのまま移動されています)。あらかじめ既読にしますか?」のように尋ねます。同意が得られたら、groups json の meta に "preTickMechanical": true を設定します。
ページでは、機械的な変更があるすべてのファイルに「Mechanical」チップが表示され、オン・オフにかかわらず、ツールチップに理由が示されます。あらかじめ既読にする設定がオンの場合、まだ何も既読にしていないレビューでは、これらのファイルが既読の状態で始まります。ファイルは引き続き読み込み順に並び、折りたたまれています。また、後で変更があれば、ほかの既読ファイルと同様に未読に戻ります。これらのファイルに読み込みエージェントを割り当てないでください。機械的な変更の内容を1行で伝えるだけで十分です。
3. ファイルごとの forge へのリンク
build.ts は origin リモートからリンクを生成するため、通常は何もする必要はありません。--pr <n> を指定すると、その PR の Files-changed タブ内の各ファイルにリンクします。それ以外の場合は、commit/<sha> または compare/<base>...<head> にリンクし、#diff-<sha256hex(path)> をアンカーとして指定します。これは GitHub 独自の方式です(パスは git が出力するものと完全に同じで、a//b/ のプレフィックスは付きません。リネームでは新しいパス、削除では古いパスを使います)。確定したベース URL が出力されます。
--github <url>を指定すると、リンクの生成元を上書きできます。--github noneを指定すると、リンクを生成しません。originリモートのないリポジトリでは、ビルドが失敗するのではなく、リンクが生成されません。- GitHub 以外のリモートではリンクが生成されません。forge ごとに差分のアンカー方式が異なり、ページには差分自体が表示されるため、誤ったリンクよりリンクがないほうが適切です。
- 作業ツリーモードではリンク先が存在しません。この場合、パスはクリップボードにコピーするボタンとなり、その旨をページに表示します。
このリンクは補助的な機能です。差分自体はページ内にあるため、レビューコメントを残すための「GitHub ↗」がパスの横に小さく表示されます。
4. 差分の取得は build.ts の役割です
モードに応じて、ファイルごとに適切なコマンド(git show、3点指定の git diff、または未追跡ファイル用の --no-index /dev/null)を実行し、+/− の数を集計します。また、差分が空で返ってきたファイル(通常は groups json 内の古いパス)について警告し、ファイル数、行数の合計、完成したファイルのサイズを出力します。画像が含まれる場合、数 MB になるのは通常の範囲です。大幅に膨らむ場合は、--max-side-lines/--max-images の設定が必要な可能性があります。
ページの描画に使われるのは、各ファイルの変更前と変更後の全体です(どちらも git show <rev>:<path> で取得します。作業ツリーモードでは、変更後に作業コピーを使います)。パッチを渡すのではなく、Monaco が差分を計算して折りたたみます。--max-side-lines(デフォルトは 2000)を超える長さのファイルは埋め込まれません。そのファイルの行にはその旨が表示され、ツリー内のファイルへのリンクが示されます。
画像ファイルでは変更前後ではなく、各バージョンを取得します。 groups 内の各ラスター画像について、比較対象となる各リファレンスのバイト列を build.ts が取得します。対象となるのは常に本番環境(最新のリリースタグ)と、モードごとの以下のものです。PR ではベースとブランチ、コミットでは親とコミット、作業ツリーではhead/stage/workingです。バイト列が同一のバージョンを重複排除し、画像ごとに1つの不活性な json ブロックとして埋め込みます(そのため、ペイロード自体に画像データが含まれることはありません)。これにより、ページのビューアーでは、レビューの対象としてビルドされた組み合わせだけでなく、任意の2つのバージョンを比較できます。--max-images(デフォルトは 100)で、埋め込む画像の数を制限します。上限を超えた画像の行も表示されますが、除外されたことが示されます。比較はすべてページ内で行われるため、画像処理ツールへの依存はありません。
生成ファイルとロックファイルは、groups json から完全に除外するか、本文を読まずに行と注記だけを追加するのがおすすめです。
5. 差分を読む — バックグラウンドエージェントに並行して委任する
数ファイルを超える変更では、差分全体を自分で読むと、統合に使うべきコンテキストを消費してしまいます。ファイル一覧を、関連性のある2〜4個のまとまりに分割します(例:「コアアルゴリズム/データ構造のファイル」と「それを利用するファイルすべて」。適当に分けるのではなく、読む順序をまとめるときと同じ基準で分割してください)。各まとまりにつき、バックグラウンドの Agent を1つ(汎用、run_in_background: true)割り当て、1つのメッセージで指示して同時に実行します。
画像ファイルは、どのエージェントのまとまりにも含めないでください。 エージェントが画像を読むのはコストがかかり、ビューアーで差分をひと目で確認できるレビュアーにとっても価値がありません。また、ページ自体が画像の変更ピクセル割合を計算します。画像には該当箇所のまとまりで1行を割り当て、変更した理由を必要に応じて簡潔に記すだけにしてください。画像の見た目を説明する必要はありません。
変更内容がすでにコンテキストにある場合(例:このセッション内で自分が変更を書いた場合)は、この手順を省略します。すでに把握している内容を再度調べても、得られるものよりコストのほうが大きくなります。
各エージェントへのプロンプトは自己完結している必要があります(エージェントはこの会話の記憶を持ちません)。また、次の内容を含めてください。
- リポジトリのパス。単一コミットモードではコミット SHA と1行のメッセージ、PR 全体モードでは PR 番号/タイトルと正確な
origin/<base>...origin/<head>の範囲、作業ツリーモードでは変更が未コミットであることと差分を確認する方法。いずれのモードでも、読み取り専用の分析であり、編集は行わないこと - アーキテクチャの背景情報(関連する
CLAUDE.mdのセクションを引用)。エージェントがシステムを一から調べ直さずに済むようにします - まとまりに含まれるファイルの正確な一覧と、手順1のモードに合った読み取りコマンド。差分のハンクだけでは理解が難しい場合、内容の多いファイルを3〜5個選んで、現在の全内容も
Readするよう指示します - 報告内容:(a)そのまとまり全体の変更内容を簡潔な段落で説明する。(b)各ファイルについて、何が変わったか、理由、依存する他のファイル(まとまり内または他のエージェントのまとまり内のファイル名を明記)を含む2〜4文の説明。(c)まとまり独自の推奨読み順。名前を付けたグループにまとめ、各グループをその位置に置く理由を1文で説明します。各テストファイルは、テスト対象のコードと同じグループに入れ、コードの直後に配置します。テストだけのグループは作りません
- そのまとまりの
moves.tsの出力。移動されたブロックは、その編集箇所を除いて既読として扱い、移動されたファイルについては本文を一から説明するのではなく、「X から移動、さらに Y を変更」と報告するよう指示します - ファイルごとの説明を省略して短くまとめないよう明確に指示します。報告はユーザーに直接見せるのではなく、あなたが統合に使うものなので、簡潔さよりも網羅性を優先します
6. マクロな読み順を一つにまとめる
両方、またはすべてのエージェントから報告があったら、各チャンクの読み順を統合し、単一のシーケンスにまとめます。チャンクをただ連結するのではなく、各チャンクの内容をほかのチャンクと比べて実際にどこに配置すべきか判断します(例:共有される低レベルのプリミティブを最初に置き、次に中核となる仕組み、それをシステムの残りの部分に接続する統合箇所、最後に周辺部分を置く)。明らかに関連のある作業が文書全体に分散してしまうような場合は、厳密な依存関係順よりもテーマごとのグループ化を優先します。
テストは、それがテストするコードと一緒に配置し、独立した章にはしません。 テストは、隣にあるコードが何をする想定なのか、どのケースで問題が起きたのかを示す証拠として読めます。最後に「テスト」章を置くと、読者がそこにたどり着く頃には、確認対象のコードが何章も前にあり、テストと切り離されてしまいます。各テストファイルは、それが対象とするコードの章に、そのコードの直後に配置します。複数の章にまたがるテストは、最も直接的に検証するコードの章に配置します。テストヘルパーやフィクスチャは、それらを使用するテストと一緒に配置します。
各グループ内では、読者が実際に開いて読み進める順にファイルを並べます。削除と追加のペアは対比しながら読めるように並べ、基盤となる型はそれを利用するコードより前に置き、各テストはテスト対象のコードの直後に置きます。インポートパスだけを変更する些細な名前変更はまとめて流し読みできるようにし、実質的なロジック変更と同等の重みを与えないようにします。
7. groups json を作成してビルドする
json ファイルを一つ作成します — 出力はそれだけです。
{
"meta": {
"title": "the one-line thesis of the change",
"headerTitle": "short name for the sticky header",
"eyebrow": "reading order",
"lede": "a paragraph on what the change is and why this order; html allowed",
"facts": ["branch <code>x</code> vs <code>origin/main</code>", "…"],
"footer": "html; eg where the ticks live, what was left out"
},
"groups": [
{
"title": "group name",
"blurb": "one sentence on why this group sits here",
"items": [{ "path": "src/…", "status": "M", "note": "2-4 sentences" }]
// blurb and note both take html too - <code> especially
}
]
}
PR をコミットごとに作成する
PR は、スタックが PR の連なりであるのと同じように、コミットの連なりです。コミットは変更がどのように書かれたかを表し、それぞれに独自の理由がある一つの段階です。個別に読む価値のあるコミットが複数ある PR では、ひとまとめにするのではなく、コミットごとに作成します。groups を commits に置き換えてください。
{
"meta": { "title": "…" },
"commits": [
{ "ref": "<sha>", "groups": [ { "title": "…", "items": [ { "path": "…", "status": "M", "note": "…" } ] } ] },
{ "ref": "<sha>", "groups": [ … ] }
]
}
refには、git が解決できるものなら何でも指定できます。ビルドでは、その短縮 sha と件名が読み込まれます。- 各コミットの章では、そのコミット単独を読みます。そこでのファイルの差分は、PR 全体の範囲ではなく、
git show <sha> -- <path>です。同じファイルが 3 つのコミットで変更されていれば、コミットごとに、そのコミットで変更された内容を 3 回読みます。それが目的です。メモには、そのコミットについて記してください。ファイルの要約ではなく、この段階で何をしたのか、そしてその理由です。 groupsとcommitsはいずれか一方を指定します。ビルドでは両方の指定が拒否されます。"ref": "uncommitted"は、コミットではない唯一の ref です。これは、ワーキングツリーにあるものの、まだコミットされていない内容をHEADと比較して読み込みます(git が一度も認識していないファイルの場合、比較対象はありません)。最後に配置してください。ここには、ブランチで最後にコミットしてからの変更が含まれます。行にはディスク上のファイルが表示されるため、コミットの行とは異なり、編集可能で、ディスク上の内容に追従します。バー内のそれ以外の項目はすべて履歴です。
ページにはスタックバーの下に コミットバー が追加されます。PR 内の各コミットに加えて all があり、最初に表示されます。レビュー対象は引き続き PR 全体です。すべてを読む場合は、各コミットの章が、そのコミット名を見出しにして配置されます。すべての PR のレビューも有効にすると、最上位の見出しが PR になり、その下にコミットが並びます。
チェックマークはファイルだけでなく、コミット内のファイルに対して付けます。すべてのコミットをまとめて読む場合、ファイルにチェックを付けると、そのファイルが含まれるすべてのコミット(およびすべての PR のレビューでは、PR 間でも)に反映されます。そのため、読み進めるにつれて未読項目が減っていきます。一つのコミットを読む場合、チェックマークはそのコミットだけに付きます。コミットの行はディスクに保存されません。ディスク上には、そのコミット時点のファイルが存在しないためです。ただし編集は可能です。編集内容はそのコミットに紐づけて保持され、コミットバーからコミットまたはそのコミットへの修正として取り込めます(「任意の PR の編集」を参照)。チェックマークは、コミットを作り直した後も、そのコミットに追従します。メモはファイルと行に紐づけられ、そのファイルに触れるすべてのコミットで共有されます。
fixtures/commitStack.sh <new dir> を実行すると、試用できる 2 つの PR からなる 5 コミットのスタックが作成されます。
変更されたファイルは、それぞれ必ず 1 つのグループに入れてください。2 つのテーマに関係するファイルでも、最も読みやすいグループ 1 つに配置し、もう一方のグループの説明文やメモからそのファイルを参照できます。build.ts は、同じパスを複数回記載した groups json を拒否し、そのパスが含まれるグループ名を示します。
ファイル数と行数は自動的に計算されます。facts に記載しないでください。その後:
node .claude/skills/guided-review/build.ts \
--groups <scratchpad>/review.json \
--out <scratchpad>/review.html \
--mode worktree
# a commit: --mode commit --ref <sha>
# a pr: --mode pr --base <base> --head <head> --pr <n> (refs from resolvePr.sh)
# a conflict resolution: --mode conflict (--ref <merge> for a finished merge)
スナップショットのみのレビューは、すべてスクリプトで処理できます
変更全体ではなく「変更されたスクリーンショットのベースラインをレビューする」という依頼の場合、作成作業はすべて省略してください。snapshotGroups.ts が groups json を自動で作成します(groups はスナップショットディレクトリをアルファベット順に並べたもので、メモはありません。各画像の変更率はページが計算します)。
node .claude/skills/guided-review/snapshotGroups.ts --mode worktree --out <scratchpad>/groups.json
# or --mode pr --base <base> --head <head>, --mode commit --ref <sha>
# --glob 'e2e/*-snapshots' bounds what counts as a baseline (that is its default)
node .claude/skills/guided-review/build.ts --groups <scratchpad>/groups.json \
--out <scratchpad>/snapshots.html --mode <same mode and refs>
あとは通常どおり配信します。これは「ベースラインを再生成しました。差分は正しいですか?」という確認にそのまま使え、スマートフォンでの閲覧にも対応します。
その後、配信して(手順 8)、URL を共有してください。ビルドし直して更新すれば、繰り返し確認できます。build.ts は同じ場所に上書きするため、サーバーを再起動しなくても、最新のビルドが配信され続けます。
ノートの作成
ファイルごとのノートとグループの説明は、この変更を今読んでいる1人の読者に向けたものです。前のバージョンを見たことがなく、何年も後に読む人にも意味が通じなければならないコードコメントとは異なります。そのため、ここでは逆のルールが適用されます。レビュー用ノートでは、過去の経緯に触れてもかまいません。「以前はルームをもう一度読み込んでいました」「単なる2つの参照でした」「グループ4で続く修正」は、レビューではいずれも適切ですが、ソースコードでは不適切です。コードが現在どう動くかだけでなく、何が変わり、なぜ変わったのかを書いてください。コードはノートの隣に表示されるので、同じ内容を繰り返すと読者の注意を無駄にします。
画像の行には、ほとんどの場合、作成者によるノートは必要ありません。ページが各画像の下に機械的にWxH — N% of pixels differという行を追加します。また、変更点は文章よりビューアーのほうがよく示せます。変更の理由が周囲のグループから明らかでない場合に限って、ノートを書いてください。
8. 提供する — アクセス可能ならサーバーで提供し、不可能ならファイルを送る
まず、自分がどちらのモードにいるかを、実行環境について自分で判断してください。この判断に使う環境変数はないので、セッションについて把握している情報から推測してください。
- レビュアーが、あなたの開いた
127.0.0.1のポートにアクセスできる場合 — 通常のローカルターミナルセッション、レビュアー自身のマシンへのSSHセッションなど、「localhost:<port>にブラウザーでアクセスする」ことが実際に可能な環境です。ここで説明するとおりにサーバーで提供してください。これは最も機能が充実した方法です。編集可能な差分は作業ツリーに保存され、記入されたノートを反映するライブノートチャンネルも利用できます。 - アクセスできない場合 — クラウドサンドボックス、CIジョブ、レビュアーのブラウザーへのポートフォワーディングがないリモートマシン(そこで提供したURLは最初からアクセスできません)です。サーバーで提供しないでください。以下の非アクセス可能モードに切り替えてください。ビルド済みの
review.htmlは、すでに単一ファイルとしてwindow.__reviewServerを読み取り、それに応じて動作するため、そのまま渡してください。
どちらの環境か本当に判断できない場合は、推測せずレビュアーに確認してください。アクセスできない場所でサーバーを提供すると使えないリンクを渡すことになり、サーバーで提供できるのにファイルを送ると、編集可能な差分とライブノートを理由なく失うことになります。
ここから「Publishing」までは、アクセス可能な場合の手順です。非アクセス可能モードではここを飛ばして、「### 非アクセス可能モード」に進んでください。
# reachable mode only:
node .claude/skills/guided-review/serve.ts --html <scratchpad>/review.html --repo . --open
--repoには、レビュー対象のヘッドをチェックアウトしたリポジトリを指定する必要があります。prモードでは、現在実行している場所ではなく、ブランチ固有のワークツリーを指定してください。保存やノートに基づく編集はここに反映され、ライブファイルのポーリングでは、開いているエディターの内容とこのリポジトリのディスク上の内容が比較されます。スタックページでは、レビュー対象のブランチのうち、ヘッドブランチをチェックアウトしているものが編集可能になります。サーバーがこれを自動で判定し、その他のブランチはノート機能付きの読み取り専用として安全に動作します。
レビューはコミットの組み合わせではなく、ブランチを追跡します。
-
編集:
build.tsは、HEADまたは SHA が指定された場合でも、ヘッドをブランチ名として記録し、サーバーはその名前でチェックアウトを照合します。そのため、編集内容は新しいコミット、リベース、修正コミットの後も保持されます。単一のブランチも指していないためにヘッドが単独の SHA のままである場合は、その SHA を含むチェックアウトと照合されます。 -
ブランチが動くと再ビルド: コミット、リベース、修正コミット、またはベースのマージによって、ヘッドコミットまたはマージベースが変わります。サーバーは次のポーリング時にこれを検知し、リベースやマージが進行中の場合は完了を待ってから、バックグラウンドで
build.tsを再実行します。最初のビルドでページに埋め込まれたレシピ(#recipe:ヘッドをブランチ名として指定した引数、作成者が用意した groups json、除外したファイル)を使い、レビューの ID を維持するため、メモやチェックマークも引き継がれます。再ビルドした HTML が古いものと置き換わるため、ページを再読み込みした場合やサーバーを再起動した場合にも反映されます。ページは次のポーリング時に新しいビルドを取り込みます。サイド、件数、移動したコードのボックス、リネーム、画像、リンクはすべて、新規ビルドとまったく同じになります。メモ、チェックマーク、開いている差分、スクロール位置、未保存の編集内容はそのまま残ります。再ビルドはビルドと同じコードを使うため、再ビルドで得られる内容が古いまま残ることはありません。これは単一の PR または作業ツリーのレビューにのみ適用され、スタックやコミットごとの閲覧には適用されません。 -
ファイル一覧: サーバーは、現在の対象範囲と読み順を比較します。ビルド以降に対象範囲に入ったファイル(新規ファイル、新たに変更されたファイル、またはリネームされたファイル)は、ライブ差分付きで最終章「このレビューの作成後に変更」に行として追加されます。サーバーはファイルごとに
new in scope: <path> - not in the reading orderと出力します。これが表示された場合は、メモとともにそのファイルを groups json に追加して再ビルドしてください。ビルド時に対象範囲に含まれていたものの読み順から除外されたファイル(ロックファイル、生成コード)はleftOutとして記録され、表示対象にはなりません。変更の対象ではなくなった一覧内のファイル(変更が取り消された、追加後に削除された、別の名前にリネームされたファイル)は表示から外れ、変更が戻れば元の位置に再表示されます。サーバーはこのことも出力します。一覧内のすべてのファイルは現在の状態で表示されるため、変更後に削除されたファイルは D と表示されます。行は位置ではなくパスをキーにするため、行が表示から外れたり再び表示されたりしても、開いている差分や折りたたみ状態は移動しません。 -
コミットバーもブランチを追跡します。 ブランチを追跡する配信済みレビューには、コミットが 1 つだけの場合も、常にコミットバーがあります。「all」(作成時点のレビュー)、ベース以降のブランチの各コミット、続いて staged(
HEAD→ インデックス)と working(インデックス → ディスク)が並び、それぞれ内容がある場合にのみ表示されます。サーバーは毎回のポーリング時にこれらを一覧表示するため(/stateのstops)、git add、編集、コミットがすぐに反映されます。stop には、変更されたファイルに絞った読み順と、残りのファイルを含む「読み順に含まれないファイル」章が表示されます。各 stop のサイドは、最初に読むときと、そのファイルが変更されたときにサーバーから取得されます(/stop-sides)。working の行は編集でき、ディスクに保存されます。staged の行は読み取り専用で、代わりにどこを編集すればよいかが示されます。コミットの行はそのコミットに対する編集内容を保持します(「任意の PR を編集する」を参照)。stop 上で再読み込みすると、その stop に戻ります。コミットごとに作成されたレビューは、代わりに独自のバーを保持します。 -
読んだ後に変更された場合: 差分が変わったチェック済みファイル(ディスク上の編集、リベースなど)はチェックが外れて展開され、再びチェックされるまで「読んだ後に変更」と表示されます。行数は、差分が開いているかどうかにかかわらず、変更のたびに更新されます。
-
再ビルドの間は追跡されないもの: ディスク上の未コミットの編集は、変更後のサイドと件数にライブで反映されますが、移動したコードのボックスと画像は次の再ビルドまで更新されません。再ビルドはコミットまたはリベースによってのみ実行されます。コミットレビューまたはコミットごとのレビューは履歴であり、コミット一覧と行は固定されています。
-
エディターの保存先: エディターの下に表示されるヒントに、ディスク、PR に保持されている編集内容(「このコミットに対する編集内容を保持しています…」)、または読み取り専用の理由(ステージ済みの変更、すべての PR のレビューでは全体を読み込む)が示されます。
http://127.0.0.1:<port>/が出力されます。これをレビュー用として渡してください。 セッションがブロックされないよう、バックグラウンドで実行し、使用するポートを伝えてください。 サーバーを立ち上げると、ファイル(または公開済みのページ)にはない次の3つの機能が使えます。 -
差分を編集できます。 各 Monaco エディターの変更後の側が書き込み可能になり、「保存」と「元に戻す」が追加されます。保存時には、ページのビルド元となった sha256 が毎回送信されます。その後ディスク上でファイルが変更されていた場合は、409 と「ディスク上で変更されています — 再読み込みしてください」が返され、変更が黙って上書きされることはありません。ローカルホスト限定で、トークン認証が必要です。また、パスはリポジトリ内に制限されます。「元に戻す」を実行すると、レビューのビルド元の内容がエディター内だけで復元され、変更ありの状態が維持されます。意図的に「保存」しない限りディスクには反映されないため、クリックを誤っても問題ありません。
-
メモが保持されます。
alt-N(またはエディターのコンテキストメニュー)で、現在の行にメモを追加できます。サーバー経由の場合、メモは<store>/notes.jsonと Markdown 形式の<store>/notes.mdに保存されるため、再ビルド後も残ります。「メモをコピー」を使うと、PR コメントに貼り付けるためにメモをコピーできます。 -
チェック状況はブラウザーではなくファイルに保存されます。 読んだ箇所は
<store>/ticks.jsonに保存されるため、別のスクラッチパッドや別のポートで再ビルドしても引き継がれます。また、同じレビューを開いた2つのタブ間でも共有されます。さらに、確認がどこまで進んだかを、尋ねることなく確認できます(cat <store>/ticks.json)。サーバーを使わない場合、チェック状況はレビュー ID をキーとして localStorage に保存されます。
<store> は <the html's directory>/<review id>/ で、ticks.json、notes.json、notes.md、token の4つすべてが保存されます。serve.ts は起動時にこのパスを出力し、build.ts は生成した ID を出力します。ID はリポジトリとレビュー対象(コミット、PR 番号、または作業ツリーのブランチ)から決まり、HTML の出力先には左右されません。そのため、同じレビューを翌日に再ビルドしても続きから再開できます。同じスコープの2つのレビューを分けておく必要がある場合は、--id で ID を上書きできます。
PR にすでにあるコメント
ここでレビューする PR には、GitHub にも同僚や自動レビュアーのコメントが付いていることがよくあります。それらをページに取り込みます。
node .claude/skills/guided-review/prComments.ts --html <scratchpad>/review.html --repo . --watch 60
取り込まれた各レビューは、対応する PR(スタックされた PR の場合はその番号、それ以外の場合はヘッドブランチが属する PR)を見つけ、そのコメントを <store>/github.json に書き込みます。ページの既存のポーリングが数秒以内にこれを取得します。--watch は読み取りを続けるため、レビューが開いている間に付いたコメントも表示されます。serve.ts の横でバックグラウンド実行してください。背後に PR のないレビュー(コミット、作業ツリー、まだプッシュされていないスタックのレイヤー)はスキップされ、ページには表示されません。
ページでは、次のように表示されます。
- インラインのレビュースレッドは、差分内の該当行に表示されます。 GitHub の印が付き、各コメントの投稿者と経過時間が表示され、フォージ上のスレッドへのリンクも付きます。
- 表示場所がないスレッド(変更によって削除された行に関するものや、ブランチの更新後に残されたもの。フォージでは outdated と呼ばれます)は、ファイルの行の下に理由とともに表示されます。
- 解決済みのスレッドは一行に折りたたまれ、クリックすると再び開きます。 非表示にはなりません。スレッドへの返信によってフォージ上で解決済みになることがあり(GitHub は自分自身のレビュアーのスレッドに対してこれを行います)、返信した瞬間にスレッドが消えてしまうと、返信が届かなかったように見えるためです。ここで返信したスレッドは、セッションが終わるまで開いたままになります。
- 行のパスには、ファイル内の未解決スレッド数が表示されます。
--prの色で、ローカルメモの数の横に表示されます。 - PR 自体の会話と各レビューの判定は、読み取り順の上にある折りたたみ可能な領域に表示されます。
返信すると GitHub に投稿されます。 投稿者は gh にサインインしているユーザーです。スレッド内の入力欄から、または PR 自体の領域から返信できます。投稿されるのはページ上で明示的にクリックした場合だけです。サーバーは投稿時に PR を再読み込みするため、スレッドには返信がすぐに表示されます。エージェントや watch によって投稿されることも、ページを開いただけで投稿されることもありません。
ページ上のコメントには 2 種類あることをレビュアーに伝えてください。メモはあなたとエージェント向けのもので、ローカルに保存されます。GitHub への返信は PR に投稿され、チームのほかのメンバーにも表示されます。
メモが書かれたらすぐに対応する
メモはアーカイブ項目ではなく、依頼です。頼まれるまで待ったり、レビューの最後までため込んだりしないでください。レビュアーはその場にいます。レビュアーがそのファイルを見ている間に対応するほうが、1 時間後に対応するより 10 倍価値があります。
どう対応するかを考える前に、まず受け取ったことを伝えてください。
node .claude/skills/guided-review/ackNote.ts --notes <store>/notes.json \
--path src/ui/useStableValue.ts --line 7
これが反映されると、ページの「送信済み。エージェントの応答待ち」が「エージェントが対応中」に変わります。何かが実際に応答していてもいなくても同じように動くアニメーションではなく、実態に即した表示になります。このあとすぐに reply.ts で返答する場合に限り、省略してください。実際の返信がすでに受領を示すため、先に受領通知を送ると無駄な往復になるだけです。
メモを受け取ったら、次のどちらかになります。
-
依頼が明確な場合(「これを src/util/preact に移動してください」「これは const にするべきです」「このファイルを変更から外してください」など)— 今すぐ編集し、レビュアーがその行から自分で編集する場合と同じ場所に反映してから、スレッドで伝えてください。メモには、レビュアーが書いたときに見ていた内容が記録されています。PR から提供されたチェックアウトがチェックアウト済みであれば、その内容を最後まで読み込み、レビュアーの編集はディスクに書き込まれます。それ以外の場所(別の PR や、いずれかのコミット)からの場合は、その PR の保留中の編集に書き込まれ、レビュアーがコミットまたは修正できます。あなたの編集も同じように反映されるので、レビュアーが確認する場所に反映され、何をコミットするかは引き続きレビュアーが選べます。どちらに反映するかは
held.tsが判定します。node .claude/skills/guided-review/held.ts open --html <review.html> --repo <checkout> \ --notes <store>/notes.json --path src/ui/useStableValue.ts --line 7行の変更内容をディスクに保存する場合は、その旨が表示されます。その場合は、通常どおりチェックアウト内のファイルを編集してください。または、PR のそのコミット時点の状態を保持するスクラッチ用ワークツリーを作成し、すでに保持されている変更があれば適用します。そこでいつものツールを使って編集し、
held.ts save --dir <that worktree>を実行すると、ページから保存した場合とまったく同じようにすべての変更が保持中の編集に加わり、ワークツリーが削除されます。ブランチのチェックアウト、コミット、または自分での amend は絶対にしないでください。保持中の変更をコミットするかどうかは、コミットバーから操作するレビュー担当者が決めます。その PR の別のコミットに対する編集がすでに保持されている場合、openは拒否されます(PR ごとに置き場所は 1 つです)。その場合は、先にコミットまたは amend してもらえるようスレッドで依頼してください。その後、--editedを付けて編集したファイルをすべて指定し(ファイルごとに 1 回)、返信します。返信の最後にファイルの一覧がリンクとして並び、各リンクからページ内の編集が反映された箇所(ディスク上、または保持中の編集の停止位置)を確認できます。node .claude/skills/guided-review/reply.ts --notes <store>/notes.json \ --path src/ui/useStableValue.ts --line 7 \ --html <review.html> --repo <checkout> \ --edited src/util/preact/useStableValue.ts --edited src/ui/Panel.tsx \ --text "moved to \`src/util/preact\`, import sites updated"--textはマークダウンです。code、太字、リスト、リンク、フェンス付きコードブロックが使えます。役立つ場合に使いましょう。たとえば、シンボル名をバッククォートで囲んだり、2 行のスニペットをフェンス付きコードブロックで示したりできます。差分内に表示されるため、短くまとめてください。複数行のテキストは$'...'またはヒアドキュメントで渡してください。ページには数秒以内に通知が届きます。ファイルの差分がディスクから再読み込みされ、
+N −Mが更新され、何が起きたかを示すトーストが表示され、あなたのメッセージがレビュー担当者自身のメモ欄に表示されます。すでに依頼されていることを再度許可を求めたり、レビューが終わるまで待ったりしないでください。 -
議論が必要な場合 —
--askingを付けてスレッド内で質問し、作業を進めるために必要な 1 つの質問に絞ってください。レビュー担当者は同じ入力欄に回答を入力し、その回答はawaitNotes.tsを通じてあなたに届きます。編集するのはその後にしてください。ターミナルで質問してレビューを中断しないでください。相手はチャットではなくページを見ています。
メモが届いたのと同じ場所だけで返信してください。ページから届いたメモはチャットからではありません。スレッド内で reply.ts を使って返信し、チャットの会話には手を加えないでください。同じことをスレッドとチャットの両方で伝えるのは、念入りなのではなく、混乱を招くだけです。
何をしたかは 1 行で伝えてください。メモ欄は小さく、相手はレビューの途中です。
メモは 3 つの方法で届きます。
notes.jsonの監視:ホスト環境に最適な方法でawaitNotes.tsを実行します。詳しくは、下の「メモ用チャネルを開いておく」を参照してください。レビュー中にメモを受け取る方法はこれだけなので、これが最も重要です。- すべてのメモは、サーバー自体の出力にもリアルタイムで表示されます:
note <path>:<line> <text>。 - ヘッダーの「エージェントにメモを送信」を使うと、
=== N review note(s) handed off ===マーカーで囲まれたメモ一式が出力され、<store>/notes.mdが書き換えられます。これはレビュー担当者からの「終わりました。あなたに引き継ぎます」という合図です。メモが届いていることを伝えてください。
メモの意味があいまいな場合は、レビューを止めず、そのメモについて質問してください。話している間もページは使えます。
ページで発生した競合:解決を申し出る
保持中の編集をコミットすると、PR の後続コミットが再適用され、上位の PR をリベースすると、その PR の上にある PR が再適用されます。再適用で競合が発生すると、何も変更されず、その競合が起きた PR のレビュー内で、最初に競合したファイルに関するメモが届きます。どのコミットを、何に対して適用し、どのファイルで競合したかが記載されます。返信で解決を申し出て、レビュー担当者が了承した場合(または、こうした競合はそのまま対処するよう依頼されている場合)は、次のようにします。
node .claude/skills/guided-review/restack.ts resolve --html <review.html> --repo <dir> --dir <scratch>
# fix the conflict markers in the files it lists, in <scratch> - each side is labelled
node .claude/skills/guided-review/restack.ts resolved --html <review.html> --repo <dir> --dir <scratch>
resolved は、修正内容を特定のコミットから特定の親に適用する記録を残します(参照先なので、その後も保持されます)。また、ワークツリーを削除します。ブランチには触れません。スレッドでレビュー担当者に伝え、同じボタンをもう一度押せば今度は処理が進むことを知らせてください。restack.ts status では、保持中のもの、古くなったもの、最後に発生した競合を確認できます。
メモ用チャネルを開いておく
読まれないメモは、メモがないよりも悪いものです。レビュー担当者は、自分の意見が聞き入れられたと思ってしまいます。レビューの間、メモが書かれるたびに監視機能であなたを呼び戻す必要があります。その方法はホストが何に対応しているかによって異なり、ホストごとの差も大きくあります。awaitNotes.ts には、これらに対応する2つのモードがあります。
# streams each new note as it's written, and never exits on its own
node .claude/skills/guided-review/awaitNotes.ts --notes <store>/notes.json
# waits for new notes, prints them and exits - re-run it after every one
node .claude/skills/guided-review/awaitNotes.ts --notes <store>/notes.json --once [--timeout <seconds>]
--once を使えば、実行の間に情報が失われることはありません。報告済みの内容は <store>/awaited.json に保存されるため、作業中に書かれたメモも次の --once では新しいものとして扱われ、すぐに返されます。いつ再起動しても安全です。レビュー中にこのファイルを削除したり、手動で編集したりしないでください。次の待機で、すでに確認済みの内容まで既読として扱われてしまいます。
ホストがどのカテゴリに該当するかを確認し、対応している方法のうち最も信頼性の高いものを使ってください。 以下に挙げるホストの情報は、2026年9月時点のドキュメントと変更履歴に基づいています。この情報はすぐに古くなります。これらのツールには数週間ごとに機能が追加されるためです。現在、お使いのホストに、ここで示すカテゴリよりも信頼性の高い呼び出し方法がある場合は(以前は終了通知しかなかったのにストリーミング監視が使えるようになった、通知がなかったのに終了通知が使えるようになった、組み込みのファイル監視機能がある、プッシュチャネルがあるなど)、そちらを使ってください。 同様に、ここに記載された方法がセッションで機能しない場合(ツールが見つからない、または無効になっている、呼び出しが一度も届かないなど)は、このリストを信頼せず、次の下位カテゴリに切り替えてください。
1. 各行が届くたびに呼び出されるストリーミング監視。 Claude Code の対話型 CLI と、その Monitor ツールです。serve.ts の直後に Monitor でストリーミングモードを実行し、各メモがイベントとして届くたびに対応してください。Monitor の監視は期限に達すると終了することがあります。その場合はすぐに再開してください。awaited.json から再開されるため、何も見逃しません。Monitor はどこでも使えるわけではありません(たとえば、一部のプロバイダーや、必須ではないトラフィックが無効になっている環境など)。使えない場合は、カテゴリ2を使ってください。
2. 終了時にあなたを呼び戻すバックグラウンドコマンド。 Claude Code(run_in_background)、Cursor(IDE エージェントと cursor-agent)、GitHub Copilot CLI、VS Code の GitHub Copilot エージェントモード(バックグラウンドターミナルの通知。VS Code 1.116 以降はデフォルトで有効)、opencode v2(background: true を指定したシェル呼び出し)、および Gemini CLI。ただし、Gemini CLI は tools.shell.backgroundCompletionBehavior を "inject" に設定した場合のみです(デフォルトの silent では呼び戻されません)。
--onceをバックグラウンドコマンドとして実行します。メモが届くと終了し、その終了によってあなたが呼び戻されます。メモに対応し(ackNote.ts、続いて編集とreply.ts)、その後すぐにバックグラウンドの--onceを再起動してください。- こうした呼び出しは完全には信頼できません。ホストによっては、まれに通知が届かなかったり、遅れたりすることがあります。VS Code の通知は、チャットがアイドル状態のときに新しいターンを開始せず、次のターンまで届かないことがあります。そのため、各応答の最後にも
--onceがまだ待機中か確認し、待機中でなければ再起動してください。awaited.jsonのおかげで、これによる負担はありません。 - ここではストリーミングモードは役に立ちません。終了しないため、あなたが呼び戻されることもありません。
3. ブロッキングコマンドのみ。 OpenAI Codex(CLI、IDE 拡張機能、アプリ)、Zed のエージェント、Goose、Cline、Windsurf/Cascade、Amp、およびデフォルト設定の Gemini CLI が該当します。ターンの間にあなたを呼び戻すものはありません。待機しているコマンドの出力だけを確認できます。
- フォアグラウンドで
--once --timeout <seconds>を実行し、タイムアウトはホストがコマンドを強制終了する時間より短く設定します。制限の例:Gemini CLI はコマンドを 出力がない状態で 300 s 経過すると強制終了します(--onceは待機中に何も出力しません)。そのため、Gemini CLI では--timeout 240以下を指定してください。opencode v1 のデフォルトは 2 分です。Codex は実行中のコマンドを一度に最大 5 分間ポーリングします。制限が不明な場合は、240 が安全な値です。 - 「新しいメモはありません」と表示されたら、もう一度実行します。メモが届いたら内容に対応し、もう一度実行します。これを行うと、レビューが終わるまでターンが継続するため、その間はチャットできません。最初にそのことを伝え、レビュアーが終了を伝えたとき、または会話するために割り込んできたときに停止します。
- メモに関係のないチャットへの返信も含め、すべての応答で、最後に次の待機を開始してください。 待機を開始せずに応答を終えると、レビューが中断されます。
- Codex では、代わりに Stop フックから待機を実行し、メモを理由としてブロックすることもできます。メモが届くとセッションが再開されます。レビュアーが希望する場合にのみ設定してください。Codex の設定が変更されるためです。
4. ターン間のやり取りがない場合。 GitHub Copilot のコーディングエージェントや Codex cloud などのクラウドエージェント、および自動でコマンドを実行しない Aider が該当します。クラウドエージェントはレビュアーのマシンで動作できないため、到達不能モード(以下を参照)を使用してください。それ以外の場合、メモが届くのはレビュアーが「Send notes to agent」とチャットメッセージで渡したとき、または notes.md を貼り付けたときだけです。返信が来るのを待たせないよう、最初にはっきり伝えてください。
どのカテゴリでも、次の点に従ってください。
serve.tsは専用のバックグラウンドターミナルで実行してください。待機に使うターミナルでは実行しないでください。待機によってブロックされたり、待機と一緒に終了したりするのを防ぐためです。- メモを受け取る方法を、レビュアーに一行で伝えてください。リアルタイム、ターン間、または手渡された場合のみ、のいずれかです。メモに返信がないように見える場合は、チャットに何か入力すると再び応答できるようになります。
- レビューが終わったら、監視(ストリーミング監視、または待機中のバックグラウンド
--once)とサーバーを停止してください。
どちらも Monaco 専用で、Monaco は serve.ts 自体から提供されます。serve.ts は独自の monaco-editor インストールを /vs で提供し、それがない場合は起動しないため、提供されるレビューは完全にオフラインで動作します(ページには オフライン — メモはローカルにキューされます というチップが表示され、メモのスレッドにはエージェントが後で確認すると表示されますが、機能はすべて動作します)。任意の静的サーバーでページを閲覧できますが、/vs、/save、/notes に応答するのは serve.ts だけです。
到達不能モード(サンドボックス):ファイルをそのまま送る
レビュー担当者が公開されたポートに接続できない場合、ビルド済みの review.html はすでに単体で完結するファイルです(フォントと UI ランタイムはインライン化され、すべての変更前後の内容と画像が埋め込まれています)。実行時に window.__reviewServer の値によって動作を切り替えます。この値を挿入するのは serve.ts だけなので、直接開いたページは読み取り専用として正常に表示されます。行ごとのメモ作成 UI は有効にならず(ガターの + グリフ、alt-N/コンテキストメニューの操作、ビューゾーンは表示されません)、Save/Revert/「エージェントにメモを送信」も非表示のままです。サーバーがなければコード行に差分は表示されません。Monaco は serve.ts が配信する依存関係であり、配信されていないコード行は、機能が劣化するのではなく、その旨を明示します。それ以外の機能はすべて利用できます。読み進める順序、グループ、説明文、目次サイドバーとそのドラッグ可能な分割線、ファイルごとのメモのテキスト、ステータスチップ、パス/GitHub リンク、画像比較ビューアと変更率の行、チェックマーク、ファイルごとの折りたたみと進捗バーです。取り除くものはなく、2 回目のビルドも不要です。手順 7 の build.ts でページのバンドルと完成済み HTML の書き出しはすでに済んでおり、このモードではそのファイルをそのまま渡します。
<scratchpad>/review.html をレビュー担当者に渡します。
SendUserFile(display: "render")がデフォルトです。ファイルをローカルで開き、読み取り専用の状態で確認できます(画像は全体が表示され、コード行には差分表示にサーバーが必要である旨が表示されます)。フィードバックを残したい場合は、チャットに貼り付けてもらいます。- チームメイトと共有できるリンクが必要な場合は、代わりに
Artifactを使います(「公開」を参照)。ファイルではなく、ホストされたページが提供されます。
ポートを使えない場合に限った公開
Artifact を使うのは、ローカルポートを開けない、または接続できない場合に限ります。クラウドサンドボックスや、別のマシンを使うレビュー担当者が該当します。その場合は、すでに背後にサーバーがなくても読み取り専用になっている review.html を直接公開し、内容に合ったファビコンと一文の description を設定します。同じパスに再公開すれば、URL は維持されます。
その代わりに何ができなくなるか把握しておいてください。Artifact ホストの CSP では同一オリジンへの接続しか許可されないため、公開されたページから 127.0.0.1 に接続することはできません(トンネル経由も同様です。制限対象はスキームではなく connect-src であり、さらに Chrome のプライベートネットワーク規則でも拒否されます)。設計上、読み取り専用です。保存はできず、メモはそのブラウザーの localStorage に保存され、コード行に差分は表示されません(Monaco は serve.ts からのみ提供されます)。公開したレビューで確認できるのは、文章、グループ、画像です。
9. ページ自体の変更
ページの見た目や動作を変える必要がある場合に限り、このセクションに従ってください。それ以外の場合は、このセクション全体を読み飛ばしてください。
**コミット前にテストしてください。**スキルのディレクトリから pnpm check:type と pnpm test:e2e を実行します。テストスイートの仕組みと追加方法は、リポジトリの CLAUDE.md に記載されています。ページの機能の大半はブラウザーでしか確認できず、ここで発生するバグの多くは以前の動作に対するリグレッションなので、すべて実行してください。src/ を編集し、以後のレビューすべてに変更を反映させます。その前に artifact-design スキルを読み込んでください。これは実用的なリファレンスツールであり、編集記事のようなページではありません。整ったタイポグラフィ階層と適切な(少数の)色使いに徹し、ヒーローや装飾は加えないでください。
Preact アプリで、Vite でビルドされています
src/ は preact 11 上で動作する一般的な Preact + TSX アプリで、buildPage.ts が Vite のライブラリモード(write: false)でバンドルし、1 つの ES モジュールと 1 つのスタイルシートを文字列として生成します。build.ts はフォントとペイロードの JSON とともに、それらをページにインライン化します。実行時に取得されるのは、serve.ts 自身のインストールから /vs で読み込まれる monaco だけです。CDN も代替レンダラーもありません。monaco が見つからない場合は、その失敗がそのまま報告されます。また、配信されていないページでは、コード行にレビューを配信するよう表示されます。
- skill ディレクトリで
pnpm exec tsc --noEmitを実行すると、型チェックできます。monaco はバンドルされたインポートではなくスクリプトのグローバル変数として読み込まれるため、ページで使用する monaco API の一部を手書きで記述した型定義(src/monacoApi.ts)も含め、アプリ全体に型が付いています。 - ページは
build.tsのpage()で組み立てられます。charset、<style>、#app、`` マーカー、ペイロード、モジュールスクリプトの順です。ペイロード内のすべての<は\u003cにエスケープされます。これはパーサーが追加処理なしで元に戻す有効な JSON エスケープです。そのため、レビュー対象のファイルにこのマーカーや閉じタグが含まれていても、配置先のブロックから抜け出すことはできません。 src/page.cssにスタイルシート全体があり、FONT_B64はbuild.tsが埋め込む唯一のプレースホルダーです。
各部分の間で状態を共有するために、2 つの構造を使います。
Appが閲覧状態(ticked、collapsed、closedGroups、openDiffs、activeId)を管理し、単一のstateオブジェクトをヘッダー、サイドバー、グループに渡します。これにより、サイドバーでのチェックと行でのチェックが同じチェックになります。状態のコピーを別の場所に追加しないでください。makeStore/useStoreは、命令型の側が管理するもの、つまりメモ(monaco のビューポートゾーンとサーバーのポーリングから書き込まれます)、トースト、インライン表示と左右並列表示の選択(マウントの有無にかかわらず、すべての monaco エディターが読み取ります)に使います。notesStoreからレンダリングされるものは、メモが追加されると再レンダリングされます。そのため、メモの件数がサイドバーと行のヘッダーの両方に表示されます。
Monaco は命令型のままにし、DOM は Monaco が管理します。createDiffEditor はエディターを 1 つ作成し、MonacoDiff が渡すセッターを通じて結果を返します。また、メモのゾーンでは、その DOM ノードに Preact のツリーを render します。
Palette:VS Code 独自のデフォルトテーマ
これはコードレビュー用ツールです。独自の配色を考案するのではなく、開発者が毎日差分を読むために使っているツールから配色を取り入れています。すべての色は、VS Code 独自のデフォルトテーマ(2026 Dark/2026 Light)のキーに対応しています。このテーマは 1.113 以降、デフォルトとして同梱されています。microsoft/vscode の extensions/theme-defaults/themes/2026-{dark,light}.json から直接読み取っています。
| トークン | 参照元のキー | レビューページでの用途 |
|---|---|---|
--paper | editor.background | ページの背景 |
--card | sideBar.background | チャプターカード、ヘッダー、トースト |
--control-bg | checkbox.background | 中立的なボタン/チェックボックスの塗りつぶし |
--rule | panel.border / sideBar.border | 区切り線 |
--ink / --ink-soft / --ink-faint | editor.foreground / descriptionForeground / disabledForeground | テキストの階層 |
--accent / --accent-hover | button.background / button.hoverBackground | リンク、主要な操作 |
--accent-ink | button.foreground | 単色の --accent の塗りつぶし上のテキスト |
--new / --del | editorGutter.addedBackground / deletedBackground | 新規/削除チップのテキスト、差分統計 |
--mod | notificationsWarningIcon.foreground | 変更済みチップのテキスト |
--pr / --pr-bg / --pr-edge / --pr-ink | charts.purple | スタック内のどの PR か:スタックバー、「すべて」切り替え、行上のチップ、集約されたノートのラベル。他の用途には使わないため、PR への参照がどこに表示されても判別できます。この方針を維持してください |
--new-bg / --mod-bg / --del-bg | diffEditor.insertedTextBackground / ゲージ/警告の色合い / removedTextBackground | チップとコールアウトの塗りつぶし。すべて半透明です。VS Code 独自の色は全体を通してアルファ値を活用しているため、このパレットも同様です |
page.css の :root ブロックには、各値に対応するコメントとともに、現在のトークン一式が記載されています。内容に食い違いがある場合は、この表ではなくそちらを参照してください。この表は概要です。境界線が復活しています(VS Code 独自の UI では全体を通して使われています)。ページ独自の monaco 差分テーマ(monacoLoader.ts)も同じ値を editor.background/diffEditor.*/editorLineNumber.foreground に取り込んでいるため、このページの差分は VS Code 自体の差分と同じ見た目になります。
カスタム Web フォントはありません。--display と --body はどちらも標準のシステムフォントスタック(system-ui, -apple-system, "Segoe UI", Roboto, sans-serif)で、VS Code 独自のワークベンチに合わせています。ワークベンチにも別個のディスプレイ用書体はありません。見出しは書体ではなく、サイズと太さで区別します。パスと差分には ui-monospace を使います。
このページがすでに備えている機能 — 編集する場合は維持してください
- スティッキーヘッダー:ライブ進捗バーとチェック済み/合計数、すべてを開く、すべてを閉じる、チェックをすべて解除するボタン、そして Diffs の選択メニュー(デフォルトのサイドバイサイド/インライン、さらに競合レビューでのみ使える3ウェイ)。
- 目次はヘッダーではなく、配置されている端から開きます。目次が閉じているときだけ、ビューポートの左端に固定された縦向きの Contents タブを表示します。閉じるにはサイドバー自身の見出し内にあるシェブロンを使います。ツールバーの端にある切り替えボタンは、開閉する対象から遠すぎました。閉じたとき、サイドバーが存在することを示すものが何も残らないからです。
- ヘッダー行の上にスタックバーを表示します。シェルが2件以上のスタックを認識しているときだけ、チェーン内の各PRを順番に並べ、レビュー中のものを強調表示します。ビルド時に作成されている場合は、末尾に all ボタンを表示し、一度にすべてのPRを読み込みます(「Stacks, on the forge or only local」を参照)。これはチェーンのステップではなく、矢印の外側に配置されます。また、チェックボックスではなくボタンです。このバーのチェックボックスはどれも「このPRの全ファイルを読んだ」という意味ですが、こちらは「すべて表示する」という意味です。同じコントロールが異なる意味を持つのは、形を分けるよりも好ましくありません。どのPRをクリックしても、そのPRを読む表示に戻ります。バー内はすべて
--prで描画します。これはPR参照用に確保された色です。ページにあるもう一つの機能は、そのレビューにページ全体を切り替えるボタンです。selectReviewがその不活性ペイロードブロックを再解析し、App はレビューをキーにして再マウントされます。そのレビュー固有のメモとチェック状態を読み込み直し、画像スイープを再開します。保存されていないエディターの変更がある状態で切り替えようとすると、先に確認を求めます。レビューのない兄弟PRは、forge ↗ リンク付きでグレー表示されます。エージェント向けの指示ファイルが残されている場合は、awaiting review チップも表示します。バーはheader.top内にあるため、計測対象の--header-heightと、その下にあるスティッキー要素は、すでにバーの高さを考慮しています。 - 目次サイドバーは、読み進める順序の横にスティッキー表示されます。各グループとファイルをツリーとして表示し、それぞれに専用のチェックボックスと
done/totalを持ちます。ファイルのチェック状態と行のチェック状態は同じものです。3つの表示モード(章、fs ツリー、差分サイズ)は、1つのツリーウィジェットです(「Every contents view is a tree widget」を参照)。各ファイルと各章の見出しは実際のリンクなので、中クリックまたは「新しいタブで開く」でそれぞれ独立したタブに開きます。通常のクリックではグループとファイル自体を開いてその位置までスクロールします。スクロールスパイは読んでいるファイルを強調表示しますが、クリック後1秒間は抑制され、明示的なジャンプが通り過ぎた行によって取り消されないようにします。60rem 未満ではオーバーレイになり、最初は閉じた状態で表示され、使うと再び閉じます。 - 2つの領域の境界はドラッグで調整できます。
react-resizable-panels(preact/compat経由で利用する React ライブラリ)を使います。buildPage.tsのresolve.aliasとtsconfig.jsonの対応するpathsにより、同じ Preact がライブラリとページの両方をレンダリングします。コピーが2つあると、ライブラリのフックにレンダラーがなくなってしまいます。3つのコンポーネントはclassではなくclassNameを受け取ります。幅はautoSaveIdを使って、レビューごとではなく閲覧者ごとに保存されます。Layoutは、ドッキング表示の場合にのみPanelGroupをレンダリングします。オーバーレイ表示(60rem 未満)または目次を閉じた状態では、分割するものがないため、代わりに通常の flex.layoutをレンダリングします。同じ60remのしきい値を、マークアップ(useContentsOverlays)とスタイルシートでも使います。- パネルをスクロールコンテナーにしてはいけません。ページ全体が1つの文書としてスクロールしなくなるためです。グループは
height: auto、両ペインはoverflow: visibleにし、ハンドルは以前サイドバーのborder-rightだった境界線を描く、全高のドラッグ領域にします。 - 2つのレイアウトを切り替えるとすべての行が再マウントされるため、スクロールスパイのオブザーバーは
rowNodes.tsに置き、アプリのマウント時に一度だけではなく、行が登録されるたびに監視します。
- 各グループはデフォルトで開きます。読み進める順番の番号(ここでは実際の構造を表し、ほかの場所にある装飾的な01/02/03マーカーとは異なります)、説明文、ファイル行を表示します。見出しはページヘッダーの下にスティッキー表示されます(ヘッダーの高さは計測されるため、コントロールが折り返されても対応します)。グループ全体をチェックするチェックボックスがあり、一部だけ読んだ状態では中間状態を表示し、チェックするとグループを折りたたみます(チェックを外すと再び表示します)。見出し自体の切り替えと競合しないよう、そのチェックボックスは
stopPropagationする必要があります。 - 各行には、パスをキーにするチェックボックスがあります。サーバー経由の場合は
<store>/ticks.jsonに、そうでない場合は(レビューIDをキーにして)localStorageに保存され、どちらの場合も最初のレンダリング前に読み込まれます。グループのチェックは1回の書き込みにまとめられ、ポーリング時に再読み込みされるため、2つ目のタブとも状態が一致します。さらに、ステータスチップ、パスをクリップボードにコピーするボタン(--githubが指定されている場合は「GitHub ↗」リンクも表示)、+N −M、キャレットがあります。ファイルをチェックすると折りたたまれ(チェックを外すと再び開きます)、読み進めるにつれてページは読み進める順序だけの表示に近づきます。再読み込みしても、読んだところまで正確に折りたたまれた状態に戻ります。折りたたみ部分にはメモと、最初は閉じた状態のパネルにかぶせた Show diff/Hide diff ボタンがあります。読み進める順序を見やすく保つ必要があり、差分は読者がそのファイルに到達したときに行う操作だからです。これは通常のボタンです。 - 差分は最初に開いたときに遅延レンダリングします(そうしないと数十件が最初からすべて構築されてしまいます)。その後は閉じたり折りたたまれたりしても、非表示のままマウントし続けます。破棄すると、ファイルにチェックを付けるたびに未保存の編集が失われてしまうためです。差分は jsdelivr から読み込む Monaco の差分エディターにレンダリングします。ファイル拡張子ごとの構文強調表示と
hideUnchangedRegionsにより、長いファイルは変更箇所に折りたたまれます。静的なパッチではなくエディターを使う理由です。読み取り専用で、言語サービスの診断は無効にします(ここで修正を行うわけではなく、数メガバイトのワーカーを節約できます)。テーマにはページ独自の配色を適用します。また、scrollbar.alwaysConsumeMouseWheel: falseにより、ポインターがエディター上を通ってもページのスクロールを維持します。 - 画像の行では Monaco の代わりに比較ビューアーが開きます。 ツールバーにはバージョン選択があります(キャプチャした各バージョンごとに列を表示し、ラベルの上にあるラジオボタンで「元」側、下にあるもので「先」側を選択します。同じバージョンを両側に選択することはできません。バイト単位で同一の列はグレー表示され、ツールチップが表示されます)。ほかに ⇄ 入れ替え、モード切り替え from | to | diff | swipe | flick、現在表示中の単一バージョンにマゼンタの差分を重ねる overlay チェックボックス(from/to のみ。diff モード自体がオーバーレイであり、swipe ではすでに両方を表示するため、このモードでは無効になります)、差分の外接矩形に合わせる zoom diff、fit、px/% 表示があります。flick モードでは、画面上に現在表示されているバージョンのラベルも表示します。Flick は両方を同時に表示する代わりに、from/to のレイヤーを600msごとに切り替えます。差分が動きとして見えるため、小さな差分や微妙な差分を見つけやすく、静的なオーバーレイよりも発見しやすくなります。差分はページ内の canvas で計算し、両方の画像を含む範囲に、透明背景のマゼンタで描画します。範囲外のピクセルは異なるものとして数え、ペアごとにメモ化します。
- ビューアーのステージは自由にパン、ズームできます。 ポインターのドラッグでパンします(swipe モードでは、仕切りから24px以内で始まるドラッグは仕切りを動かし、それ以外はパンします)。2本指のピンチでズームし、ダブルタップで表示に合わせてリセットします。ctrl/⌘キーを押しながらのホイール操作ではカーソル位置を中心にズームします(macOS のトラックパッドピンチはまさにこの方法で届きます)。それ以外のホイール入力はヒューリスティックで振り分けます。行/ページ単位の delta、または粗い単軸ピクセルステップはマウスホイールと見なしてズームし、細かい delta または2軸の delta はトラックパッドと見なしてパンします。400msの慣性期間を設け、フリックの余韻でズームに切り替わらないようにします。
touch-action: noneを設定したビューポートでホイールリスナーを非パッシブにする必要があります。そうしないと、これらの入力をpreventDefaultで抑制できません。 - 各画像行には、機械的に計算した概要行があります。
WxH — N% of pixels differ (Npx)の形式で、初回描画後にバックグラウンドスイープで計算します。画像は一度に1つずつ処理し、次に進む際にピクセルデータを解放します。画像のバイト列は画像ごとに不活性な<script type="application/json">ブロックに格納し、その行で必要になったときだけ解析します。これによりペイロードと実行可能なスクリプトを小さく保ち、スマートフォンの WebView でもページをレンダリングできます。 - 画像のメモはファイルに紐付きます。 メモを付ける行がないため、サーバー経由の画像行には Add note ボタンがあり、行メモと同じスレッド UI を開いて、行0として保存します。
reply.ts --line 0で返信すると、Markdown の対応版ではL0として表示されます。 - サイドバイサイド/インラインはヘッダーの Diffs 選択メニューで切り替え、すべてのエディターに一度に適用します。デフォルトはサイドバイサイドです。レビュー担当者は変更前と変更後を比較するため、インラインでは1列に沿って比較する必要があります。
diffViewStoreはエディターの構築時に読み込まれ、すでに画面上にあるエディターからも変更を購読し、閲覧者ごとにlocalStorageに記憶します。サイドバイサイドを有効にするには2か所で指定する必要があります。この幅の狭いペインでは、renderSideBySide: trueだけでは機能しません。monaco のuseInlineViewWhenSpaceIsLimitedが、約900px未満では自動的にインライン表示に切り替えるためです。両方の設定をsideBySideOptionsに指定します。 - 移動したコードは差分内で囲み表示されます。
build.tsはレビュー範囲と、コミットごとのレビューの場合は各コミットについて、moves.tsと同じ検出処理を実行します(どちらもmoveDetection.tsを使用します)。各ファイルの移動範囲をsidesと同じ形式のキーでpayload.movesに埋め込みます。createDiffEditor.tsxのwireMovesは、移動した色(.tw-movedですでに使われている--modと同じ--moved)で各範囲を囲みます。ボックスの上には同じ色のタブがあり、after 側には「moved here fromx.ts:390」、before 側には「moved to」と表示し、コードが完全一致かどうかも示します。行のレビューにそのファイルが含まれていれば、端にはそのファイルの行へのリンクを表示します。移動中に編集された行はボックスの左右線を残しつつ、塗りつぶしを外して青いガターバーを表示します。そこが読むべき箇所だからです。ボックスの装飾は行全体に適用します(すべての行に左右線、先頭行に上線、末尾行に下線)。タブは view zone なので、サイドバイサイド表示でも左右の差分が揃います(monaco は反対側に余白を挿入します)。また、リンクをクリックできるよう、.note-zoneと同様に monaco のテキストレイヤーより前面に表示します。行の統計には「N moved in / out」が追加され、目次のサイズバーでは移動した行を除外します。移動した行は一瞥すればよく、読む必要がないためです。インポートは移動として数えません。分割すると、新しいファイルすべてに散らばるためです。3ウェイ表示では、競合解決時に独自の移動は起こらないため、移動を表示しません。 - Space キーで、目次で強調表示されている、現在読んでいるファイルをチェックし、もう一度押すとチェックを外します。Monaco エディター、メモ、または入力欄で入力中は動作しません。ボタンにフォーカスがある場合は、ボタンを押す代わりにファイルをチェックします。行のチェックボックスのツールチップにも、この動作を記載します。
- HEAD の位置を表示します。 ブランチがチェックアウトされているPRにはスタックバーに HEAD チップが付き、HEAD が指しているコミットにはコミットバーにチップが付きます。どちらもポーリングのたびに追従するため(
checkoutStore)、新しいコミットが加わるとすぐに移動します。ブランチを切り替えるとページが再読み込みされます。 - PR またはコミットにカーソルを合わせると規模が表示されます。 スタックバーのPR(および「all」)には、スタック内の位置、コミット数、ファイル数、追加・削除行数が表示されます。コミットバーのコミットには、PR内の位置、ファイル数、行数が表示されます。サーバー経由のバーでは、staged、working、held の各停止位置に、ファイル数と読了後の行数が表示されます。ビルドは各PRの
sizeをシェルエントリーに記録し(sizeOf:ベースからのgit diff --shortstatによるPR全体の差分)、各コミットの行数を編集インデックスに記録します。表示処理はbarTips.tsが行います。 - 各差分には、比較対象が表示されます。 各エディターの上に、「diff
main~2 (52e9aab)→item-behaviours (a84dc34)」と表示し、右側がディスクの状態に続く場合は「+ working tree」も表示します。名前は git 独自のもの(name-rev)で、レビュー範囲ではビルドがpayload.refsに、コミットごとのレビューでは各コミットのcommits[].refsに記録します。ブランチ名で指定された head は、そのブランチ名を保持します。参照名のないコミットは名前がないため、SHA だけを表示します。これにより、別のブランチや古いコミット、存在しないコミットなど、読者が想定しているものと異なるレビューが一目で分かります。 - 別の場所でも編集されたファイルには、その場所が表示されます。 行のメモの下に、「also edited in PR 1/3 commit 3/5」と表示し、同じファイルを変更したスタック内のほかのPRと、PR内のほかのコミットをそれぞれ示します。すでにページに表示されている場所は除外するため、PR全体を読んでいる場合はほかのPRだけが表示され、すべてのPRとコミットを読んでいる場合は何も表示されません。スタックが1つのPRだけの場合は、PRの表示を省きます。PRが1つのコミットだけの場合は、コミットの表示を省きます。各場所はそのファイルへのリンクで、ページ上でレビューとコミットを切り替え、再読み込み時と同様にその行へ移動します(
?file=)。サーバー経由のコミットバーだけが認識しているコミットは要求され、次のポーリングで一覧に表示されると開かれます。ページにレビューが含まれないPRはプレーンテキストで表示します。ビルドは各PRのすべてのコミット(レビュー済みかどうかは問いません)と、変更するコミットへのパスごとの対応を示すbyPathを含む編集インデックスをシェルに集約します(shell.edits)。各行は自身のパスだけを参照します(alsoEditedIn.ts)。buildStack.tsはすべてのレイヤーをインデックス化します。build.ts --mode prは自身のPRをインデックス化し、--stackを指定すると兄弟PRを取得してすべてのレイヤーもインデックス化します。取得できないPRがあっても、ビルド失敗ではなく警告になります。ページ内のどこでも「PR 2/3」は、レビュー済みかどうかにかかわらず、3件のPRからなるスタックの2番目のPRを意味します。fixtures/commitStack.shはこの動作を試せるよう、両方のPRでsrc/parse/block.tsを編集します。 - 任意のPRを編集:保留中の編集。 チェックアウト先のディスクが元になっていない行(別のPR、または別のコミットの行)から保存すると、そのPRの 保留中の編集になります。
refs/guided-review/hold/<branch>として保持されるコミットで、親は行が参照するコミット(PR全体を読む行ならPRの最後のコミット)です。各保存でそのコミットを amend します(stackEdit.tsがスクラッチ用インデックス、commit-tree、古い値を使ったupdate-refなどの処理を行うため、保存が競合しても一方が失敗し、編集を失うことはありません)。コミットバーでは、編集元のコミットの直後に held 停止位置として表示し、そのコミットと保留中の編集との差分をファイルごとに行に表示します。編集元の行にも保留中の内容を表示するため、そのまま編集を続けると保留中の内容を編集できます(heldSync.ts)。PRごとに保留できる場所は1つです。同じPRの別のコミットを編集しようとすると、編集が保留されている場所を示して拒否されます。コミットの内容に戻すと、保留が解除されます。コミットバーの下にある held bar から、New commit(メッセージを入力し、そのコミットの後に新しいコミットを挿入)、Amend into(そのコミットに amend)、Discard(バックアップ参照として保持)を選べます。保留中の編集があるPRは、スタックバーに表示されます。レビュー担当者が書いた各メッセージには、読んでいたコミット(SHA、またはheld:<branch>。PR全体を読んでいた場合は省略)を記録します。これにより、メモに対応するエージェントの編集はheld.tsを使って同じ場所に保存され、awaitNotes.tsは各メモがどの内容を読んでいたかを示せます。 - コミットまたは amend を行うと、そのPRはすぐに更新され、上位のPRは古い状態のままになります。 PRの後続コミットはメモリー上で新しいコミットの上にリプレイされます(
merge-tree --write-tree)。すべてが競合なくリプレイできた場合にのみ、単一の ref トランザクションで更新します。古い head はrefs/guided-review/backup/<branch>/<time>にバックアップされます。サーバー経由のチェックアウトでブランチがチェックアウトされている場合は、未コミットの変更を保ったままワーキングツリーも更新します(read-tree -m -u。上書きされる箇所がある場合は拒否します)。ほかの worktree でチェックアウトされているブランチは ref のみを更新します。リプレイされたコミットは元の作者、メッセージ、日時を保持するため、同じリプレイでは常に同じコミットが作成され(記録済みの解決も再び見つかります)、上位のPRは古い head のままにしてrefs/guided-review/restack-from/<branch>に記録します。スタックバーには Rebase N upstack が表示され、上位のPRをすべて同様に新しいPRの上へリプレイします(全件成功か、全件失敗のどちらかです)。これはgh stack rebaseとは異なります。gh stack rebaseはブランチの途中のコミットを置き換えられず、停止するためのチェックアウトも必要です。競合がある場合は何も移動せず、エージェントへのメモになります(「A conflict the page hit」を参照)。更新後、サーバーはすべてのレビューのチェック状態と、作成者が記録したグループのコミット参照を新しいコミットに対応付け、レシピからページを再構築します。スタックページにもkind: "stack"のレシピがあります。コミットごとのレビューやスタックレビューのレシピはfollow: falseなので、このときにのみ再構築され、自動では再構築されません。その後、ページが再読み込みされます。 - Monaco の差分ガターメニューは無効です(
renderGutterMenu: false)。メニュー項目は最後の差分を基に配置されるため、ファイルが短くなった場合(Revert や保留中の内容の反映など)に存在しない行を要求し、「Illegal value for lineNumber」エラーが発生します。Revert は通常の編集なので、取り消すことができます。 - すべての目次表示はツリーウィジェットです(Headless Tree、
@headless-tree/coreと/react)。すべて1つのコンポーネントContentsTree.tsxを通します。各表示モードが構築するのはノードだけです。ファイルと、それぞれに独自の見出しマークアップを持つフォルダー(章、章内のディレクトリ階層、fs ディレクトリ、またはパッケージ)で、差分サイズはフラットなリストです。タブで移動するフォーカス位置は1つだけです。矢印キーで移動し、左右キーで折りたたみ/展開、Home/End で先頭/末尾へ移動し、Enter でファイル(または章の最初のファイル)へ移動します。クリック、ctrl/cmd-クリック、shift-クリック、shift-矢印キー、ctrl-A で選択します。Space で選択項目をすべてチェックし、すでにすべてチェック済みの場合はチェックを外します。選択中の項目のチェックボックスを操作すると、選択範囲全体をチェックします。章を通常クリックすると最初のファイルに移動し、開いた状態を維持します。キャレットをクリックすると折りたたみます。shift 範囲選択が通過するだけのフォルダーは、その中のほかのファイルを選択範囲に加えません。既読のファイルは見出しの下から移動するため、そのフォルダーが表すファイルを基準に判定します。各表示モードは、画面に表示される順序でノードを渡すため、キーボード操作も視覚的な順序に沿います。フォーカスと選択範囲はライブラリが管理し、行はページ独自のものです。チェックすると行が移動してフォーカスが外れるため、ツリーからチェックしたときはフォーカスを戻します。ページ独自の Space 操作ではツリーの状態は変わりません。ツリー内で ctrl/cmd-クリックは選択操作なので、新しいタブを開くには中クリックまたはリンクのコンテキストメニューを使います。アイコンのグリフ(キャレット)は\uf229形式のエスケープで記述します。Unicode の私用領域の文字をそのまま使うと見えず、編集時に気付かないまま削除されることがあるためです。 - 名前が変わったファイルは、変更前のパスと比較されます。
build.tsは git にスコープ内のリネームを要求し(--name-status -M)、リネームされたファイルの変更前の内容と行数を、以前のパスから読み取ります。行ヘッダーには、payload.renamedFromを使って元のパス("fromold/path.ts")を表示します。新しいパスだけで差分を取ると、リネームされたファイルは新規ファイルとして扱われ、変更前の内容が空になり、すべての行が追加行になってしまいます。 - モノレポでは、ファイルのパスにパッケージを反映します。
package.jsonを含むディレクトリはパッケージとして扱います。ただしモノレポでは、リポジトリのルートはワークスペースなので除きます。build.tsは各ファイルの最も近いパッケージを見つけ、レビュー時点のpackage.jsonを読み取り、packages(ディレクトリ → 名前)をペイロードに記録します。パスを表示する箇所では、PathLabelがパッケージ名を色付きの枠付きチップとして描画し、その後にパッケージ内のパスを表示します。リポジトリ内のすべてのパッケージで npm スコープが共通の場合は、チップではスコープを省略し(@shop/coreはcoreと表示)、完全な名前はチップのツールチップに移します。レビュー対象ファイルのパッケージだけでなく、リポジトリ内のすべてのpackage.jsonを対象に確認するため、スコープなしのパッケージが1つでもあれば、すべてのスコープを表示します。これには、行見出し、章表示のディレクトリ見出し、差分サイズ表示が含まれます。ツリー表示では各パッケージがトップレベルのノードになり、その内部のツリーはパッケージから始まります。ラベルが省略表示される場合、ディレクトリ階層の見出しではチップ全体を表示し、ファイル行ではファイル名より先にチップを省略します。fixtures/monorepo.sh <new dir>は、worktree モードで試せるよう、未コミットの変更を含む小さなモノレポを構築します。 - 3ウェイはオプションではなく、別の表示です。 競合レビュー(
payload.conflictがあり、各辺にincoming/ancestorがある場合)では、createDiffEditorは差分エディターの代わりに、ホスト内に3つの通常のエディターを構築します。2ウェイと3ウェイを切り替える際は、表示を一方破棄して、同じモデル上にもう一方を構築します。そのため、未保存の編集と保存時の SHA は切り替え後も保持されます。各ペインは全高で、ページは3つのペインすべてをまとめてスクロールし、スクロール同期は一切ありません。threeWayLayout.tsが位置を揃えます。各側を解決結果と照合し(Myers アルゴリズムのlineDiff.ts)、両側から解決結果に引き継がれた行を同期位置として扱います。同期位置の間にある区間は、斜線入りの view zone で最も高いペインに合わせて余白を追加します。メモのスレッドも解決結果の区間の高さに含めるため、両側の高さもそれに合わせて調整します(noteZonesChanged)。3つのペインすべてで折りたたみとワードラップを無効にします。どちらも、ほかのペインとの行位置がずれてしまうためです。表示方法は競合レビュー用に別途記憶され、デフォルトは3ウェイです。 - Monaco は機能追加ではなく依存関係です。 レビューサーバー自身の
monaco-editorインストールから/vsを通じてのみ配信します(そのため、配信されたレビューは完全にオフラインで動作します)。serve.tsは Monaco がなければ起動を拒否します。CDN もプレーンテキストの代替レンダラーもありません。読み込みに失敗すると差分の場所にエラーブロックを表示し、サーバーなしで開いたページでは、コード行にサーバーが必要だと示します。ネットワークが切れるとヘッダーに offline チップが表示され、メモのスレッドは「エージェントが後で受け取ります」に切り替わります。メモは常にサーバー側に保存されるため、オフライン時に変わるのはメッセージだけで、動作は変わりません。 - artifact-design のトークンパターンに従い、両方のテーマに対応します。
:root、prefers-color-schemeメディアクエリー、両方のdata-themeオーバーライドを使います。Monaco のテーマは両方のシグナルに応じて切り替えます。 - タブアイコンは
src/favicon.pngからデータ URI としてページにインライン化します。ヘッダーからゲームにリンクするものと同じマークです。配信ではなくインライン化が必要です。ビルド済みのreview.htmlをディスクから直接開いた場合、問い合わせ先のサーバーがなく、serve.tsは何も要求されないよう/favicon.icoに204を返すためです。 <meta charset="utf-8">はページを組み立てるときの最初の行で、build.tsは明示的に UTF-8 の読み書きを行います。両方がないと、file://で開いたときに Windows-1252 と誤認され、本文中のダッシュや矢印がすべて文字化けします。- サーバー経由の場合、各エディターには Revert も表示されます(ビルド時の内容をバッファーに戻します。ディスクには直接書き込みません)。ヘッダーには「Send notes to agent」も表示されます。サーバーなしで開くと、書き込み先がないため、どちらも非表示になります。
.tsxを正しく読み込むには2つの設定が必要です。どちらか一方でも欠けると、ファイル全体に'>' expectedが表示されます。TypeScript のデフォルト設定のコンパイラーオプションにjsx: JsxEmit.Preserveを指定し、実際のパスを含むモデル URI を使います。TypeScript はファイル拡張子からスクリプト種別を判定するため、拡張子のないinmemory://URI では、すべてのタグが型アサーションとして解析されます。2つの側には異なる URI が必要です。- メモを追加するには、カーソルを合わせた行のガターにある
+を使います(onMouseMoveで装飾を設定し、ターゲットの型がGUTTER_GLYPH_MARGINのときにonMouseDownで開きます)。キーボードショートカットだけでは、機能を見つけにくいためです。alt-Nとコンテキストメニューも引き続き使えます。 - メモは文字列ではなくスレッドです。
{line, messages: [{from, text}]}の形式で、以前の単一のtext形式は最初のメッセージとして読み込みます。ゾーンには会話と Reply 入力欄が表示され、エージェント側の返信はreply.ts経由で届きます。エージェントのメッセージは Markdown 形式で、snarkdown-in-react(Markdown.tsx)がレンダリングします。要素を構築し、HTML 文字列は作らないため、メッセージ内の生の HTML はテキストとして表示されます。このページ内のファイルへのリンク(reply.ts --editedが書き込む?review=…&commit=…&file=…)は、そのファイルをページ上で開きます。also edited inと同様にgoToPlaceを通し、保留中の編集がその後コミットされている場合は、PR全体を読む表示に切り替えます。それ以外のリンクは新しいタブで開くため、リンクをたどってもレビューから離れません。リンクとして扱うのは http(s)、mailto、#、相対リンクだけです(javascript:などはテキストとして表示します)。画像は http(s) またはdata:imageのものだけを許可します。レビュー担当者自身のメッセージは入力どおりに表示されます。このパッケージは宣言ファイルではなく TypeScript ソースを同梱していますが、そのソースはこのプロジェクトの厳格なチェックを通りません。そのためtsconfig.jsonのpathsは、代わりにsrc/snarkdownInReact.d.tsを型チェッカーに使わせます。vite はパッケージ自体をバンドルし、そのreactをreact-resizable-panelsと同様にpreact/compatにエイリアスします。メモを読むコードはすべて、どちらの形式にも、読み書き途中のファイルにも対応する必要があります。メモファイルは読み込み中にも書き込まれるためです。 - メモゾーンには2つの状態があります。書き込み中または返信中はテキストエリアを表示し、その後に Reply/Delete を含むスレッドを表示します。この状態切り替えがなければ、保存しても何も起きなかったように見えます。ゾーンは破棄されて同じ内容で再構築されるからです。
- サーバー経由の場合、ページは2秒ごとに
/stateをポーリングし、開いているエディターのパスを送ります。ディスク上で SHA が変わったファイルは/file経由で再読み込みされます(エディターに未保存の変更がある場合は、そちらを優先します)。行数も更新され、トーストが表示されます。エージェントの新しいメッセージもトーストで通知されます。これにより、メモへの返信として行った編集は、誰も再読み込みしなくても反映されます。 serve.tsは/favicon.icoに204を返します。ブラウザーは何も指定しなくても要求してきますが、404にすると、ページが必要としていないものについて、すべてのレビューのコンソールにエラーが出てしまうためです。- 書き込みトークンはレビューの隣に保存され、再起動後も再利用されます。このトークンは、ブラウザー内の別のページがレビュー担当者に無断でそのポートへ書き込むのを防ぐためだけにあります。実行ごとに新しいトークンを作ると、開いたままのタブがすべて黙って使えなくなります。守ろうとしているものより、メモを失うほうがはるかに重大です。
do_POSTは認証を含むあらゆる判定を行う前にリクエスト本文を読み込みます。要求を拒否して本文を読み取らずに残すと、その JSON がソケット内に残ります。その keep-alive 接続上の次のリクエストは本文の途中から解析され、自分のメモのテキストを含む不可解な400 Bad request syntaxエラーとして表面化します。- メモは Monaco の view zone に置かれます。ゾーンについては、見落としやすい点が4つあります。エディターのテキストレイヤーより下に描画されるため、メモには
position: relative; z-indexが必要です。ないとボタンをクリックできません。また、エディターより先に mousedown を処理するにはsuppressMouseDown: falseが必要です。monaco はゾーン自身の DOM ノードにインラインのdisplayを設定するため、メモのレイアウトはゾーン内側のラッパーに置く必要があります。さらに、monaco はゾーンを追加した時点ではなく、独自のレンダーフレームでノードを文書に追加します。そのため、新しいメモの入力欄にフォーカスを当てる処理は、成功するまで複数のフレームにわたって再試行する必要があります(折りたたまれた未変更領域内のゾーンではフォーカスを当てられません。display: noneになり、表示されていない要素にはフォーカスできないためです)。ゾーンの内容はaria-hiddenサブツリー内にもあるため、メモ UI の操作はマウス/キーボードによるもので、role クエリーでは取得できません。 - ページは
window.__reviewServerに応じて動作します。これはserve.tsだけが(`` マーカー内に)注入します。このマーカーは保存/Revert/引き渡しだけでなく、メモ作成や共同作業に関する機能全体を制御します。ガターの+グリフ、alt-N/コンテキストメニューのアクション、view zone のメモエディターは、マーカーがある場合にのみ有効になります。そのため、サーバーなしで開いたページは完全な読み取り専用です。このマーカーをテンプレートに残し、両方の分岐が引き続き動作するようにしてください。同じファイルで、共有可能なスタンドアロン形式と、サーバー経由で作業する形式の両方を提供します。