Use this to view the timeline of GitHub issues or pull requests. Note that it does not fetch the title and description, unlike `gh pr view`.
日本語の概要は準備中です。原文の説明を表示しています。
Use when GitHub may have shipped a GraphQL schema update affecting timeline event unions (PullRequestTimelineItems / IssueTimelineItems), when the gh-timeline dispatcher fallback is hitting more than expected, before a gh-timeline release, or when the user requests phrases like "verify timeline coverage", "audit gh-timeline schema", "check for new timeline events", "GitHub schema changed". Walks through schema diff → patch → test update → live verification → optional fixture refresh, marking each spot where a human decision is required.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
このリポジトリ (gh-timeline) は GitHub の GraphQL union を 2 つ網羅する:
PullRequestTimelineItems (PR timeline 用)IssueTimelineItems (Issue timeline 用)GitHub は schema を不定期に更新する。新規 event 追加・既存 field の変更・deprecate が起きると、本リポジトリの宣言と乖離して dispatcher fallback に落ちたり、 そもそも query が schema error で落ちる可能性がある。
このスキルは その差分検出 → 修正 → 検証 までを段階的に案内する。
以下のいずれかが当てはまれば本スキルを invoke する:
coverage_test.go の TestDispatchCoverage_* が failgh timeline を叩いたら未知の __typename が頻発[Step 1: Schema diff]
↓
[Step 2: Triage] ← 人間判断ポイント
↓
[Step 3: Patch code]
↓
[Step 4: Update tests]
↓
[Step 5: Live verification] ← 人間判断ポイント
↓
[Step 6: (任意) Fixture refresh] ← PLAN.md へ委譲
⚠️ マーク = 人間の判断や手動操作が必要な箇所。
GitHub の現行 union member を introspection で取得し、本リポジトリの宣言と比較する。
# 作業ディレクトリへ移動
cd "$(git rev-parse --show-toplevel)"
# 1.1 GitHub schema 側の union member
gh api graphql -f query='
query {
pr: __type(name: "PullRequestTimelineItems") { possibleTypes { name } }
issue: __type(name: "IssueTimelineItems") { possibleTypes { name } }
}' --jq '.data.pr.possibleTypes[].name' | sort -u > /tmp/pr_upstream.txt
gh api graphql -f query='
query { issue: __type(name: "IssueTimelineItems") { possibleTypes { name } } }
' --jq '.data.issue.possibleTypes[].name' | sort -u > /tmp/issue_upstream.txt
# 1.2 本 repo 側の宣言 (graphql tag を抽出)
grep -hE '`graphql:"\.\.\. on [A-Za-z0-9]+"`' internal/timeline/query.go \
| grep -oE 'on [A-Za-z0-9]+' | sed 's/^on //' | sort -u > /tmp/repo_declared.txt
# 1.3 PR union の差分 — 本 repo は両 union 分を 1 ファイルに宣言しているので
# pr_upstream に対してのみ厳密チェック (issue union は subset)
echo "== upstream PR にあって repo に無い (新規対応が必要) =="
comm -23 /tmp/pr_upstream.txt /tmp/repo_declared.txt
echo "== repo に宣言があるが upstream PR に無い (deprecated 検証) =="
comm -13 /tmp/pr_upstream.txt /tmp/repo_declared.txt | grep -v -E '^(Issue|PullRequest)$'
echo "== issue union の追加分 (PR 共通でない場合) =="
comm -23 /tmp/issue_upstream.txt /tmp/pr_upstream.txt
Step 1 の結果を 4 つに分類する。
| 差分カテゴリ | 内容 | 対処 |
|---|---|---|
| (A) 新規追加 | upstream にあって repo にない typename | Step 3 で fragment + dispatch case + handler を追加 |
| (B) 削除 (deprecated) | repo にあって upstream にない typename | Step 3 で fragment / case / handler を撤去。Projects classic 系は EOL 後しばらく schema には残ることがある — GitHub Docs で確認 |
| (C) フィールド変更 | typename は同じだが field が変わった | introspection だけだと検出不可。GitHub Docs / Changelog で event 型の field 一覧を目視確認 |
| (D) 命名変更 | A と B の両方に同時出現 | 既存 fragment / case を rename で対応 |
⚠️ 人間判断が要るところ:
新規 event <FooEvent> 1 つにつき以下を実施:
internal/timeline/nodes.go)actor + createdAt しか使わないなら commonEvent を embed:
type fooEventFragment struct {
commonEvent
SomeSpecificField githubv4.String
}
actor / createdAt が独自フィールド (例: submittedAt) なら個別宣言:
type fooEventFragment struct {
ID githubv4.ID
Author actorFragment
SubmittedAt githubv4.DateTime
}
internal/timeline/query.go)prTimelineNode に追加:
FooEvent fooEventFragment `graphql:"... on FooEvent"`
PR / Issue 両 union のメンバなら issueTimelineNode にも同じ tag で追加。
internal/timeline/dispatch.go)dispatchPRNode (および該当すれば dispatchIssueNode) の switch に case 追加:
case "FooEvent":
return handleFooEvent(t, n.FooEvent)
internal/timeline/dispatch.go)既存 handler の summary は 動詞句 + 対象フィールド の形に統一されている
(added label bug, assigned bea, merged deadbee into main 等)。フィールドが
空でも動詞句は残すことで、最低限の意味を保つ。新規 handler もこの流儀に揃える:
func handleFooEvent(typename string, f fooEventFragment) Event {
verb := "did foo to" // ⚠️ 文言は人間判断
summary := verb
if target := string(f.SomeSpecificField); target != "" {
summary = fmt.Sprintf("%s %s", verb, target)
}
return Event{
Type: typename,
Actor: string(f.Actor.Login),
Timestamp: f.CreatedAt.Time,
Summary: summary,
Ref: Ref{NodeID: graphqlIDString(f.ID)},
}
}
ペアになる add/remove 型 event (Labeled/Unlabeled, Assigned/Unassigned,
Milestoned/Demilestoned, ReviewRequested/ReviewRequestRemoved 等) は 同じ
handler を共有して typename で動詞を切り替える のが既存流儀:
verb := "added label"
if typename == "UnlabeledEvent" {
verb = "removed label"
}
⚠️ 人間判断:
added label か labeled か など)(column %q) のような
括弧書き、(%s) の duration 等。handleConnected / handleProjectChange /
handleUserBlocked が実例)shortSHA() を通すこと (handleMerged 参照)B カテゴリの event は上記 4 つを削除。fragment 型は他の event と共有していないかを git grep で確認してから削除する。
internal/timeline/coverage_test.go は reflection で struct tag を列挙するため、
Step 3.2 で query.go に追加した時点で対応する sub-test が自動で生まれる。
mise run test -- -run TestDispatchCoverage
# 期待: TestDispatchCoverage_prTimelineNode/<NewType> が新規に PASS
internal/timeline/dispatch_test.go の table-driven テストに代表的なケースを追加。
name は 何を assert しているかが伝わる文 にする (例: "FooEvent uses the
foo'd verb with target name") — assert behavior, not implementation の原則:
{
name: "FooEvent uses the foo'd verb with target name",
node: func() prTimelineNode {
n := prTimelineNode{Typename: "FooEvent"}
n.FooEvent.Actor.Login = "alice"
n.FooEvent.CreatedAt = dt(ts)
n.FooEvent.SomeSpecificField = "bar"
return n
}(),
wantType: "FooEvent",
wantActor: "alice",
wantSummary: "foo'd bar",
},
ペア event を追加した場合は、両方の typename と 特定フィールドが空 の fallback も別ケースとしてカバーする。
mise run fmt
mise run lint
mise run test
3 つ全部 0 issue / pass で次へ。
実 PR / Issue で発火するかを確認:
# 該当 event が出る PR が分かっていれば
go run . --repo OWNER/REPO PR_NUMBER --json | jq -r '.[] | select(.type=="FooEvent")'
# 出る PR が思い当たらない場合 → GitHub search で探す
gh search prs "<キーワード>" --json url,number --limit 5
⚠️ 人間判断:
.timeline-fixtures/PLAN.md 末尾の「取得不能 7 種」表に追記本 repo の reference fixture (sushichan044/gh-timeline の PR-A / PR-B / Issue-A / Issue-B)
を新 event に対応させたい場合、.timeline-fixtures/PLAN.md の該当 Phase に
操作を追加してから再実行する。
判断基準:
スキル完了時、以下が全て満たされること:
[ ] Step 1 の schema diff で repo と upstream が乖離なし
[ ] Step 3 の patch がコミット済み (conventional commit: feat/fix/refactor)
[ ] mise run fmt / lint / test が全て 0 issue / pass
[ ] 新 event について Step 5 で Live 観測 or Step 6 の fixture 拡張 or
取得不能と判定して PLAN.md に追記
[ ] (該当すれば) .timeline-fixtures/PLAN.md を最新化
[ ] commit message に「新 event X 件、削除 Y 件」のような差分サマリを記載
Issue / PullRequest のような top-level union 名 (timelineQuery 自体の
... on PullRequest 等) は本スキルの差分対象外。grep のフィルタで除外するdatabaseId (Int) が int32 overflow することがある (comment_id 4G 超え)。
fragment では int64 で受ける (既存 issueCommentFragment / pullRequestReviewFragment 参照)gh timeline 自体が schema 変更で動かなくなった場合 (= まず query が
schema error で落ちる) は本スキルではなく直接 internal/timeline/query.go を
最小修正してから本スキルを起動する.timeline-fixtures/PLAN.mdinternal/timeline/dispatch.gointernal/timeline/query.gointernal/timeline/nodes.gointernal/timeline/coverage_test.gointernal/timeline/dispatch_test.goまだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Use this to view the timeline of GitHub issues or pull requests. Note that it does not fetch the title and description, unlike `gh pr view`.
日本語の概要は準備中です。原文の説明を表示しています。