Skip to content

Commit 2cecee0

Browse files
Merge branch 'main' into fix/oauth-prompt-configurable
2 parents 609ae63 + 47ceeba commit 2cecee0

63 files changed

Lines changed: 4258 additions & 242 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/adk-pr-triage/SKILL.md

Lines changed: 284 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Helper script for ADK PR Triage verification and remote update."""
17+
18+
from __future__ import annotations
19+
20+
import argparse
21+
import json
22+
import subprocess
23+
import sys
24+
25+
26+
def run_command(args: list[str]) -> tuple[int, str, str]:
27+
"""Runs a shell command and returns its exit code, stdout, and stderr."""
28+
try:
29+
res = subprocess.run(args, capture_output=True, text=True, check=False)
30+
return res.returncode, res.stdout.strip(), res.stderr.strip()
31+
except Exception as e:
32+
return -1, "", str(e)
33+
34+
35+
def fetch_pr_data(pr_number: str) -> dict | None:
36+
"""Fetches all PR metadata in one shot from GitHub."""
37+
print(f"[*] Fetching PR #{pr_number} metadata from GitHub...")
38+
cmd = [
39+
"gh",
40+
"pr",
41+
"view",
42+
pr_number,
43+
"--repo",
44+
"google/adk-python",
45+
"--json",
46+
",".join([
47+
"number",
48+
"title",
49+
"body",
50+
"state",
51+
"url",
52+
"author",
53+
"additions",
54+
"deletions",
55+
"changedFiles",
56+
"labels",
57+
"statusCheckRollup",
58+
"assignees",
59+
"closingIssuesReferences",
60+
]),
61+
]
62+
code, stdout, stderr = run_command(cmd)
63+
if code != 0:
64+
print(
65+
f"Error: Failed to fetch PR details from GitHub: {stderr}",
66+
file=sys.stderr,
67+
)
68+
return None
69+
try:
70+
return json.loads(stdout)
71+
except json.JSONDecodeError:
72+
print("Error: Failed to parse GitHub API JSON response.", file=sys.stderr)
73+
return None
74+
75+
76+
def verify_cla(pr_data: dict) -> bool:
77+
"""Verifies if the Google CLA is signed using cached PR data."""
78+
status_checks = pr_data.get("statusCheckRollup") or []
79+
cla_check = None
80+
for check in status_checks:
81+
if check.get("name") == "cla/google":
82+
cla_check = check
83+
break
84+
85+
if not cla_check:
86+
print("\n" + "=" * 80)
87+
print("🚨 CRITICAL COMPLIANCE REFUSAL: GOOGLE CLA NOT SIGNED/VERIFIED 🚨")
88+
print("=" * 80)
89+
print(
90+
"Error: The mandatory 'cla/google' status check is completely missing"
91+
" on GitHub."
92+
)
93+
print(
94+
"The contributor HAS NOT signed the Google Contributor License"
95+
" Agreement."
96+
)
97+
print(
98+
"Legal policy strictly prohibits triaging, downloading, or reviewing"
99+
" this PR."
100+
)
101+
print("=" * 80 + "\n")
102+
return False
103+
104+
conclusion = cla_check.get("conclusion")
105+
if conclusion != "SUCCESS":
106+
print("\n" + "=" * 80)
107+
print("🚨 CRITICAL COMPLIANCE REFUSAL: GOOGLE CLA NOT SIGNED/VERIFIED 🚨")
108+
print("=" * 80)
109+
print(
110+
"Error: The 'cla/google' status check has the status:"
111+
f" '{conclusion or 'UNKNOWN'}'."
112+
)
113+
print(
114+
"The contributor HAS NOT successfully signed or verified the Google"
115+
" CLA."
116+
)
117+
print(
118+
"Legal policy strictly prohibits triaging, downloading, or reviewing"
119+
" this PR."
120+
)
121+
print("=" * 80 + "\n")
122+
return False
123+
124+
print("✅ Google CLA is verified and signed (status SUCCESS).")
125+
return True
126+
127+
128+
def get_current_user() -> str | None:
129+
"""Fetches the login name of the current authenticated GitHub user."""
130+
cmd = ["gh", "api", "user", "-q", ".login"]
131+
code, stdout, stderr = run_command(cmd)
132+
if code != 0:
133+
return None
134+
return stdout.strip()
135+
136+
137+
def verify_pr_assignment(pr_data: dict, pr_number: str) -> bool:
138+
"""Checks if the PR is assigned to the current user using cached PR data."""
139+
print(f"\n[*] Verifying assignment for PR #{pr_number}...")
140+
141+
# Fetch the current logged in user
142+
current_user = get_current_user()
143+
if not current_user:
144+
print(
145+
"Warning: Could not determine current GitHub user. Skipping assignment"
146+
" check."
147+
)
148+
return True
149+
150+
print(f"[*] Current GitHub user: {current_user}")
151+
152+
assignees = pr_data.get("assignees") or []
153+
assignee_logins = [a.get("login") for a in assignees if a.get("login")]
154+
155+
if current_user in assignee_logins:
156+
print(f"✅ Pull Request #{pr_number} is assigned to you.")
157+
return True
158+
159+
assignees_str = ", ".join(assignee_logins) if assignee_logins else "None"
160+
print(
161+
f"⚠️ WARNING: Pull Request #{pr_number} is NOT assigned to you!"
162+
f" Current assignees: {assignees_str}"
163+
)
164+
print("\n[!] ACTION REQUIRED: The Pull Request is not assigned to you.")
165+
print(" Please ask the user if they want to take over the PR.")
166+
return False
167+
168+
169+
def update_pr_branch(pr_number: str) -> None:
170+
"""Updates the remote PR branch with the latest changes from the base branch."""
171+
print(
172+
f"\n[*] Attempting to update PR #{pr_number} branch via remote REBASE..."
173+
)
174+
rebase_cmd = [
175+
"gh",
176+
"pr",
177+
"update-branch",
178+
pr_number,
179+
"--rebase",
180+
"--repo",
181+
"google/adk-python",
182+
]
183+
code, stdout, stderr = run_command(rebase_cmd)
184+
if code == 0:
185+
print(
186+
"✅ Successfully updated PR branch on GitHub by rebasing onto base"
187+
" branch!"
188+
)
189+
if stdout:
190+
print(stdout)
191+
return
192+
193+
print(f"Warning: Remote rebase-update failed: {stderr}")
194+
print("[*] Falling back to standard remote MERGE commit update...")
195+
196+
merge_cmd = [
197+
"gh",
198+
"pr",
199+
"update-branch",
200+
pr_number,
201+
"--repo",
202+
"google/adk-python",
203+
]
204+
code, stdout, stderr = run_command(merge_cmd)
205+
if code == 0:
206+
print(
207+
"✅ Successfully updated PR branch on GitHub via standard merge commit!"
208+
)
209+
if stdout:
210+
print(stdout)
211+
return
212+
213+
print(
214+
"\n[!] Warning: Remote branch update failed completely on GitHub server:"
215+
f" {stderr}"
216+
)
217+
print(" This is typical if edits are disabled on the contributor's fork.")
218+
print(
219+
" No worries! We will automatically rebase locally after checking out."
220+
)
221+
222+
223+
def main() -> None:
224+
parser = argparse.ArgumentParser(
225+
description="Triage PR verification and sync helper."
226+
)
227+
parser.add_argument(
228+
"pr_number", help="The GitHub Pull Request number (e.g. 5875)."
229+
)
230+
parser.add_argument(
231+
"--skip-update",
232+
action="store_true",
233+
help="Skip updating the remote PR branch on GitHub.",
234+
)
235+
args = parser.parse_args()
236+
237+
# Step 0: Fetch PR data in one-shot
238+
pr_data = fetch_pr_data(args.pr_number)
239+
if not pr_data:
240+
sys.exit(1)
241+
242+
# Step 1: Verify CLA using cached PR data
243+
if not verify_cla(pr_data):
244+
sys.exit(2) # Exit code 2 indicates compliance refusal
245+
246+
# Step 2: Output the PR metadata JSON directly to standard output
247+
print("\n[PR_METADATA_JSON]")
248+
print(json.dumps(pr_data, indent=2))
249+
print("[/PR_METADATA_JSON]")
250+
251+
# Step 3: Verify PR Assignment using cached PR data
252+
if not verify_pr_assignment(pr_data, args.pr_number):
253+
sys.exit(3) # Exit code 3 indicates assignment block
254+
255+
# Step 4: Update branch
256+
if not args.skip_update:
257+
update_pr_branch(args.pr_number)
258+
259+
print("\n[*] Verification complete. Safe to proceed with checkout.")
260+
sys.exit(0)
261+
262+
263+
if __name__ == "__main__":
264+
main()

.github/workflows/copybara-pr-handler.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ jobs:
5757
console.log(`\n--- Processing commit ${sha.substring(0, 7)} ---`);
5858
console.log(`Committer: ${committer}`);
5959
60-
// Check if this is a Copybara commit
60+
// Check if this is a Copybara commit or has a pull request reference
6161
const isCopybara = committer === 'Copybara-Service' ||
6262
commit.author?.email === 'genai-sdk-bot@google.com' ||
6363
message.includes('GitOrigin-RevId:') ||
64-
message.includes('PiperOrigin-RevId:');
64+
message.includes('PiperOrigin-RevId:') ||
65+
/Merge:?\s+https:\/\/github\.com\/google\/adk-python\/pull\/(\d+)/.test(message);
6566
6667
if (!isCopybara) {
6768
console.log('Not a Copybara commit, skipping');
@@ -118,6 +119,14 @@ jobs:
118119
body: `Thank you @${author} for your contribution! 🎉\n\nYour changes have been successfully imported and merged via Copybara in commit ${commitSha}.\n\nClosing this PR as the changes are now in the main branch.`
119120
});
120121
122+
// Add 'merged' label to the PR
123+
await github.rest.issues.addLabels({
124+
owner: context.repo.owner,
125+
repo: context.repo.repo,
126+
issue_number: prNumber,
127+
labels: ['merged']
128+
});
129+
121130
// Close the PR
122131
await github.rest.pulls.update({
123132
owner: context.repo.owner,

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ For all matters regarding ADK development, please use the appropriate skill:
1818
- Read `.agents/skills/adk-review/SKILL.md` for full instructions.
1919
- **`adk-issue`**: Use this skill when analyzing and triaging GitHub issues for the adk-python repository to verify legitimacy, recommend fixes, and check for existing PRs.
2020
- Read `.agents/skills/adk-issue/SKILL.md` for full instructions.
21+
- **`adk-pr-triage`**: Use this skill when triaging and analyzing GitHub pull requests (PRs) to evaluate their objectives, legitimacy, value, and alignment with ADK's architectural, styling, and testing principles.
22+
- Read `.agents/skills/adk-pr-triage/SKILL.md` for full instructions.
23+
2124

2225
## Project Overview
2326

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ dependencies = [
3636
"aiosqlite>=0.21",
3737
"authlib>=1.6.6,<2",
3838
"click>=8.1.8,<9",
39-
"fastapi>=0.124.1,<1",
39+
"fastapi>=0.133,<1",
4040
"google-auth[pyopenssl]>=2.47",
4141
"google-genai>=2.4,<3",
4242
"graphviz>=0.20.2,<1",
@@ -51,7 +51,7 @@ dependencies = [
5151
# go/keep-sorted start
5252
"pyyaml>=6.0.2,<7",
5353
"requests>=2.32.4,<3",
54-
"starlette>=0.49.1,<1",
54+
"starlette>=1.0.1,<2",
5555
"tenacity>=9,<10",
5656
"typing-extensions>=4.5,<5",
5757
"tzlocal>=5.3,<6",

0 commit comments

Comments
 (0)