目次
Connector

GitHub

GitHub Connectorは、リポジトリのIssue、Pull Request、コメント、コミット、ファイルを読み、Issueとコメントを書き戻し、ファイルをコミットし、マージ、クローズ、ラベル付け、レビュー依頼を行います。プログラムはこれを使って、LLMでIssueをトリアージし、リポジトリのIssueを他のシステムと揃え、生成したファイルを公開します。

読み取り元

  • github.issues — リポジトリのIssue。number、node_id、title、state、state_reason、body、author、assignees と labels(配列)、milestone、comments(コメント数)、created_at、updated_at、closed_at、url。キーは number です。
  • github.pull_requests — Pull Request。Issueと同じ形に base、head、draft、mergeable、merged_at、merged_by、review_decision が加わります。キーは number です。
  • github.issue_comments — IssueとPull Requestのコメント。id、subject_number、author、body、created_at、updated_at。キーは id です。
  • github.repositories — 接続のリポジトリと、組織のトークンなら組織のリポジトリ。name、full_name、default_branch、visibility、pushed_at、open_issues。キーは full_name です。
  • github.commits — ブランチのコミット。sha、message、author、committed_at、parents、files(パスと変更の種類の配列)。ブランチと期間で絞り込みます。キーは sha です。
  • github.files — あるrefのファイル。resource、version(blobのsha)、path、size、media_type、ref。LIST_FILES(github, glob => r'docs/**/*.md') は、既定のブランチに対する同じ一覧です。
  • github.contents — ファイルのバイト列を content_ref として読みます。resource、version、media_type、content。github.files から resource と version でJOINしてください。ConnectorはJOINで指定されたblobだけを取得します。

接続は1つのリポジトリ(owner/repo)に対応します。別のリポジトリには別の接続を使います。

型

GitHubの値ActQL備考
number、id、comments、sizeint64
node_id、sha、ログイン名、urlstring
state、state_reason、visibility、review_decisionstringGitHubの列挙値を文字列で(open、closed、completed、not_planned など)
title、body、messagestring本文はMarkdown。プログラムはプレーンな文字列を書き、GitHubがそれを描画する
labels、assignees、parentsarray<string>ラベル名、ログイン名、親のsha
created_at、updated_at、closed_at、merged_at、pushed_at、committed_attimestampUTC
draft、mergeableboolmergeable は、GitHubが計算中のあいだはNULL
files(コミットの)array<object>path、status、additions、deletions
ファイルの内容content_refgithub.contents を通して。media_type はパスの拡張子から

Effectと操作

レコードのEffectは、Issue、Pull Request、コメントをキーで書きます。

  • insert — EFFECT INSERT github.issues (title, body, labels, assignees) VALUES (…) はIssueを開き、EFFECT INSERT github.pull_requests (title, body, head, base) VALUES (…) はPull Requestを開き、EFFECT INSERT github.issue_comments (subject_number, body) VALUES (…) はIssueかPull Requestにコメントします。
  • update — EFFECT UPDATE github.issues ID number SET (title = …, body = …, labels = …, state = 'closed')。github.pull_requests(title、body、base、draft)と github.issue_comments ID id SET (body = …) も同じです。
  • delete — EFFECT DELETE github.issue_comments ID id はコメントを削除します。EFFECT DELETE github.issues ID number は、GraphQLの deleteIssue ミューテーションでIssueを削除します。リポジトリの管理者権限が必要です。Pull RequestにはGitHub側に削除がないので、EFFECT DELETE github.pull_requests ID number はクローズしてheadブランチを削除します。プレビューにはそう表示されます。

キーはちょうど1件を識別しなければなりません。IssueとPull Requestではリポジトリの中の number、コメントでは id です。

コンテンツ:

  • store — EFFECT STORE github.files SET (path = …, branch = …, message = …, content = content) は、レコードの content(SERIALIZE または contents の読み取り元から来た content_ref)を、そのブランチのそのパスにコミットし、ファイルを作成または置換します。1つのEffectは、パスごとに1つのコミットを作ります。

操作:

  • merge — EFFECT COMMAND github.merge TARGET number ID key INPUT (method = 'squash', commit_title = …)。
  • close — EFFECT COMMAND github.close TARGET number ID key INPUT (reason = 'not_planned') は、IssueかPull Requestを理由付きでクローズします。
  • request_review — EFFECT COMMAND github.request_review TARGET number ID key INPUT (reviewers = ['kim'])。
  • add_labels — EFFECT COMMAND github.add_labels TARGET number ID key INPUT (labels = ['triage']) は、既存のラベルを残したままラベルを足します(labels の update は集合を置き換えます)。

TARGET は操作の対象となるIssueかPull Requestで、ID はレコードのキーです。ID はプレビューとステータスで操作を識別します。

セットアップ

"github": {
  "connector": "github",
  "owner": "acme",
  "repo": "platform",
  "credentials": {"token": {"env": "GITHUB_TOKEN"}}
}

Fine-grained personal access tokenか、GitHub Appのインストールトークンを使います。読み取りには issues: read、pull_requests: read、contents: read、metadata: read が必要です。書き込みには issues: write(Issue、コメント、ラベル)、pull_requests: write(作成、更新、マージ、レビュー依頼)、contents: write(ファイルのコミット)が必要です。GitHub Enterprise Serverでは api_origin の項目を指定します。ActQL Cloudでは、Connectorを選んだリポジトリにGitHub Appとしてインストールします。ローカルのHostではトークンを直接使います。

Capabilityとプッシュダウン

演算プッシュダウンされる条件
列の選択エンジンが保持する量を減らす。REST APIはオブジェクト全体を返す
WHEREIssueとPull Requestでは state、ラベル('bug' IN UNNEST(labels))、assignee、author、milestone、updated_at >= …(since パラメーター)。コメントでは since。コミットではブランチと committed_at の期間(since/until)。ファイルでは ref とパスの前方一致。それ以外の述語(body や title に対するものを含む)はHostで実行する。GitHubの検索APIはそれらに使わない
ORDER BYIssueとPull Requestの created_at、updated_at、comments。昇順・降順どちらも
LIMIT常に。ページサイズとページ番号で実現する
GROUP BY、JOINされない。Hostで実行する

プレビューと突き合わせ

プレビュー。 更新、削除、merge、close、request_review、add_labels は full_diff です。Connectorは対象を updated_at とETagとともに読み、プレビューには現在の値と新しい値が出ます(マージならマージされるheadのsha、ラベルなら結果の集合)。適用時のリクエストはETag(If-Match)を条件にし、マージではプレビュー時のheadの sha も条件にします。その間に変わった対象は項目が failed(conflict)になり、何も送りません。追加とファイルのコミットは targeted です。プレビューには、リポジトリと、タイトル、本文、ラベル、またはパスとコンテンツの識別子がそのまま出ます。コミットでは、続くことになるブランチのheadも出ます。

突き合わせ。 Issue、Pull Request、コメントの作成はGitHub側では冪等ではありません。そのため、Connectorが書く本文の末尾には必ずリクエストのマーカーをHTMLコメントで入れます(<!-- actql:effect:… -->。GitHubはこれを描画しません)。応答が失われたときは、最近のIssue、Pull Request、コメントをそのマーカーで探して突き合わせます。同じ本文でもマーカーのないものは証跡になりません。ファイルのコミットはブランチのheadを読んで突き合わせます。メッセージにマーカーを持ち、ツリーに承認されたblobのshaを含むコミットが結果を確定させます。更新、クローズ、ラベルの変更、レビュー依頼は、Issueのタイムラインイベントで突き合わせます。各変更の実行者と時刻が記録されているので、このインストールがリクエストの時間帯に行い、承認された状態を作ったイベントがあれば結果が確定します。対象がその状態になっているだけでは確定しません。マージは、このインストールがリクエストの時間帯に行った merged のタイムラインイベントで確定します。マージコミットがあるだけでは確定しません。証跡がなければ、項目は unknown のままで、何も再送しません。操作は、GitHubが2xxを返した時点で accepted、読み返して状態を確認できた時点で applied になります。

上限と費用

  • REST APIはトークンごとに1時間あたり5,000リクエスト(Enterprise CloudのGitHub Appインストールでは15,000)までで、同時書き込みには二次的な制限があります。Connectorはその両方の範囲内でペースを調整し、100件ずつページングします。
  • 書き込みは項目ごとに1リクエストで送ります。GitHubにバッチのエンドポイントはありません。コンテンツを作成するリクエストの間には、GitHubが求める1秒の間隔を置きます。
  • blobの読み取りにはプログラムのバイト数の上限が適用されます。GitHubのblob上限100 MBを超えるファイルはコミットできません。
  • アクセス権のあるリポジトリに対して、GitHubのAPIに利用料はありません。

例

2週間コメントのない、開いたままのIssueです。

FROM github.issues
|> WHERE state = 'open' AND comments = 0
     AND created_at < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 14 DAY)
|> SELECT number, title, author
|> ORDER BY created_at;

それぞれにLLMで最初の返事の下書きを作り、コメントとして投稿し、Issueにラベルを付けます。

FROM github.issues
|> WHERE state = 'open' AND comments = 0
|> INFER `model-task:issue-first-reply.v1` INPUT (title, body) AS reply
|> WHERE reply.outcome = 'ok'
|> EFFECT INSERT github.issue_comments (subject_number, body)
     VALUES (number, reply.value.text);
FROM github.issues
|> WHERE state = 'open' AND 'triage' NOT IN UNNEST(labels)
|> INFER `model-task:issue-category.v1` INPUT (title, body) AS c
|> WHERE c.outcome = 'ok'
|> EFFECT COMMAND github.add_labels TARGET number ID number
     INPUT (labels = [c.value.category]);

生成したCSVをリポジトリに公開します。

FROM snowflake.usage_30d
|> SELECT account_id, active_users, trend
|> SERIALIZE FORMAT CSV ORDER BY account_id
     OPTIONS (name = 'usage-30d.csv', header = TRUE, null_marker = '')
|> EFFECT STORE github.files
     SET (path = 'reports/' || name, branch = 'main', message = 'Update usage report', content = content);

Source: actql/actql/docs/connectors/github.md at d904416674e4 · Edit on GitHub