Skip to content

Commit cc2e6c3

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: Adds the ADK Stale Issue Auditor sample
PiperOrigin-RevId: 936490043
1 parent f14f644 commit cc2e6c3

15 files changed

Lines changed: 2835 additions & 1 deletion

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Audits stale adk-java issues with the ADK Stale Issue Auditor sample under
2+
# contrib/samples/github/adkstale.
3+
#
4+
# Required repository secrets:
5+
# - GOOGLE_API_KEY : Gemini API key (or wire up Vertex AI credentials and
6+
# set GOOGLE_GENAI_USE_VERTEXAI=TRUE).
7+
# Commenting/labelling/closing uses the built-in GITHUB_TOKEN (no secret to
8+
# manage); the `permissions:` block below grants it the `issues: write` scope it
9+
# needs. Swap in a PAT only if you specifically want stale actions attributed to
10+
# a distinct bot identity.
11+
#
12+
# Prerequisite: the `stale` label (and, if used, `request clarification`) must
13+
# exist in the repository; GitHub will not auto-create labels when the agent
14+
# applies them.
15+
name: ADK Stale Issue Auditor
16+
17+
on:
18+
schedule:
19+
# Run daily at 06:00 UTC.
20+
- cron: '0 6 * * *'
21+
workflow_dispatch:
22+
23+
# Serialize runs so an in-flight daily sweep can't overlap a manual dispatch and
24+
# act on the same issues twice.
25+
concurrency:
26+
group: ${{ github.workflow }}
27+
cancel-in-progress: false
28+
29+
jobs:
30+
agent-audit-stale-issues:
31+
runs-on: ubuntu-latest
32+
# Only run on the upstream repo, on the daily schedule or a manual dispatch.
33+
if: >-
34+
github.repository == 'google/adk-java' && (
35+
github.event_name == 'schedule' ||
36+
github.event_name == 'workflow_dispatch'
37+
)
38+
permissions:
39+
issues: write
40+
contents: read
41+
42+
steps:
43+
- name: Checkout repository
44+
uses: actions/checkout@v6
45+
46+
- name: Set up Java
47+
uses: actions/setup-java@v5
48+
with:
49+
distribution: temurin
50+
java-version: '17'
51+
cache: maven
52+
53+
- name: Run Stale Issue Auditor
54+
env:
55+
# Built-in token scoped by the `permissions:` block above. Replace with a
56+
# PAT (e.g. ${{ secrets.ADK_STALE_AGENT }}) only if you need a distinct
57+
# bot identity for the comment/label/close actions.
58+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59+
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
60+
GOOGLE_GENAI_USE_VERTEXAI: '0'
61+
OWNER: ${{ github.repository_owner }}
62+
REPO: ${{ github.event.repository.name }}
63+
INTERACTIVE: '0'
64+
# Defaults to a dry run (logs intended comments/labels/closures without
65+
# writing). Verify the pipeline, then set DRY_RUN to '0' to go live.
66+
DRY_RUN: '1'
67+
EVENT_NAME: ${{ github.event_name }}
68+
# Max number of stale-candidate issues to audit per run.
69+
ISSUE_COUNT_TO_PROCESS: '20'
70+
# Optional: comma-separated GitHub handles to treat as maintainers when
71+
# the token cannot list push-access collaborators. Stored as a repo
72+
# variable rather than committed to source.
73+
MAINTAINERS: ${{ vars.ADK_MAINTAINERS }}
74+
run: |
75+
# Install the ADK libs + this sample, then run exec:java scoped to this
76+
# module (exec:java with -am would also run on the parent/core modules,
77+
# which have no mainClass).
78+
./mvnw -B -q -pl contrib/samples/github/adkstale -am install -DskipTests
79+
./mvnw -B -q -pl contrib/samples/github/adkstale exec:java

contrib/samples/github/GitHubTools.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.kohsuke.github.GHIssue;
3232
import org.kohsuke.github.GHIssueComment;
3333
import org.kohsuke.github.GHIssueState;
34+
import org.kohsuke.github.GHIssueStateReason;
3435
import org.kohsuke.github.GHLabel;
3536
import org.kohsuke.github.GHPullRequest;
3637
import org.kohsuke.github.GHPullRequestCommitDetail;
@@ -48,7 +49,8 @@
4849
*
4950
* <p>The tools cover the operations needed by the ADK GitHub automation samples: reading releases,
5051
* diffs and file contents; searching code; listing and reading issues; creating issues and pull
51-
* requests; labelling/assigning issues; and reading, labelling and commenting on pull requests.
52+
* requests; labelling/assigning issues; commenting on or closing issues; and reading, labelling and
53+
* commenting on pull requests.
5254
*
5355
* <p>Defense in depth against prompt injection: the agents read untrusted GitHub content (diffs,
5456
* file contents, issue/PR titles) and could be steered into harmful writes. Independently of the
@@ -851,6 +853,73 @@ private static List<Map<String, Object>> collectStatusChecks(
851853
return checks;
852854
}
853855

856+
@Schema(
857+
name = "add_comment_to_issue",
858+
description = "Posts a comment on an issue. Returns the created comment's html_url.")
859+
public static Map<String, Object> addCommentToIssue(
860+
@Schema(name = "repo_owner", description = "The repository owner.") String repoOwner,
861+
@Schema(name = "repo_name", description = "The repository name.") String repoName,
862+
@Schema(name = "issue_number", description = "The issue number to comment on.")
863+
int issueNumber,
864+
@Schema(name = "body", description = "The Markdown body of the comment.") String body) {
865+
if (body == null || body.isEmpty()) {
866+
return error("Comment body must not be empty.");
867+
}
868+
String targetError = writeTargetError(repoOwner, repoName);
869+
if (targetError != null) {
870+
return error(targetError);
871+
}
872+
if (dryRun) {
873+
return dryRunPreview(
874+
"DRY RUN: no comment was posted. Set DRY_RUN=0 to comment on issues for real.",
875+
"issue_number",
876+
issueNumber,
877+
"body",
878+
body);
879+
}
880+
try {
881+
GHRepository repo = connect().getRepository(repoOwner + "/" + repoName);
882+
GHIssueComment comment = repo.getIssue(issueNumber).comment(body);
883+
Map<String, Object> result = new LinkedHashMap<>();
884+
result.put("issue_number", issueNumber);
885+
result.put("html_url", comment.getHtmlUrl() == null ? "" : comment.getHtmlUrl().toString());
886+
return success(result);
887+
} catch (IOException | GHException e) {
888+
return error("Failed to comment on issue #" + issueNumber + ": " + e.getMessage());
889+
}
890+
}
891+
892+
@Schema(
893+
name = "close_issue",
894+
description =
895+
"Closes an issue as 'not planned' (the state used for stale/won't-do issues). Succeeds as"
896+
+ " a no-op if the issue is already closed.")
897+
public static Map<String, Object> closeIssue(
898+
@Schema(name = "repo_owner", description = "The repository owner.") String repoOwner,
899+
@Schema(name = "repo_name", description = "The repository name.") String repoName,
900+
@Schema(name = "issue_number", description = "The issue number to close.") int issueNumber) {
901+
String targetError = writeTargetError(repoOwner, repoName);
902+
if (targetError != null) {
903+
return error(targetError);
904+
}
905+
if (dryRun) {
906+
return dryRunPreview(
907+
"DRY RUN: no issue was closed. Set DRY_RUN=0 to close issues for real.",
908+
"issue_number",
909+
issueNumber);
910+
}
911+
try {
912+
GHRepository repo = connect().getRepository(repoOwner + "/" + repoName);
913+
repo.getIssue(issueNumber).close(GHIssueStateReason.NOT_PLANNED);
914+
Map<String, Object> result = new LinkedHashMap<>();
915+
result.put("issue_number", issueNumber);
916+
result.put("state", "closed");
917+
return success(result);
918+
} catch (IOException | GHException e) {
919+
return error("Failed to close issue #" + issueNumber + ": " + e.getMessage());
920+
}
921+
}
922+
854923
/** Formats an issue into the compact map (number, title, body, html_url, labels, assignees). */
855924
private static Map<String, Object> formatIssue(GHIssue issue) {
856925
Map<String, Object> info = new LinkedHashMap<>();

contrib/samples/github/adkprtriaging/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
<exclude>**/*.yml</exclude>
154154
<exclude>adktriaging/**</exclude>
155155
<exclude>adkreleasedocs/**</exclude>
156+
<exclude>adkstale/**</exclude>
156157
<exclude>**/src/test/**</exclude>
157158
<exclude>target/**</exclude>
158159
</excludes>
@@ -169,6 +170,7 @@
169170
<sourceFileExcludes>
170171
<sourceFileExclude>adktriaging/**</sourceFileExclude>
171172
<sourceFileExclude>adkreleasedocs/**</sourceFileExclude>
173+
<sourceFileExclude>adkstale/**</sourceFileExclude>
172174
<sourceFileExclude>**/src/test/**</sourceFileExclude>
173175
</sourceFileExcludes>
174176
</configuration>

contrib/samples/github/adkreleasedocs/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<excludes>
125125
<exclude>**/*.jar</exclude>
126126
<exclude>adkprtriaging/**</exclude>
127+
<exclude>adkstale/**</exclude>
127128
<exclude>adktriaging/**</exclude>
128129
<exclude>target/**</exclude>
129130
</excludes>
@@ -139,6 +140,7 @@
139140
would otherwise fail. -->
140141
<sourceFileExcludes>
141142
<sourceFileExclude>adkprtriaging/**</sourceFileExclude>
143+
<sourceFileExclude>adkstale/**</sourceFileExclude>
142144
<sourceFileExclude>adktriaging/**</sourceFileExclude>
143145
</sourceFileExcludes>
144146
</configuration>

0 commit comments

Comments
 (0)