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、size | int64 | |
node_id、sha、ログイン名、url | string | |
state、state_reason、visibility、review_decision | string | GitHubの列挙値を文字列で(open、closed、completed、not_planned など) |
title、body、message | string | 本文はMarkdown。プログラムはプレーンな文字列を書き、GitHubがそれを描画する |
labels、assignees、parents | array<string> | ラベル名、ログイン名、親のsha |
created_at、updated_at、closed_at、merged_at、pushed_at、committed_at | timestamp | UTC |
draft、mergeable | bool | mergeable は、GitHubが計算中のあいだはNULL |
files(コミットの) | array<object> | path、status、additions、deletions |
| ファイルの内容 | content_ref | github.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はオブジェクト全体を返す |
WHERE | Issueと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 BY | Issueと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