Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/stale-adk-java-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Audits stale adk-java issues with the ADK Stale Issue Auditor sample under
# contrib/samples/github/adkstale.
#
# Required repository secrets:
# - GOOGLE_API_KEY : Gemini API key (or wire up Vertex AI credentials and
# set GOOGLE_GENAI_USE_VERTEXAI=TRUE).
# Commenting/labelling/closing uses the built-in GITHUB_TOKEN (no secret to
# manage); the `permissions:` block below grants it the `issues: write` scope it
# needs. Swap in a PAT only if you specifically want stale actions attributed to
# a distinct bot identity.
#
# Prerequisite: the `stale` label (and, if used, `request clarification`) must
# exist in the repository; GitHub will not auto-create labels when the agent
# applies them.
name: ADK Stale Issue Auditor

on:
schedule:
# Run daily at 06:00 UTC.
- cron: '0 6 * * *'
workflow_dispatch:

# Serialize runs so an in-flight daily sweep can't overlap a manual dispatch and
# act on the same issues twice.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

jobs:
agent-audit-stale-issues:
runs-on: ubuntu-latest
# Only run on the upstream repo, on the daily schedule or a manual dispatch.
if: >-
github.repository == 'google/adk-java' && (
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
)
permissions:
issues: write
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: maven

- name: Run Stale Issue Auditor
env:
# Built-in token scoped by the `permissions:` block above. Replace with a
# PAT (e.g. ${{ secrets.ADK_STALE_AGENT }}) only if you need a distinct
# bot identity for the comment/label/close actions.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_GENAI_USE_VERTEXAI: '0'
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
INTERACTIVE: '0'
# Defaults to a dry run (logs intended comments/labels/closures without
# writing). Verify the pipeline, then set DRY_RUN to '0' to go live.
DRY_RUN: '1'
EVENT_NAME: ${{ github.event_name }}
# Max number of stale-candidate issues to audit per run.
ISSUE_COUNT_TO_PROCESS: '20'
# Optional: comma-separated GitHub handles to treat as maintainers when
# the token cannot list push-access collaborators. Stored as a repo
# variable rather than committed to source.
MAINTAINERS: ${{ vars.ADK_MAINTAINERS }}
run: |
# Install the ADK libs + this sample, then run exec:java scoped to this
# module (exec:java with -am would also run on the parent/core modules,
# which have no mainClass).
./mvnw -B -q -pl contrib/samples/github/adkstale -am install -DskipTests
./mvnw -B -q -pl contrib/samples/github/adkstale exec:java
71 changes: 70 additions & 1 deletion contrib/samples/github/GitHubTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHIssueComment;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHIssueStateReason;
import org.kohsuke.github.GHLabel;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHPullRequestCommitDetail;
Expand All @@ -48,7 +49,8 @@
*
* <p>The tools cover the operations needed by the ADK GitHub automation samples: reading releases,
* diffs and file contents; searching code; listing and reading issues; creating issues and pull
* requests; labelling/assigning issues; and reading, labelling and commenting on pull requests.
* requests; labelling/assigning issues; commenting on or closing issues; and reading, labelling and
* commenting on pull requests.
*
* <p>Defense in depth against prompt injection: the agents read untrusted GitHub content (diffs,
* file contents, issue/PR titles) and could be steered into harmful writes. Independently of the
Expand Down Expand Up @@ -851,6 +853,73 @@ private static List<Map<String, Object>> collectStatusChecks(
return checks;
}

@Schema(
name = "add_comment_to_issue",
description = "Posts a comment on an issue. Returns the created comment's html_url.")
public static Map<String, Object> addCommentToIssue(
@Schema(name = "repo_owner", description = "The repository owner.") String repoOwner,
@Schema(name = "repo_name", description = "The repository name.") String repoName,
@Schema(name = "issue_number", description = "The issue number to comment on.")
int issueNumber,
@Schema(name = "body", description = "The Markdown body of the comment.") String body) {
if (body == null || body.isEmpty()) {
return error("Comment body must not be empty.");
}
String targetError = writeTargetError(repoOwner, repoName);
if (targetError != null) {
return error(targetError);
}
if (dryRun) {
return dryRunPreview(
"DRY RUN: no comment was posted. Set DRY_RUN=0 to comment on issues for real.",
"issue_number",
issueNumber,
"body",
body);
}
try {
GHRepository repo = connect().getRepository(repoOwner + "/" + repoName);
GHIssueComment comment = repo.getIssue(issueNumber).comment(body);
Map<String, Object> result = new LinkedHashMap<>();
result.put("issue_number", issueNumber);
result.put("html_url", comment.getHtmlUrl() == null ? "" : comment.getHtmlUrl().toString());
return success(result);
} catch (IOException | GHException e) {
return error("Failed to comment on issue #" + issueNumber + ": " + e.getMessage());
}
}

@Schema(
name = "close_issue",
description =
"Closes an issue as 'not planned' (the state used for stale/won't-do issues). Succeeds as"
+ " a no-op if the issue is already closed.")
public static Map<String, Object> closeIssue(
@Schema(name = "repo_owner", description = "The repository owner.") String repoOwner,
@Schema(name = "repo_name", description = "The repository name.") String repoName,
@Schema(name = "issue_number", description = "The issue number to close.") int issueNumber) {
String targetError = writeTargetError(repoOwner, repoName);
if (targetError != null) {
return error(targetError);
}
if (dryRun) {
return dryRunPreview(
"DRY RUN: no issue was closed. Set DRY_RUN=0 to close issues for real.",
"issue_number",
issueNumber);
}
try {
GHRepository repo = connect().getRepository(repoOwner + "/" + repoName);
repo.getIssue(issueNumber).close(GHIssueStateReason.NOT_PLANNED);
Map<String, Object> result = new LinkedHashMap<>();
result.put("issue_number", issueNumber);
result.put("state", "closed");
return success(result);
} catch (IOException | GHException e) {
return error("Failed to close issue #" + issueNumber + ": " + e.getMessage());
}
}

/** Formats an issue into the compact map (number, title, body, html_url, labels, assignees). */
private static Map<String, Object> formatIssue(GHIssue issue) {
Map<String, Object> info = new LinkedHashMap<>();
Expand Down
2 changes: 2 additions & 0 deletions contrib/samples/github/adkprtriaging/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
<exclude>**/*.yml</exclude>
<exclude>adktriaging/**</exclude>
<exclude>adkreleasedocs/**</exclude>
<exclude>adkstale/**</exclude>
<exclude>**/src/test/**</exclude>
<exclude>target/**</exclude>
</excludes>
Expand All @@ -169,6 +170,7 @@
<sourceFileExcludes>
<sourceFileExclude>adktriaging/**</sourceFileExclude>
<sourceFileExclude>adkreleasedocs/**</sourceFileExclude>
<sourceFileExclude>adkstale/**</sourceFileExclude>
<sourceFileExclude>**/src/test/**</sourceFileExclude>
</sourceFileExcludes>
</configuration>
Expand Down
2 changes: 2 additions & 0 deletions contrib/samples/github/adkreleasedocs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<excludes>
<exclude>**/*.jar</exclude>
<exclude>adkprtriaging/**</exclude>
<exclude>adkstale/**</exclude>
<exclude>adktriaging/**</exclude>
<exclude>target/**</exclude>
</excludes>
Expand All @@ -139,6 +140,7 @@
would otherwise fail. -->
<sourceFileExcludes>
<sourceFileExclude>adkprtriaging/**</sourceFileExclude>
<sourceFileExclude>adkstale/**</sourceFileExclude>
<sourceFileExclude>adktriaging/**</sourceFileExclude>
</sourceFileExcludes>
</configuration>
Expand Down
Loading
Loading