コンテンツにスキップ

GitHub コネクター

GitHub コネクターは、プルリクエストについて Pointsman に判定を依頼し、判定ごとにプルリクエストへチェック(check run)として表示します。モジュール @pointsman/github のうち、コネクターの役割を受け持つ部分です(connectors.md、英語)。同じモジュールのファクトの役割は、エンジンのためにプルリクエストを読みます(profile-format.md)。

既定ではシャドーモードで動きます。チェックを書くだけで、その結論は neutral なので、マージを止めることはありません。デプロイでは、選んだ判定に対してアクション(コメント、ラベル、レビューの依頼、承認)を有効にできます。また、判定コメントも有効にできます。これはプルリクエストごとに 1 つのコメントで、最新の判定を表示します。判定が人の確認を待っているときは、プルリクエストの中で回答できるようにもできます。どれを有効にしても、チェックは neutral のままです。

  1. プルリクエストでイベント(作成、新しいコミット、レビュー可能になった、編集、レビュー、チェックスイートの完了)が起きると、GitHub が webhook を送ります。コネクター自身のチェックスイートでは実行しません。コネクター自身の承認とその取り消しでも実行しません。それを行った実行が、そのあとでレビューを読み直し、判定コメントを描くためです。人がコネクターの承認を取り消したときや、ほかのアプリのレビューでは実行します。
  2. コネクターは webhook の署名を確かめてから、イベントをそのプルリクエスト専用のオブジェクト(Durable Object)に渡します。オブジェクトはプルリクエストごとに 1 つあります。
  3. そのオブジェクトは、自分のプルリクエストの処理を 1 回ずつ、イベントの 20 秒後に実行します。そのため、イベントが続けて届いても(複数のチェックスイートの完了など)、実行は 1 回にまとまります。
  4. 1 回の実行では、プルリクエストを GitHub から読み直し、設定した各プロファイルで Pointsman に判定を依頼し、プロファイルと head コミットごとに 1 つのチェック Pointsman: <profile> を書きます。チェックのタイトルは判定のシグナルとアクションで、たとえば 🟢 approve や 🔴 review (shadow) です((shadow) は、そのアクションに動作が対応づけられていないことを示します)。チェックの詳細には路線と分岐器が表示されます。

Pointsman に送るのは、自分のトークンを付けた POST /v1/decide/<profile> です(または、同じ呼び出しをトークンなしの内部呼び出しとして送ります)。本文は次のとおりです。

{
"state": { "pull_request": { "repo": "owner/name", "number": 7, "head": "<commit>", "title": "…", "body": "…" } },
"ref": "github:owner/name#7@<commit>"
}

タイトルは 300 文字、本文は 8,000 文字で切り詰めます。本文は、作成者が書いたとおりの説明です。CodeRabbit の要約ブロック(開始と終了のマーカー auto-generated comment: release notes by coderabbit.ai に挟まれた部分。ブロック全体の場合だけ)は除きます。そのため、ボットによる編集はモデルに届かず、once_per_head で新しい説明とも数えません。ほかのボットが説明に書いたテキストは除きません。Pointsman は、すべてのファクトを自分のアプリ(後述)で GitHub から読みます。冪等性キーは、プロファイル、この状態、ドラフトかどうか、ベースブランチ、そのプルリクエストの実行回数をもとにします。そのため、実行のたびに(再試行でも)新たに判定を依頼します。アクションが古い回答にもとづくことはなく、新しいイベント(たとえば CI の完了)ごとに新しい判定が出ます。例外は、人が確認する判定だけです。その判定は head コミットごとに保持し、もとになったものが変わらない間は、後の実行で読み直します(人に尋ねる)。

  • 署名。 webhook にはすべて、有効な X-Hub-Signature-256(本文を webhook のシークレットで計算した HMAC-SHA256)が必要です。ない場合は、本文を解析する前に 401 を返します。2 MB を超える本文には、Content-Length がなくても 413 を返します(本文は上限までしか読みません)。
  • リプレイ。 GitHub が署名するのは本文だけなので、再送された webhook に新しい配信 ID が付いている可能性があります。プルリクエストごとのオブジェクトは配信 ID と本文のハッシュを 7 日間覚えておき、重複を捨てます。また、イベントはきっかけにすぎません。コネクターは自分のトークンでプルリクエストを読み直すため、リプレイが通ったとしても、現在の状態をもう一度確かめるだけです。
  • 自分のリポジトリだけ。 ほかのリポジトリのイベントは無視します。自分が書いたチェックのイベントも無視するため、チェックが次の実行を起こすことはありません。
  • プロファイルと head ごとに 1 つのチェック。 チェックを作る前に、その記録を書きます。その間で実行が止まった場合、次の実行はまず GitHub でそのチェックを探して更新し、2 つ目は作りません。同じ head への後の判定も、同じチェックを更新します。
  • 再試行。 GitHub や Pointsman が混んでいる、止まっている、つながらない場合(5xx、429、ネットワークのエラーやタイムアウト)は、あとで実行し直します(Durable Object のアラームで、間隔を広げながら数回)。Pointsman が拒否した場合(それ以外の 4xx)や、アプリにできない場合(インストールされていない、権限がない、キーが壊れている)は、直すべき設定の問題です。エラーをトークンやキーを含めずにログに残し、プルリクエストには何も表示しません。どちらの場合も、そのプルリクエストの次のイベントで新しい実行が始まります。GitHub の設定画面から同じ webhook を再配信しても、リプレイとして捨てます。
  • エンジンの回答を確かめる。 判定を使う前に、どちらの経路で届いたかにかかわらず(Pointsman へのつなぎ方)、コネクターは読むフィールドを確かめます。対象は decision_id(判定 ID)、action(アクション名。英小文字、数字、_、- で 64 文字以内)、profile(依頼したプロファイル)、ref(送った ref)、profile_version、rule と、あれば facts(ファクト名と、その missing と reason)、review(pending、または読み直したときの resolved。resolved では人の回答が final_action にアクション名として入ります。ほかの状態では何もしません)、test です。ほかのフィールドは見ません。確認に通らない回答は、ログに残し(フィールド名だけで、値は残しません)、チェックに invalid answer: nothing done と表示し、次のイベントか再確認までは再試行しません。その回答では何もせず、保持もせず、ほかのプロファイルにも渡しません。head コミットへの承認は取り消します。
  • テストの判定は使わない。 test: true の判定(テスト用トークンで出たもので、ログには残りません)では、何も実行しません。アクションも keep_approval もなく(head コミットへの承認は取り消します)、保持も受け渡しもしません。そのチェックのタイトルは test decision: nothing done です。コネクターには、テスト用ではないトークンを渡してください。
  • 書き込みの権限はここだけ。 書き込めるアプリのキーを持つのはコネクターだけで、それも自分の Worker の中だけです。コネクターのトークンは、必要な権限(プルリクエストの読み取り、チェックの書き込み。アクションか判定コメントを有効にしたときだけプルリクエストの書き込み)と、1 つのリポジトリだけに絞ります。
  • GitHub で削除されたチェックは、次の実行で作り直します。

コネクターが Pointsman につなぐ方法は 2 つあり、デプロイの設定で選びます(adapters.md、英語)。

  • トークンを使う HTTPS(既定。どの構成でも使えます): POINTSMAN_URL とシークレット POINTSMAN_TOKEN を使います。POINTSMAN_TOKEN は、コネクターのクライアント用の Pointsman API トークンです。トークンには使えるプロファイルが決まっていて、有効期限を設けることができ、scripts/tokens.mjs で入れ替えます。
  • 内部呼び出し(エンジンの Worker が同じ Cloudflare アカウントにあるとき): エンジンの Worker のエントリーポイント InternalEngine への、POINTSMAN という名前のサービスバインディングを使います。トークンも URL も要りません。

バインディングがあれば、コネクターは内部呼び出しを使います。なければ POINTSMAN_URL と POINTSMAN_TOKEN が必要です。どちらもない場合や、バインディングと POINTSMAN_URL の両方がある場合は、コネクターは 500 を返し、どの設定が誤っているかをログに残します。バインディングと並んで残っている POINTSMAN_TOKEN は使いません。削除するまで、コネクターは警告をログに残します。

コネクターの Worker の設定は次のとおりです。

"services": [{ "binding": "POINTSMAN", "service": "<engine Worker name>", "entrypoint": "InternalEngine" }]

エンジンの Worker の設定で、呼び出しをどのクライアントとして扱うかと、そのプロファイルを指定します。

"vars": {
"INTERNAL_CLIENT": "{\"client\":\"github-connector\",\"profiles\":[\"pr-docs-check\",\"workflow-check\"]}"
}

クライアント名には、コネクターのトークンがいま持っている名前を使ってください。判定が同じクライアントの名前でログに残るため、冪等性、チェーン、判定ログから読むファクトが、これまでの続きとして動きます。プロファイルは GITHUB_CONNECTOR に並べたものです("*" は使えません)。

トークンが要らない理由は次のとおりです。InternalEngine には URL がありません。呼び出せるのは、同じ Cloudflare アカウントにあって、それへのバインディングを持つ Worker だけです。また、呼び出し元が誰かは、呼び出し元ではなくエンジンが決めます(INTERNAL_CLIENT)。その先は、呼び出しごとに、その HTTP のルートでトークンを確認したあとと同じコードが動きます(decide、get、resolve、close は、それぞれ POST /v1/decide、GET /v1/decisions/{id}、POST /v1/reviews/{id}/resolve、/close にあたります)。プロファイルの範囲、冪等性、判定ログも同じです(adapters.md、英語)。エンジンの /v1/ のパスに設けたエッジのルール(ファイアウォール、レート制限)は、内部呼び出しには働きません。

これは次のことを意味します。この Cloudflare アカウントに Worker をデプロイできる人は誰でも、InternalEngine へのバインディングを作り、INTERNAL_CLIENT のクライアントとして、そのプロファイルで、トークンなしで期限もなく動けます。判定の依頼、判定の読み取り、レビューの解決、そのクライアントの判定のレビューを閉じることができます。バインディングを持つ Worker はすべて同じクライアントです(判定の履歴も冪等性の範囲も 1 つです)。呼び出し元ごとに取り消すことはできず、古いトークンを取り消しても止まりません。内部呼び出しを止めるには、エンジンの設定から INTERNAL_CLIENT を削除してデプロイします。

既存のデプロイをトークンから内部呼び出しに切り替える手順は次のとおりです。

  1. エンジンの Worker を、INTERNAL_CLIENT(コネクターのトークンのクライアント名とプロファイル)を付けてデプロイします。

  2. コネクターの設定から POINTSMAN_URL を削除し、services のバインディングを追加して、コネクターをデプロイします。

  3. 新しいチェックが表示されることを確かめます(POINTSMAN_TOKEN が残っている間は、コネクターのログに警告が出ます)。そのあと、トークンのハッシュを調べ、トークンを取り消し、シークレットを削除します。

    ターミナルウィンドウ
    node engine/scripts/tokens.mjs list --remote --config wrangler.jsonc
    node engine/scripts/tokens.mjs revoke --hash <hash of the github-connector token> --remote --config wrangler.jsonc
    wrangler secret delete POINTSMAN_TOKEN --config github-connector.jsonc

    これは設定用リポジトリでのパスです(engine/ はエンジンのチェックアウト、github-connector.jsonc はコネクターの設定)。このリポジトリでは node scripts/tokens.mjs と --config connectors/github/wrangler.jsonc を使います。

元に戻すには、POINTSMAN_URL と新しいトークンを設定し、バインディングを削除します。

コネクターがアクションを実行するのは、デプロイが GITHUB_CONNECTOR で、プロファイルの判定のアクションを動作に対応づけた場合だけです。

{
"repos": ["owner/name"],
"profiles": ["pr-docs-check"],
"actions": {
"pr-docs-check": {
"light": { "do": "label", "labels": ["docs-only"] },
"review": { "do": "request_reviewer", "reviewers": ["octocat"] }
}
}
}
do設定動作
commenttext(1 から 1,000 文字の固定の文)文を判定 ID とともにコメントとして書きます。プルリクエストから取った文は書きません。
labellabels(1 から 10 個)ラベルを付けます。まだないラベルは GitHub が作るため、名前を確かめてください。
request_reviewerreviewers(1 から 10 個の GitHub のログイン名)その人たちにレビューを依頼します。
approveなし判定のもとになった head コミットを承認します。
keep_approvalなしGitHub では何もしません。アプリが head コミットを承認していれば、その承認を取り消さずに残します。「承認済み」という判定のためのものです。approve にもアクションを対応づけたプロファイルでだけ使えます。
copilot_reviewなしCopilot にプルリクエストのレビューを依頼します(後述)。

一覧はこれですべてです。デプロイはアクションを有効にできますが、追加はできません。動作を対応づけていない判定のアクションは、シャドーモードと同じくチェックだけになります。チェックには、どのアクションを実行したか、または実行しなかった理由が表示されます。

すべてのアクションに共通の決まりは次のとおりです。

  • head コミットごとに 1 回まで(プロファイルと種類ごと)。記録をアクションの前に書き、アクションのあとで閉じます。新しいコミットでは最初からやり直します。
  • 途中で止まった場合: 開いたまま残った記録は、GitHub に残った跡(マーカー付きのコメントや承認、ラベル、依頼したレビュアー)で決着させます。跡がなければ、10 分たってから実行し直せます。そのときコネクターは、新しいイベントがなくても見直します。
  • 人が先: 人がまだ判定を確認している間(プロファイルの people_check、review: pending)は、何もしません。人が回答したら、その回答がアクションになります(人に尋ねる)。
  • GitHub が拒否した場合(権限がない、依頼できないレビュアー): チェックに表示してログに残し、そのコミットでは再試行しません。混んでいる、止まっている場合は、あとで再試行します。
  • プロファイルと種類ごとに記録は 1 つ: 1 つのプロファイルで 2 つの判定のアクションを同じ種類に対応づけた場合(たとえば 2 つの label アクション)、コミットごとに、先に来たほうの 1 回だけ実行します。

approve には、さらに次の決まりがあります。

  • 判定したコミットにだけ承認します。直前にプルリクエストを読み直し、head が変わった、ドラフトになった、閉じられた場合は何もしません。承認ではそのコミットを指定します(commit_id)。
  • フォークからのプルリクエストは承認しません。
  • push のあとは、実行のたびに、古いコミットへの自分の承認を取り消します。取り消すのは自分が出したと記録した承認だけで、ほかの人のレビューは取り消しません。
  • 同じコミットへの後の判定が承認でなくなった場合(CI が失敗した、ファクトが変わった、人の確認が必要になった)、そのコミットへの承認を取り消します。その後に承認の判定が出れば、もう一度承認できます。承認が残る条件を参照してください。
  • 古い承認を取り消せない場合、その実行では何も承認しません。
  • 必須: ブランチのルールセット(またはブランチ保護)で「Dismiss stale pull request approvals when new commits are pushed」を有効にしてください。有効でないと、古いコミットへの承認が、コネクターの次の実行で取り消されるまでマージのための承認として数えられます。
  • 自分の承認は、自分のボットのアカウント(<app slug>[bot]。slug は、アプリのインストールについての GitHub の回答から取ります)によるマーカー付きのレビューです。ほかのアプリのレビューに同じマーカーがあっても、自分のものとは見なしません。
  • コードオーナー: アプリはコードオーナーになれません(CODEOWNERS に書けるのはユーザー、チーム、メールアドレスです)。ルールセットがコードオーナーのレビューを求め、変更されたファイルが CODEOWNERS の対象であれば、やはり人の承認が必要です。それでも、アプリの承認は必要な承認数に数えられます(アプリに「Contents: write」の権限があるとき。後述)。

copilot_review には、さらに次の決まりがあります。

  • 誰が依頼したかにかかわらず、コミットごとに Copilot のレビューは 1 回。 依頼する前に、コネクターはプルリクエストを読みます。Copilot がすでに依頼されているか、head コミットをすでにレビューしている場合(人やほかのプロファイルが依頼したとき)は、何も依頼せず、アクションは実行済みとして扱います。Copilot は、名前ではなくアカウントの ID で見分けます。
  • 結果を確かめる: GitHub は依頼を受け付けても、Copilot を追加しないことがあります(たとえば、そのリポジトリで Copilot のコードレビューが有効でないとき)。コネクターは、GitHub の回答に Copilot が含まれるときだけ実行済みとします。含まれなければ拒否として扱い、チェックに表示して、そのコミットでは再試行しません。
  • フォークからのプルリクエストでは依頼しません。外部の人の push で、組織の Copilot の利用枠が使われないようにするためです。閉じたプルリクエストでも依頼しません。
  • 判定したコミットにだけ: 直前にプルリクエストを読み直し、head が変わっていれば何も依頼しません。
  • わからない場合: Copilot がすでに依頼されたかどうかを読めないとき(プルリクエストのレビューが 1,000 件を超えるときも)は、何も依頼せず、10 分後にもう一度確かめます。
  • 費用: Copilot のレビューは、1 回ごとに Copilot のプレミアムリクエストを使います。アプリからの依頼がどこに請求されるかは GitHub が決めます。コネクターが依頼するのはコミットごとに 1 回までで、どのコミットでレビューするかはプロファイルが決めます。skip_drafts(チェーン)と、github_copilot を使うルール(たとえば reviews の数)で回数を抑え、組織で Copilot の予算を設定してください。
  • Copilot は、プルリクエストのコードと文章を読みます。copilot_review は、組織がそのリポジトリで Copilot のコードレビューを認めている場合にだけ対応づけてください。Copilot が書いた内容を、コネクターは読みません。
  • GitHub アプリからの依頼はまだ試していません(試したのは github-actions[bot] だけです)。有効にしたら、最初の依頼を確かめてください。

コネクターが承認するのは、head コミットごとに 1 回までです。そのコミットへの後の判定は、それぞれ承認を残すか取り消す(レビューを dismiss する)かのどちらかです。

同じコミットへの後の判定承認
approve に対応づけたアクション残る(2 回目の承認はしない。このコミットでは承認済み)
keep_approval に対応づけたアクション残る
それ以外のアクション(対応づけの有無にかかわらず。ask-person、review、pay、split、wait など)取り消す
人がまだ確認している判定(review: pending)。approve や keep_approval に対応づけたものも含む取り消す
その判定への人の回答(人に尋ねる)回答したアクションと同じ
テストの判定(test: true)、または有効な判定でない回答(安全性)取り消す
新しい head コミット(push)判定にかかわらず、古いコミットへの承認を取り消す

迷うときは承認を取り消します。承認が残るのは、上の 2 種類だけです。タイトルや説明を編集すると、同じコミットに新しい判定が出ます。リスクの区分が変われば、その判定はもう approve でも keep_approval でもないため、承認は取り消されます。

keep_approval が必要な理由は、承認によってプロファイルが見る内容が変わるからです。たとえば、Pointsman が承認したあとで CodeRabbit がレビューし、承認するルールが「CodeRabbit がまだレビューしていないこと」を条件にしているとします。すると、大事なことは何も変わっていないのに、次の判定はもう approve になりません。ファクト github_app_approval を使えば、プロファイルは、アプリがこのコミットをすでに承認したことを知り、そのための独自のアクションで答えられます。そのアクションを keep_approval に対応づけます。このルールは、承認するルールより前に、そして止めるルール(欠けたファクト、失敗したチェック、変更の要求)をすべて置いたあとに置きます。また、承認するルールが求めることを、承認そのものによって変わるファクト(ここでは、CodeRabbit がまだレビューしていないこと)を除いて、すべて求める必要があります。そうしないと、同じコミットでチェックが失敗しても承認が残ってしまいます。

facts:
- { name: pointsman, type: github_app_approval, at: $.pull_request, app: <the connector app's slug>, bot_id: <its bot's account id> }
rules:
- when: "facts.pointsman.missing == true or facts.ci.missing == true"
action: retry
- when: "facts.ci.failed == true"
action: ask-person
# … the other rules that block
- when: "facts.pointsman.approved_head == true and (decisions.risk.outcome == 'approvable' or decisions.risk.outcome == 'release' or decisions.risk.outcome == 'bump') and facts.coderabbit.changes_requested == false and facts.ci.green == true"
action: done
# … then the rule that approves

GITHUB_CONNECTOR では次のようにします。

"actions": { "pr-next-step": { "approve": { "do": "approve" }, "done": { "do": "keep_approval" } } }

コネクターには、どのルールがそのアクションを出したかはわかりません。keep_approval は、上のルールのように、承認を残すべきときにプロファイルが出すアクションにだけ対応づけてください。プロファイルがそのアクションをほかの場合(たとえばマージ済みのプルリクエスト)にも出すなら、その場合も承認が残ります。そのときは、「承認済み」のルールに専用のアクション(たとえば approved)を与え、それを対応づけます。

once_per_head(チェーン)を使うプロファイルは、同じコミットで再び判定を依頼されないため、後の判定で承認を残すことも取り消すこともありません。そのようなプロファイルには approve や keep_approval を対応づけられません(設定のエラーになります)。同じコミットでチェックが失敗したり変更が要求されたりしても、承認が取り消されなくなるためです。

アクションには、コネクターのアプリの権限 Pull requests: Read and write が必要です。読み取りだけでは、トークンはプルリクエストに書き込めず、すべてのアクションが失敗します(チェックに表示されます)。アプリの承認をルールセットの必要な承認数に数えさせるには、アプリに Contents: Read and write も必要です。GitHub は、書き込みの権限を持つレビュアーの承認だけを数えるからです(#174 で確認、2026-10-23)。コネクターのトークンが contents を求めるのは、差分を読むとき(contents: read、チェーン)と、誰がコミットを push したかを読むとき(プルリクエストの中での回答)だけで、書き込みには使いません。

判定に人の確認が必要なとき(review: pending。プロファイルの people_check)、コネクターはその判定を、その head コミットへの問いとして保持します。あわせて、判定のもとになったものも保持します。タイトル、説明、ベースブランチ、ドラフトかどうか、プロファイルの設定と対応づけたアクション、そして必要とする判定の内容(そのアクションとルール、人がそれに回答していればその回答。判定 ID は含みません。必要とするプロファイルは実行のたびに新しく判定されるためです)です。同じ head での後の実行では、そのどれも変わらない間は、そのプロファイルについて Pointsman にもう一度判定を依頼せず、判定を読み直します(GET /v1/decisions/{id}、または内部呼び出しの get)。そのため、イベントがいくつ届いても、Pointsman のレビューキューにある問いは、プルリクエスト、プロファイル、head コミットごとに 1 つです。

  • 人が回答する場所: Pointsman のレビューキュー(POST /v1/reviews/{id}/resolve、reviews.md、英語)、またはデプロイで有効にした場合はプルリクエストの中です(プルリクエストの中での回答)。キューで出た回答は、そのプルリクエストの次のイベントでコネクターが知ります。再確認でも読みますが、それは、人に尋ねるアクションを recheck.actions に並べたプロファイルだけで、読むたびに recheck.max の回数に数えます。
  • 回答できる人: そのプロファイルを含む Pointsman のトークンを持つ人(または InternalEngine へのバインディングを持つ Worker)なら誰でも回答できます。コネクター自身のクライアントに限りません。そのような回答でも、プルリクエストの中での回答のような確認なしに、対応づけたアクション(approve も)が実行されることがあります。このように尋ねるプロファイルには people_answers を書き、意図した回答だけができるようにしてください(profile-format.md)。
  • 回答がアクションになる。 回答は、判定自身のアクションと同じアクションの表と決まりを通ります。実行するのは対応づけたアクションだけで、head コミットごとに 1 回までです。approve は、問いの対象だったコミットにだけ承認します。回答は、そのもとになったものが変わらない間は有効で、読み直すことも、プロファイルに判定を依頼し直すこともしません。
  • もとになったものが変わったとき(タイトルや説明の編集、ベースの変更、ドラフトかどうか、設定の変更、必要とする判定が別のアクションで新しく出た、またはそれに人が回答した): 開いた問いを Pointsman で閉じ、プロファイルに判定を依頼し直します。そのため、アクションが古い回答にもとづくことはありません。古い問いにすでに出ていた回答では、何もしません。
  • 表示: 問いが開いている間は、チェックのタイトルの末尾が : a person checks first になり、詳細には人の確認を待っていることを表示します。回答のあとは、タイトルが回答と : a person's answer になります。判定コメントでは、「Decision」の列に回答を、「Set by」の列に「a person」を表示し、詳細には、ルールが人に尋ねた理由と、その下に回答を表示します。誰が回答したかを表示するのは、プルリクエストの中で出た回答だけです。「answered by 」と @ を付けずに表示するので、誰にもメンションしません。キューで出た回答では、誰の名前も表示しません。
  • 何もしない場合: アクション名でない回答、または保持した判定とは別の判定が読み直された場合(invalid answer: nothing done)。Pointsman が読み取りを拒否した場合、たとえば 403(answer could not be read: nothing done)。どちらも問いはそのまま残ります。
  • push したとき、プルリクエストを閉じたとき、プロファイルを設定から外したとき: コネクターは、該当する開いた問いを Pointsman で閉じます(POST /v1/reviews/{id}/close、または内部呼び出しの close)。閉じた問いへの遅い回答は拒否され(409 not_pending)、実行されることはありません。閉じるのに失敗しても(Pointsman が混んでいる、止まっている、コネクターより古い)、実行は止まりません。ログに残し、後の実行で、5 分に 1 回まで、最大 10 回または 1 日の間、再試行します。ほかの方法で Pointsman で閉じられた問いや、なくなった問いは、1 回だけ尋ね直します。
  • エンジンのバージョン: コネクターには、GET /v1/decisions/{id} と POST /v1/reviews/{id}/close を持つエンジンが必要です。プルリクエストで回答するには、回答を確定するルートも必要です(内部呼び出しでは InternalEngine.get、close、resolve)。エンジンを先にデプロイしてください。内部呼び出しで古いエンジンを使うと、すべての回答が拒否されます(“Pointsman could not be reached; answer again”)。
  • プロファイルの新しいバージョンには、問いが開いている間は気づきません。コネクターはプロファイルのバージョンを知らないため、問いは、回答されるか上のどれかが変わるまで、尋ねたときのバージョンのままです。

人は、開いた問い(人に尋ねる)にプルリクエストの中で回答できます。チェックのボタンで回答するか、デプロイで有効にした場合はコメントで回答します。どちらも既定では無効です。デプロイでは GITHUB_CONNECTOR で有効にします。

"answers": {
"profiles": { "pr-next-step": ["approve", "reject"] },
"command": false
}
  • profiles: プルリクエストの中で回答できるプロファイルと、それぞれで示す回答です(アクション名を 1〜10 個。review は使えません)。どの回答も、プロファイルの people_answers にも含まれている必要があります。ほかの回答は Pointsman が拒否し、チェックにそのことを表示します。このように尋ねるプロファイルには people_answers を書き、意図した回答だけができるようにしてください(profile-format.md)。コネクターには people_answers が見えません。ここに書いてあっても people_answers にない回答にもボタン(とコマンド)が付き、Pointsman がその回答を拒否します。どのアクションにも対応づけていない回答(たとえば reject)は、問いに回答するだけです。
  • ボタン: 回答が 3 個以内で、どれも 20 文字以内のプロファイルでは、問いが開いている間、そのチェックに回答ごとのボタンを付けます。回答がもっと多いか、もっと長い名前があると、ボタンは付かず、コマンドを有効にする必要があります(有効にしないと設定を拒否します)。
  • command: コメントのコマンド /pointsman <answer> <commit> です。true にしない限り無効です。

ボタン。 ボタンのラベルは回答そのもので、説明は「Pointsman: answer 」です。識別子は、回答の順番と判定 ID の先頭 8 文字です(たとえば 1-3f2a9c01)。プルリクエストからは何も入れません。クリックが有効になるのは、現在の head に対するコネクター自身のチェックで、そのチェックが示す問いについてだけです。問いに回答があったとき、head が動いたとき、プルリクエストを閉じたときには、ボタンを取り除きます。チェックを、ボタンの空のリストで更新します(これでボタンが消えるかどうかは、GitHub で確かめる必要があります。GitHub のドキュメントには書かれていません)。

コマンド。 コメントが有効になるのは、コメント全体(前後の空白を除く)が、ちょうど 1 行の /pointsman <answer> <commit>(たとえば /pointsman approve 3f2a9c01b7d4)であるときだけです。小文字で、各部分の間の空白は 1 つ、<answer> は開いた問いが示す回答のどれか、<commit> は問いの対象の head コミットの先頭 12 文字です。コミットを書くことで、回答は本人が見たものに結びつきます。コマンドを受け付けるのは、書かれたコミットについての問いだけです。そのため、古いコミットに向けて書いたコマンドは、新しいコミットについての問いへの回答になりません。遅れて届いた場合や、順番が入れ替わって届いた場合も同じです。先頭 12 文字が同じ 2 つのコミットはありえます(作成者が意図して作ることもできます)。そのため、コネクターはプルリクエストの直近 100 個の head を保持し、その 12 文字で始まる head が 2 つ以上あるコマンドは拒否します(チェックにそのことを表示します)。その場合はボタンで回答してください。ボタンは、先頭の文字ではなく、チェックと判定に結びついています。開いた問いのないコミットへのコマンドでは何も起きません(ログに残すだけです)。コミットを書いていないコメントでも何も起きません。ほかのテキスト、引用(>)、コードブロック、大文字を含むコメントはコマンドではなく、何も起きません。コメントの編集や削除でも何も起きません(一度出た回答はそのままです)。2 つ以上の開いた問い(2 つのプロファイル)が示す回答のコマンドは拒否します。ボタンで回答してください。問いが開いている間は、判定コメントとチェックに、コミットを含めたコマンドをそのまま表示します。

回答できる人。 回答が届くたびに GitHub で確かめます。ある回答で確かめたことを、次の回答に持ち越すことはありません。人はログイン名ではなくアカウント ID で比べます(ログイン名は変更できるためです)。

  • 人であること。GitHub がイベントを User として送ったものだけで、ボット、アプリ、削除されたアカウント(ghost)は対象外です。
  • リポジトリへの書き込みの権限があること。GitHub のコラボレーターの権限が write か admin です(maintain は write として扱われます)。
  • プルリクエストの作成者ではないこと。
  • head コミットを push した人ではないこと。リポジトリのアクティビティで、head ブランチへの最新の push がこのコミットを push したものである必要があり、その push をした人は拒否します。これを読めないときは、回答を拒否します。見るのは最新の push だけです。プルリクエストのそれより前のコミットを push した人は回答できます。

ある人の権限について GitHub が「not found」と答えた場合は、書き込みの権限がないものとして扱います。

ほかに確かめること。 プルリクエストがまだ開いていて、フォークからのものではなく、その head が問いの対象のコミットであること。問いがまだ開いていること。これは、コネクター自身の記録と、コネクターが読み直したときの Pointsman の両方で確かめます。そのうえで、コネクターは問いを github:<login> (<account id>) として解決します。correct は送らないため、この回答がモデルの回答へのフィードバックになることはありません(reviews.md、英語)。Pointsman が問いを解決するのは 1 回だけです。先に誰かが回答していれば(レビューキューで、または同時に別の人が)、その回答が有効で、コネクターはそちらにもとづいて動きます。

その後。 回答は、どの判定とも同じアクションの表と決まりを通ります。実行するのは対応づけたアクションだけで、head コミットごとに 1 回までです。approve は、問いの対象だったコミットにだけ承認します。チェックのタイトルは回答と「: answered by 」になり、判定コメントでは「Set by」の列と詳細に「answered by 」を @ なしで表示します。

回答を受け付けないとき。 何も解決しません。コネクターは理由をログに残し(判定 ID を含め、ログイン名は含めません)、チェックに短い決まった一文を加えます。たとえば「An answer was not taken: the author of the pull request cannot answer.」です。返信のコメントは書きません。そのため、コメントがたくさん来ても、コネクターがたくさんコメントを書くことはありません。GitHub や Pointsman を読めないときも回答を拒否します(「answer again」)。自動で再試行はしません。回答済みの問いへの 2 回目の回答、古い head のチェックでのクリック、どの開いた問いも示さない回答のコマンドでは、何も起きません。

上限。 プルリクエストごと、時計の 1 時間(:00 から :59 までの決まった枠)あたりの上限は、1 つのアカウントからの回答が 3 件、GitHub を呼ぶ回答(プルリクエストの読み取り、または開いた問いに結びつかない回答についてのチェックへの一文)が 30 件、プルリクエストの確認(開いている、head、作成者、フォーク)を通って権限を調べる回答が 10 件です。そのため、書き込みの権限のない人が、回答できる人より先に権限の確認を使い切ることはできません。GitHub や Pointsman の失敗で拒否した回答(「answer again」)は、その人の 3 件に数えません。どの上限でも、超えた最初の回答のときだけ、チェックに一文を加えます。それ以降は何も加えません。既知の制限として、多くのアカウントが合わせて(10 以上のアカウントから 30 件)回答すると、その時間の読み取りを使い切り、次の時間まで誰もプルリクエストの中で回答できなくなります。レビューキューはそのまま使えます。待たせておける回答は一度に 5 件までで、それを超えた分は捨てます。GitHub のレート制限は、コネクターのほかの処理と共有します。受け付けた回答はそのプルリクエストの実行を始めるため、ほかのプロファイルについて Pointsman に判定を依頼することがあります。回答にもとづいて動くのは、問いのもとになったものが問いから回答までの間に変わらなかったときだけです(その間にタイトルを編集すると、次の実行で問いを閉じ、その回答では何もしません)。

プルリクエストの中で回答できないもの:

  • フォークからのプルリクエスト。コネクターのトークンが読めるのは 1 つのリポジトリだけなので、フォークのブランチに誰が push したかがわからず、回答を拒否します。
  • GitHub のレビュー。承認や変更の依頼は、問いへの回答になりません。
  • コードオーナー。回答できるかどうかは、書き込みの権限だけで決まります。

個人データ。 誰が回答したか(ログイン名とアカウント ID)は、判定ログに resolved_by として、ログが判定を残す間保存されます。期間は運用者が決めます。GET /v1/decisions/{id} と replay で見え、クライアントがコールバックを設定していればそこにも送られ、プルリクエストには「answered by 」と表示されます。

有効にするには。 GitHub で、コネクター用アプリの設定を次のようにします。

  • ボタンには、「Checks: Read and write」(すでにあります)、「Contents: Read」、イベント「Check run」の購読。コネクターは、人の権限を「Metadata: Read」で読み、head を誰が push したかをリポジトリのアクティビティから読みます。GitHub はアクティビティを「Contents: Read」があるときだけ返します(GitHub の GitHub アプリに必要な権限、英語)。回答を有効にすると、コネクターのトークンは contents: read を求めます。contents への書き込みは求めません。
  • コマンドには、さらに「Issues: Read」と、イベント「Issue comment」の購読。
  • リポジトリのルールセットでは、「Dismiss stale pull request approvals when new commits are pushed」と「Require approval of the most recent reviewable push」を残してください。回答を有効にするのは、書き込みの権限を持つ人なら誰でも、対象のプルリクエストを承認してよいリポジトリだけにしてください。

プルリクエストごとに 1 つのコメントで、選んだ各プロファイルの最新の判定を、鉄道の路線図のように表示します。既定では無効です。デプロイが GITHUB_CONNECTOR で、表示するプロファイルとともに有効にします。

{
"repos": ["owner/name"],
"profiles": ["pr-risk", "pr-next-step"],
"comment": { "profiles": ["pr-risk", "pr-next-step"] },
"signals": {
"pr-risk": { "approvable": { "signal": "green", "text": "low risk" }, "release": "green", "risky": "red", "incomplete": "yellow" },
"pr-next-step": { "approve": "green", "done": "green", "ask-person": "red", "wait": "yellow", "queue": "yellow" }
}
}

このプルリクエストで、pr-risk がルール 3 によって approvable と判定し、pr-next-step がルール 8 によって ask-person と判定したときのコメントは次のとおりです。

<!-- pointsman decision-comment -->
### 🚦 Pointsman
| Profile | Signal | Decision | Set by |
| --- | --- | --- | --- |
| `pr-risk` | 🟢 low risk | `approvable` | rule 3 |
| `pr-next-step` | 🔴 needs a person | `ask-person` | rule 8 |
```text
draft ready reviewed approved merged
━━━●━━━┿━━━┿━━━●━━━┿━━━┿━━━○━━━┿━━━┿━━━○━━━┿━━━┿━━━○━━━
▲ ready for review
```
<details><summary><code>pr-risk</code>: why <code>approvable</code></summary>
```text
pr-risk @ 1a2b3c4
┃ facts ✓ 1 read
┃ AI matches_title: yes
┃
┡━━━┿━━━┿━━━ approvable ← rule 3
│
└───┼───┼─── later rules and the default: not taken
```
</details>
<details><summary><code>pr-next-step</code>: why <code>ask-person</code></summary>
```text
pr-next-step @ 1a2b3c4
┃ facts ✕ 1 of 3 missing: ci (timeout)
┃ AI – none asked
┃
┡━━━┿━━━┿━━━ ask-person ← rule 8
│
└───┼───┼─── later rules and the default: not taken
```
</details>
<sub>For commit 1a2b3c4. One comment, updated when a decision changes; the check runs `Pointsman: <profile>` have the decision ids.</sub>

同じ判定のチェック Pointsman: pr-next-step のタイトルは 🔴 ask-person (shadow)(アクションを対応づけていれば 🔴 ask-person)で、詳細には路線とその分岐器が表示されます。

表示する内容は次のとおりです。

  • シグナル: プロファイルごとに 1 つです。色の横には必ず言葉を添えます(スクリーンリーダーはその言葉を読み上げます)。シグナルを参照してください。

  • 路線: プルリクエストの進み具合です。駅名はレールの上に並びます。列車(▲)は自分の駅の下に止まり、その駅とそれより前の駅は塗りつぶされ(●: 通過済み)、先の駅は空です(○)。通過済みとは列車が通り過ぎたという意味で、その段階が実際にあったという意味ではありません。最初からレビュー可能として作られたプルリクエストはドラフトだったことがなく、レビューなしでマージされたプルリクエストでも reviewed と approved は塗りつぶされます。列車の駅は、実行ごとに GitHub から読みます。

    • merged: プルリクエストがマージされました。マージされずに閉じられた場合は、路線の下に closed without merging と表示し、列車は置かず、どの駅も塗りつぶしません。
    • draft: ドラフトです(レビューは読みません)。
    • approved: GitHub 自身のレビューの判断(GraphQL の reviewDecision)が APPROVED です。これは、リポジトリのルール(ブランチ保護またはルールセット)のもとでマージボックスが表示する内容です。誰の承認を数えるか、いくつ必要か、新しいコミットで取り消すかはルールが決めます。そのため、ボットの承認(たとえば CodeRabbit の承認)は、ルールが数える場合に数えられ、公開リポジトリで通りすがりの人がした承認は数えられません。CHANGES_REQUESTED と REVIEW_REQUIRED は approved になりません。レビューのルールがないリポジトリでは、GitHub は判断を返しません(null)。その場合、列車は approved には止まらず、reviewed までです。キャプションは、コネクター自身の最新のレビューが現在の head への承認なら approved by Pointsman on this commit、ほかの人の最新のレビューがそうなら approved on this commit、それ以外は approved だけです(ルールによっては、push のあとも古いコミットへの承認が残ります)。
    • reviewed: 作成者以外の誰かが、現在の head コミットをレビューしました(コメント、変更の要求、承認のどれか。取り消されたレビューは数えません)。
    • ready: 上のどれでもありません。

    コメントには、レビュアーもレビューの本文も表示しません。レビューの判断と最新の 100 件のレビューは、1 つの GraphQL クエリで読みます(読み取りなので、トークンの Pull requests: Read で足ります)。読むのは、路線を描く実行ごとに 1 回(コメントの有無にかかわらず)と、その実行で承認したか承認を取り消したあとにもう 1 回です。ドラフトや閉じたプルリクエストでは読みません。読めない場合(HTTP のエラー、レート制限などの GraphQL のエラー、プルリクエストを読んだあとの push)、実行は続けます(ログに残します)。列車は、この head コミットで前回読んだときの位置に、それもなければ ready に止まります。コネクターが自分の承認を取り消したあとは、approved には止まりません。チェックの路線は、そのチェックを書いた時点で描きます。そのため、同じ実行の後のプロファイルが承認した場合、前のプロファイルのチェックは次の実行まで承認前の路線を表示し、コメントは approved を表示します。

  • 分岐器: 判定がその線路に進んだ理由で、判定そのものから描きます。線路はまずファクト(いくつ読んだか、どれが欠けたか、エンジンが示した理由)と AI の回答の値(はい・いいえ、選んだ選択肢、レベル。確率は表示しません)を通り、分岐器に着きます。ルールは順に確かめ、最初に一致したルールがアクションを決めます。そのルールの分岐器で列車は太い線路(━)に入り、アクションとルールが表示されます。細い線路(─)は通らなかったほうです。どのルールにも一致しなければ、ルールの線路が細いほうになり、列車は既定のアクションへ進みます。描くのはこの 2 本だけです。ほかのルールや既定のアクションがどのアクションを出したはずかは、判定に含まれないからです。人がまだ判定を確認している場合は、その旨を 1 行で示します。内容はチェックに表示するものと同じで、ファクトの値やプルリクエストのテキストは含みません。

  • 描き方: どの等幅フォントでも揃うように、名前、ルール、理由は、レールのあとの行末にだけ置きます。図に絵文字は使いません(シグナルは表にあります)。レールの行は、テキストまですべて罫線文字だけで、どれも日本語のフォントが 2 文字分の幅で描きうる種類です。そのため、そのようなフォントでは図が横に広がるだけです。そのようなフォントでは、駅名と列車(空白のあと)が駅から少しずれることがありますが、レール上の塗りつぶした駅は常に正しい位置です。どの行も 60 文字分を超えないため、スマートフォンでも折り返しません。長い一覧は複数行に分け、それでも長すぎる名前は … で切ります(表にはプロファイル名とアクション名を、チェックの概要にはファクト名を、省略せずに載せます)。

決まりは次のとおりです。

  • 1 つのコメントをその場で更新し、判定ごとに新しいコメントは書きません。コネクターは、1 行目の隠しマーカー <!-- pointsman decision-comment --> でコメントを探し、それも自分のボットのアカウント(<app slug>[bot]。承認と同じ)のコメントの中だけで探します。人やほかのアプリのコメントに同じマーカーがあっても、読むことも編集することもありません。コメントが削除されていれば、次の変更で新しく書きます。
  • 変わったときだけ: コメントには判定 ID も時刻も含めません。そのため、アクション、ルール、ファクト、回答が同じなら、新しい判定が出てもコメントは変わらず、書き込みもしません。書き込みは実行ごとに多くて 1 回です(更新、または最初のコメント)。
  • 書き込みの前に記録: アクションと同じく、書き込み中に実行が止まると記録が開いたまま残り、次の実行で書き直します(先にマーカーでコメントを探すため、2 つ目ができることはありません)。
  • 閉じたとき(マージされたかどうかにかかわらず): コメントが有効なら、コネクターはプルリクエストの closed イベントも受け取ります。このときは Pointsman に判定を依頼せず、チェックを書かず、レビューも読みません。コメントを 1 回だけ更新し、路線を merged か closed without merging にして、最後に表示していた判定(それが出たコミットのもの)を載せます。閉じたプルリクエストへのその後のイベントでは何も変わらず、書き込みもしません。GitHub が混んでいて書き込みに失敗した場合は、その実行自体が再試行します(後のイベントは来ないからです)。まだコメントがなければ、書きません。再び開かれたプルリクエストは、通常どおり実行します。
  • 新しい head コミットでは、判定を表示しない状態から始めます。そのコミットで判定を依頼されていないプロファイル(skip_drafts を指定したドラフト)は、判定が出るまで載せません。
  • テストの判定と無効な回答(安全性)は、コメントに何も載せません。コメントには、このコミットでのそのプロファイルの最後の本当の判定が残ります。例外が 1 つあります。そのような回答によってコネクターがこのコミットへのそのプロファイルの承認を取り消した場合(承認が残る条件)、そのプロファイルの行を削除し(最後の判定はもう有効ではないため)、レビューを読み直します。列車は、いまの GitHub の状態が示す駅(reviewed、またはほかの承認が数えられるならそのまま approved)に移ります。その読み直しに失敗した場合、列車は approved には止まりません。
  • 表示するものがなくなった場合(すべての行を削除した、または新しいコミットでまだどのプロファイルも判定していない。たとえば skip_drafts を指定したドラフト): 既存のコメントを、路線とともに短い中立の文「No decision to show for commit <commit>」に更新します。コメントは削除しないため、プルリクエストには 1 か所に 1 つのコメントが残ります。まだコメントがなければ、書きません。
  • 失敗しても、実行やアクションは止まりません。GitHub が拒否した場合(権限がない)は、ログに残し、その実行のチェックに書き添え、コメントの文が変わるまで再試行しません。混んでいる、レート制限にかかった場合は、チェックに書き添え、次の実行で書きます。
  • プルリクエストからは何も載せない: コメントには、タイトル、説明、差分を表示しません。プロファイル名とアクション名は確認済みの名前です。エンジンが示すファクトの理由は、英数字と簡単な句読点だけの 40 文字に切ります。AI の回答の選択肢は、単純な名前のときだけ表示します。そして、すべてをコードブロックに入れます。そのため、プルリクエストのテキストがコメントに Markdown、HTML、リンク、メンションを加えることはできません。

comment.profiles には、profiles のうち 1 から 10 個のプロファイルを指定し、profiles の順に表示します。コメントには、コネクターのアプリの権限 Pull requests: Read and write が必要です(プルリクエストへのコメントは Issue のコメントだからです)。その場合、コネクターのトークンはこの権限を求めますが、対象はやはり 1 つのリポジトリだけです。

判定にはそれぞれシグナルが付きます。赤(人かレビューが必要)、黄(待ち)、緑(進めてよい)のどれかです。デプロイは、プロファイルとアクションごとに signals で指定します。色だけ("red")か、色とその横に表示する言葉({ "signal": "red", "text": "look at the code" }。英字、数字、空白と , . ' ( ) - で 1 から 40 文字なので、リンクや絵文字のコードは使えません)のどちらかです。シグナルはコメントと、すべてのチェックのタイトルで使います。コメントを無効にしていても使います。

指定がなければ、次の既定値を使います。

アクションシグナル
review、ask-person🔴 needs a person
wait、in-progress、queue、retry🟡 waiting
approve、pass、done🟢 go
それ以外🟡 decided: <action>
人がまだ確認している判定(review: pending)🔴 a person checks first

プロファイルは、ほかのプロファイルの判定を前提にできます(decision-chains.md、英語)。たとえば、1 つ目のプロファイルが AI への質問でプルリクエストをリスクの区分に分け、2 つ目のプロファイルがルールだけで、その区分とレビューの状態から次の段階を決めます。GITHUB_CONNECTOR にプロファイルごとの runs があれば、コネクターはこのようなチェーンを実行します。

{
"repos": ["owner/name"],
"profiles": ["pr-risk", "pr-next-step"],
"runs": {
"pr-risk": { "skip_drafts": true, "once_per_head": { "rerun": ["incomplete"] }, "diff": 30000 },
"pr-next-step": { "requires": { "risk": "pr-risk" }, "recheck": { "actions": ["wait", "queue"], "minutes": 10, "max": 12 } }
}
}
キー動作
requires同じ実行の前のプロファイルの判定を、プロファイルの requires にある名前で、decisions として送ります。指定できるのは、このプロファイルより前に並べたプロファイルだけです。前のプロファイルに判定を依頼しなかった場合(skip_drafts を指定したドラフト)は何も送らないため、プロファイルではその前提を optional にしておきます。
skip_draftsプルリクエストがドラフトの間は判定を依頼しません(作業中のものにモデルを呼びません)。
once_per_headhead コミット、タイトル、説明、ベースブランチ、ドラフトかどうか、前提の判定、そのプロファイルの runs の設定と actions の組み合わせごとに、1 回だけ判定を依頼します。後の実行では、その判定を依頼し直さずに使います(requires にも使います)。rerun には、次の実行で依頼し直すアクションを指定します。たとえば、欠けたファクトのためのルールのアクションです。判定を保持するのは、そのチェックを書き、アクションを実行した(または対応づけたアクションがない)あとだけで、人が確認している間は保持しません。プロファイルの新しいバージョンは、次のコミットか編集から使います。新しいコミットなしに変わるファクト(チェック、レビュー)は、保持した判定では読み直しません。そのため、答えがコミットだけで決まるプロファイルに使ってください。
diffプルリクエストの unified 形式の差分を、状態に diff として加えます。この文字数(1,000 から 100,000)で切り詰め、diff_truncated を付けます。GitHub は、非常に大きなプルリクエストの差分を返しません。また、非公開のリポジトリの差分は、その内容を読めるトークンにだけ返します(アプリの Contents: Read-only。その場合コネクターはこの権限を求めます)。読めないときは diff が空で diff_truncated が true になり、実行は続きますが、そうして出た once_per_head の判定は保持しません。diff を読むプロファイルは、diff_truncated: true を「わからない」として扱い、厳しく判定する必要があります。差分が空でも、変更がないとは限りません。差分を読んだあとに head を読み直し、その間に push があれば実行し直します。差分は、状態のほかの部分と同じくプロファイルのモデルに渡ります。判定ログに残るのは、プロファイルが状態を残す場合(log.store_state)だけです。差分は作成者が書いたテキストです。タイトルと同じく、慎重に作ったプロファイルでは、AI の回答を厳しくする方向にしか働きません。
recheck判定のアクションが actions のどれかである間は、新しいイベントがなくても minutes(5 から 120)分後に判定を依頼し直します。head コミットごとに多くて max 回です(既定は 12)。たとえば無料のレビューを待つような、待つ判定のためのものです。再確認も 1 回の実行で、保持した判定を除くすべてのプロファイルに依頼し直します。once_per_head と組み合わせる場合は、actions を rerun にも入れる必要があります。

runs がなければ、これまでどおり、実行のたびにすべてのプロファイルに、タイトルと説明を付けて判定を依頼します。

プルリクエストのテキストを読む部分(AI への質問をするエンジン)は、書き込めるキーを持ちません。そのため、設計のとおり、デプロイには 2 つのアプリがあります。

アプリ使う部分権限(リポジトリ)Webhook
ファクト用(読み取りのみ)Pointsman の Worker が GitHub のファクトに使う(FACTS_GITHUB: app)Read: Metadata、Contents、Pull requests、Checks、Commit statuses、Actions、Custom properties。Organization: Members(read)なし
コネクター用コネクターの WorkerRead and write: Checks。Read: Pull requests(アクションと判定コメントには read and write)、Metadata。diff を使うプロファイルが非公開のリポジトリにあるときと、プルリクエストの中での回答には Contents の read(チェーン)。承認を数えさせるには Contents の read and write(アクション)。コメントのコマンドには Issues の read(プルリクエストの中での回答)on: Pull request、Pull request review、Check suite。ボタンには Check run、コマンドには Issue comment

補足:

  • ファクトのトークンも権限を絞ります。エンジンは GitHub に、ファクトが使う権限(上の表のもの)だけを、それも read だけで持つトークンを求めます。そのため、ファクト用のアプリにもっと多くの権限や書き込みの権限があっても、エンジンが受け取るトークンは必要なものを読めるだけです。
  • GitHub アプリは非公開のリポジトリのチェックを読めます。fine-grained personal access token は読めません(Checks の権限がないため)。そのため、トークンを使う場合、非公開のリポジトリではファクト github_checks が unavailable になります。
  • Members(organization)が必要なのは、CODEOWNERS のチームを数えるとき(author_sole_code_owner)だけです。Custom properties は github_property のためだけ、Actions は github_checks(必須のワークフロー)のためだけです。
  • どちらのアプリも、コネクターが扱うリポジトリにだけインストールしてください。

コネクターの Worker(connectors/github/wrangler.jsonc、英語):

名前意味
GITHUB_APP_IDコネクター用アプリの ID(数値)
GITHUB_APP_PRIVATE_KEYシークレット: コネクター用アプリの秘密鍵(GitHub が出力したままの PEM)
GITHUB_WEBHOOK_SECRETシークレット: アプリに設定した webhook のシークレット(20 文字以上)
GITHUB_CONNECTORJSON: { "repos": ["owner/name", …], "profiles": ["pr-docs-check", …] }。任意で "actions"(アクション)、"runs"(チェーン)、"comment"(判定コメント)、"signals"(シグナル)、"answers"(プルリクエストの中での回答)
POINTSMAN_URLPointsman のベース URL(https)。POINTSMAN とは併用しません
POINTSMAN_TOKENシークレット: コネクターのクライアント用の Pointsman API トークン。使えるのはそのプロファイルだけです。POINTSMAN を使うときは不要です
POINTSMAN上の 2 つの代わりに使う、エンジンの Worker のエントリーポイント InternalEngine へのサービスバインディング(内部呼び出し)
PULL_REQUESTSDurable Object のバインディング(クラス PullRequest)

Pointsman の Worker でファクト用アプリからファクトを読むには、FACTS_GITHUB: "app"、FACTS_GITHUB_APP_ID、シークレット FACTS_GITHUB_APP_PRIVATE_KEY を設定します。内部呼び出しには、INTERNAL_CLIENT に JSON {"client": "github-connector", "profiles": ["pr-docs-check", …]} を設定します。

  1. 2 つのアプリを作ります(GitHub で、組織の Settings → Developer settings → GitHub Apps → New GitHub App)。権限は上の表のとおりにし、「Only on this account」を選びます。ファクト用アプリには webhook を設けません(「Active」をオフにします)。コネクター用アプリの webhook の URL は https://<connector Worker>/webhook で、シークレットは openssl rand -hex 32 で作ります。
  2. それぞれのアプリで秘密鍵を生成し、それぞれのアプリをリポジトリにインストールします。
  3. コネクター用の Pointsman のトークンを作ります: node scripts/tokens.mjs create --client github-connector --profiles pr-docs-check,workflow-check --remote。エンジンの Worker が同じアカウントにあれば、代わりに内部呼び出しを使えます。その場合トークンは要りません。
  4. シークレットは wrangler secret put <NAME> で設定します(コネクターでは --config connectors/github/wrangler.jsonc を付けます)。シークレットは wrangler にだけ貼り付け、ファイルやチャットには貼り付けないでください。
  5. コネクターの Worker を wrangler deploy --config connectors/github/wrangler.jsonc でデプロイし、続いて Pointsman の Worker を FACTS_GITHUB: "app" を付けてデプロイします。
  6. プルリクエストを作成または更新します。チェック Pointsman: <profile> が、結論 neutral で表示されます。
  • neutral 以外のチェックの結論(たとえば、止めるべき判定に failure): 設計では未定のままです。
  • 有料のレビューと CodeRabbit へのコマンド(設計にある pay、coderabbit-approve、check、request-review): アクションの一覧に入っていません。CodeRabbit は GitHub アプリからのコマンドを無視するため(pointsman#220)、人かエージェントが投稿します。Copilot のレビューは copilot_review です。
  • コミットのステータス。 status イベントが指すのはコミットで、プルリクエストではないため、実行は始まりません。チェックスイートの完了では始まります。
  • 2 つのプルリクエストにある 1 つのコミット(1 つのブランチから 2 つのベースへ)には、それぞれからチェックが付きます。
  • フォークからのプルリクエスト。 GitHub は、フォークのチェックスイートにプルリクエストを示さないため、実行を始めるのはプルリクエスト自身のイベントだけです。
  • タイマーによる判定のし直しは、recheck を指定したプロファイル(チェーン)だけです。ほかのプロファイルは、次のイベントで判定し直します。
  • プルリクエストの中での回答のうち、フォークからのもの、コードオーナーによるもの、GitHub のレビューによるもの(プルリクエストの中での回答)。

Geolonia のオープンソースプロジェクト · このサイトについて