mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
chore(automation): ensure status/need-triage is applied and never cleared automatically (#16657)
This commit is contained in:
138
.github/scripts/backfill-need-triage.cjs
vendored
Normal file
138
.github/scripts/backfill-need-triage.cjs
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* global require, console, process */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script to backfill the 'status/need-triage' label to all open issues
|
||||||
|
* that are NOT currently labeled with '🔒 maintainer only' or 'help wanted'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { execFileSync } = require('child_process');
|
||||||
|
|
||||||
|
const isDryRun = process.argv.includes('--dry-run');
|
||||||
|
const REPO = 'google-gemini/gemini-cli';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a GitHub CLI command safely using an argument array to prevent command injection.
|
||||||
|
* @param {string[]} args
|
||||||
|
* @returns {string|null}
|
||||||
|
*/
|
||||||
|
function runGh(args) {
|
||||||
|
try {
|
||||||
|
// Using execFileSync with an array of arguments is safe as it doesn't use a shell.
|
||||||
|
// We set a large maxBuffer (10MB) to handle repositories with many issues.
|
||||||
|
return execFileSync('gh', args, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
maxBuffer: 10 * 1024 * 1024,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
}).trim();
|
||||||
|
} catch (error) {
|
||||||
|
const stderr = error.stderr ? ` Stderr: ${error.stderr.trim()}` : '';
|
||||||
|
console.error(
|
||||||
|
`❌ Error running gh ${args.join(' ')}: ${error.message}${stderr}`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🔐 GitHub CLI security check...');
|
||||||
|
const authStatus = runGh(['auth', 'status']);
|
||||||
|
if (authStatus === null) {
|
||||||
|
console.error('❌ GitHub CLI (gh) is not installed or not authenticated.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
console.log('🧪 DRY RUN MODE ENABLED - No changes will be made.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔍 Fetching and filtering open issues from ${REPO}...`);
|
||||||
|
|
||||||
|
// We use the /issues endpoint with pagination to bypass the 1000-result limit.
|
||||||
|
// The jq filter ensures we exclude PRs, maintainer-only, help-wanted, and existing status/need-triage.
|
||||||
|
const jqFilter =
|
||||||
|
'.[] | select(.pull_request == null) | select([.labels[].name] as $l | (any($l[]; . == "🔒 maintainer only") | not) and (any($l[]; . == "help wanted") | not) and (any($l[]; . == "status/need-triage") | not)) | {number: .number, title: .title}';
|
||||||
|
|
||||||
|
const output = runGh([
|
||||||
|
'api',
|
||||||
|
`repos/${REPO}/issues?state=open&per_page=100`,
|
||||||
|
'--paginate',
|
||||||
|
'--jq',
|
||||||
|
jqFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (output === null) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const issues = output
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line.trim())
|
||||||
|
.map((line) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(line);
|
||||||
|
} catch (_e) {
|
||||||
|
console.error(`⚠️ Failed to parse line: ${line}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
console.log(`✅ Found ${issues.length} issues matching criteria.`);
|
||||||
|
|
||||||
|
if (issues.length === 0) {
|
||||||
|
console.log('✨ No issues need backfilling.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let failCount = 0;
|
||||||
|
|
||||||
|
if (isDryRun) {
|
||||||
|
for (const issue of issues) {
|
||||||
|
console.log(
|
||||||
|
`[DRY RUN] Would label issue #${issue.number}: ${issue.title}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
successCount = issues.length;
|
||||||
|
} else {
|
||||||
|
console.log(`🏷️ Applying labels to ${issues.length} issues...`);
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
const issueNumber = String(issue.number);
|
||||||
|
console.log(`🏷️ Labeling issue #${issueNumber}: ${issue.title}`);
|
||||||
|
|
||||||
|
const result = runGh([
|
||||||
|
'issue',
|
||||||
|
'edit',
|
||||||
|
issueNumber,
|
||||||
|
'--add-label',
|
||||||
|
'status/need-triage',
|
||||||
|
'--repo',
|
||||||
|
REPO,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (result !== null) {
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n📊 Summary:`);
|
||||||
|
console.log(` - Success: ${successCount}`);
|
||||||
|
console.log(` - Failed: ${failCount}`);
|
||||||
|
|
||||||
|
if (failCount > 0) {
|
||||||
|
console.error(`\n❌ Backfill completed with ${failCount} errors.`);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log(`\n🎉 ${isDryRun ? 'Dry run' : 'Backfill'} complete!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('❌ Unexpected error:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
51
.github/scripts/pr-triage.sh
vendored
51
.github/scripts/pr-triage.sh
vendored
@@ -1,4 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# @license
|
||||||
|
# Copyright 2026 Google LLC
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Initialize a comma-separated string to hold PR numbers that need a comment
|
# Initialize a comma-separated string to hold PR numbers that need a comment
|
||||||
@@ -10,7 +14,7 @@ ISSUE_LABELS_CACHE_FLAT=""
|
|||||||
|
|
||||||
# Function to get area and priority labels from an issue (with caching)
|
# Function to get area and priority labels from an issue (with caching)
|
||||||
get_issue_labels() {
|
get_issue_labels() {
|
||||||
local ISSUE_NUM=$1
|
local ISSUE_NUM="${1}"
|
||||||
if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then
|
if [[ -z "${ISSUE_NUM}" || "${ISSUE_NUM}" == "null" || "${ISSUE_NUM}" == "" ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -18,10 +22,13 @@ get_issue_labels() {
|
|||||||
# Check cache
|
# Check cache
|
||||||
case " ${ISSUE_LABELS_CACHE_FLAT} " in
|
case " ${ISSUE_LABELS_CACHE_FLAT} " in
|
||||||
*" ${ISSUE_NUM}:"*)
|
*" ${ISSUE_NUM}:"*)
|
||||||
local suffix="${ISSUE_LABELS_CACHE_FLAT#* ${ISSUE_NUM}:}"
|
local suffix="${ISSUE_LABELS_CACHE_FLAT#* " ${ISSUE_NUM}:"}"
|
||||||
echo "${suffix%% *}"
|
echo "${suffix%% *}"
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
# Cache miss, proceed to fetch
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2
|
echo " 📥 Fetching area and priority labels from issue #${ISSUE_NUM}" >&2
|
||||||
@@ -33,19 +40,19 @@ get_issue_labels() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local labels
|
local labels
|
||||||
labels=$(echo "${gh_output}" | grep -E "^(area|priority)/" | tr '\n' ',' | sed 's/,$//' || echo "")
|
labels=$(echo "${gh_output}" | grep -E '^(area|priority)/' | tr '\n' ',' | sed 's/,$//' || echo "")
|
||||||
|
|
||||||
# Save to flat cache
|
# Save to flat cache
|
||||||
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}"
|
ISSUE_LABELS_CACHE_FLAT="${ISSUE_LABELS_CACHE_FLAT} ${ISSUE_NUM}:${labels}"
|
||||||
echo "$labels"
|
echo "${labels}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to process a single PR with pre-fetched data
|
# Function to process a single PR with pre-fetched data
|
||||||
process_pr_optimized() {
|
process_pr_optimized() {
|
||||||
local PR_NUMBER=$1
|
local PR_NUMBER="${1}"
|
||||||
local IS_DRAFT=$2
|
local IS_DRAFT="${2}"
|
||||||
local ISSUE_NUMBER=$3
|
local ISSUE_NUMBER="${3}"
|
||||||
local CURRENT_LABELS=$4 # Comma-separated labels
|
local CURRENT_LABELS="${4}" # Comma-separated labels
|
||||||
|
|
||||||
echo "🔄 Processing PR #${PR_NUMBER}"
|
echo "🔄 Processing PR #${PR_NUMBER}"
|
||||||
|
|
||||||
@@ -84,7 +91,7 @@ process_pr_optimized() {
|
|||||||
ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}")
|
ISSUE_LABELS=$(get_issue_labels "${ISSUE_NUMBER}")
|
||||||
|
|
||||||
if [[ -n "${ISSUE_LABELS}" ]]; then
|
if [[ -n "${ISSUE_LABELS}" ]]; then
|
||||||
local IFS_OLD=$IFS
|
local IFS_OLD="${IFS}"
|
||||||
IFS=','
|
IFS=','
|
||||||
for label in ${ISSUE_LABELS}; do
|
for label in ${ISSUE_LABELS}; do
|
||||||
if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then
|
if [[ -n "${label}" ]] && [[ ",${CURRENT_LABELS}," != *",${label},"* ]]; then
|
||||||
@@ -94,8 +101,8 @@ process_pr_optimized() {
|
|||||||
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
|
LABELS_TO_ADD="${LABELS_TO_ADD},${label}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
IFS=$IFS_OLD
|
IFS="${IFS_OLD}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then
|
if [[ -z "${LABELS_TO_ADD}" && -z "${LABELS_TO_REMOVE}" ]]; then
|
||||||
@@ -135,7 +142,7 @@ JQ_EXTRACT_FIELDS='{
|
|||||||
labels: [.labels[].name] | join(",")
|
labels: [.labels[].name] | join(",")
|
||||||
}'
|
}'
|
||||||
|
|
||||||
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // "null") | tostring)\t\(.labels)"'
|
JQ_TSV_FORMAT='"\((.number | tostring))\t\(.isDraft)\t\((.issue // \"null\") | tostring)\t\(.labels)"' # Corrected escaping for quotes within the string literal
|
||||||
|
|
||||||
if [[ -n "${PR_NUMBER:-}" ]]; then
|
if [[ -n "${PR_NUMBER:-}" ]]; then
|
||||||
echo "🔄 Processing single PR #${PR_NUMBER}"
|
echo "🔄 Processing single PR #${PR_NUMBER}"
|
||||||
@@ -144,9 +151,9 @@ if [[ -n "${PR_NUMBER:-}" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
line=$(echo "$PR_DATA" | jq -r "$JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT")
|
line=$(echo "${PR_DATA}" | jq -r "${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}")
|
||||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line"
|
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
|
||||||
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels"
|
process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
|
||||||
else
|
else
|
||||||
echo "📥 Getting all open pull requests..."
|
echo "📥 Getting all open pull requests..."
|
||||||
PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
|
PR_DATA_ALL=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --limit 1000 --json number,closingIssuesReferences,isDraft,body,labels 2>/dev/null) || {
|
||||||
@@ -157,11 +164,15 @@ else
|
|||||||
PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length')
|
PR_COUNT=$(echo "${PR_DATA_ALL}" | jq '. | length')
|
||||||
echo "📊 Found ${PR_COUNT} open PRs to process"
|
echo "📊 Found ${PR_COUNT} open PRs to process"
|
||||||
|
|
||||||
|
# Use a temporary file to avoid masking exit codes in process substitution
|
||||||
|
tmp_file=$(mktemp)
|
||||||
|
echo "${PR_DATA_ALL}" | jq -r ".[] | ${JQ_EXTRACT_FIELDS} | ${JQ_TSV_FORMAT}" > "${tmp_file}"
|
||||||
while read -r line; do
|
while read -r line; do
|
||||||
[[ -z "$line" ]] && continue
|
[[ -z "${line}" ]] && continue
|
||||||
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "$line"
|
IFS=$'\t' read -r pr_num is_draft issue_num current_labels <<< "${line}"
|
||||||
process_pr_optimized "$pr_num" "$is_draft" "$issue_num" "$current_labels"
|
process_pr_optimized "${pr_num}" "${is_draft}" "${issue_num}" "${current_labels}"
|
||||||
done < <(echo "${PR_DATA_ALL}" | jq -r ".[] | $JQ_EXTRACT_FIELDS | $JQ_TSV_FORMAT")
|
done < "${tmp_file}"
|
||||||
|
rm -f "${tmp_file}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
|
if [[ -z "${PRS_NEEDING_COMMENT}" ]]; then
|
||||||
@@ -170,4 +181,4 @@ else
|
|||||||
echo "prs_needing_comment=[${PRS_NEEDING_COMMENT}]" >> "${GITHUB_OUTPUT}"
|
echo "prs_needing_comment=[${PRS_NEEDING_COMMENT}]" >> "${GITHUB_OUTPUT}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ PR triage completed"
|
echo "✅ PR triage completed"
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ jobs:
|
|||||||
id: 'generate_token'
|
id: 'generate_token'
|
||||||
env:
|
env:
|
||||||
APP_ID: '${{ secrets.APP_ID }}'
|
APP_ID: '${{ secrets.APP_ID }}'
|
||||||
if: "${{ env.APP_ID != '' }}"
|
if: |-
|
||||||
|
${{ env.APP_ID != '' }}
|
||||||
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
|
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
|
||||||
with:
|
with:
|
||||||
app-id: '${{ secrets.APP_ID }}'
|
app-id: '${{ secrets.APP_ID }}'
|
||||||
@@ -305,22 +306,6 @@ jobs:
|
|||||||
});
|
});
|
||||||
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`);
|
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}`);
|
||||||
|
|
||||||
// Remove the 'status/need-triage' label
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
name: 'status/need-triage'
|
|
||||||
});
|
|
||||||
core.info(`Successfully removed 'status/need-triage' label.`);
|
|
||||||
} catch (error) {
|
|
||||||
// If the label doesn't exist, the API call will throw a 404. We can ignore this.
|
|
||||||
if (error.status !== 404) {
|
|
||||||
core.warning(`Failed to remove 'status/need-triage': ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: 'Post Issue Analysis Failure Comment'
|
- name: 'Post Issue Analysis Failure Comment'
|
||||||
if: |-
|
if: |-
|
||||||
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}
|
${{ failure() && steps.gemini_issue_analysis.outcome == 'failure' }}
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ jobs:
|
|||||||
permission-issues: 'write'
|
permission-issues: 'write'
|
||||||
|
|
||||||
- name: 'Get issue from event'
|
- name: 'Get issue from event'
|
||||||
if: "github.event_name == 'issues'"
|
if: |-
|
||||||
|
${{ github.event_name == 'issues' }}
|
||||||
id: 'get_issue_from_event'
|
id: 'get_issue_from_event'
|
||||||
env:
|
env:
|
||||||
ISSUE_EVENT: '${{ toJSON(github.event.issue) }}'
|
ISSUE_EVENT: '${{ toJSON(github.event.issue) }}'
|
||||||
@@ -51,7 +52,8 @@ jobs:
|
|||||||
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
|
echo "✅ Found issue #${{ github.event.issue.number }} from event to triage! 🎯"
|
||||||
|
|
||||||
- name: 'Find untriaged issues'
|
- name: 'Find untriaged issues'
|
||||||
if: "github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'"
|
if: |-
|
||||||
|
${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
|
||||||
id: 'find_issues'
|
id: 'find_issues'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token }}'
|
||||||
@@ -161,7 +163,6 @@ jobs:
|
|||||||
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
|
9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5
|
||||||
- Anything more than 6 versions older than the most recent should add the status/need-retesting label
|
- Anything more than 6 versions older than the most recent should add the status/need-retesting label
|
||||||
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
|
10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below.
|
||||||
- After identifying appropriate labels to an issue, add "status/need-triage" label to labels_to_remove in the output.
|
|
||||||
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
|
11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation.
|
||||||
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
|
12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label.
|
||||||
|
|
||||||
@@ -262,24 +263,6 @@ jobs:
|
|||||||
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`);
|
core.info(`Successfully added labels for #${issueNumber}: ${labelsToAdd.join(', ')}${explanation}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.labels_to_remove && entry.labels_to_remove.length > 0) {
|
|
||||||
for (const label of entry.labels_to_remove) {
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
name: label
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (error.status !== 404) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
core.info(`Successfully removed labels for #${issueNumber}: ${entry.labels_to_remove.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.explanation) {
|
if (entry.explanation) {
|
||||||
await github.rest.issues.createComment({
|
await github.rest.issues.createComment({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
|
|||||||
46
.github/workflows/issue-opened-labeler.yml
vendored
Normal file
46
.github/workflows/issue-opened-labeler.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: '🏷️ Issue Opened Labeler'
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- 'opened'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label-issue:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
if: |-
|
||||||
|
${{ github.repository == 'google-gemini/gemini-cli' || github.repository == 'google-gemini/maintainers-gemini-cli' }}
|
||||||
|
steps:
|
||||||
|
- name: 'Generate GitHub App Token'
|
||||||
|
id: 'generate_token'
|
||||||
|
env:
|
||||||
|
APP_ID: '${{ secrets.APP_ID }}'
|
||||||
|
if: |-
|
||||||
|
${{ env.APP_ID != '' }}
|
||||||
|
uses: 'actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b' # ratchet:actions/create-github-app-token@v2
|
||||||
|
with:
|
||||||
|
app-id: '${{ secrets.APP_ID }}'
|
||||||
|
private-key: '${{ secrets.PRIVATE_KEY }}'
|
||||||
|
|
||||||
|
- name: 'Add need-triage label'
|
||||||
|
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||||
|
with:
|
||||||
|
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||||
|
script: |-
|
||||||
|
const { data: issue } = await github.rest.issues.get({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasLabel = issue.labels.some(l => l.name === 'status/need-triage');
|
||||||
|
if (!hasLabel) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
labels: ['status/need-triage']
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
core.info('Issue already has status/need-triage label. Skipping.');
|
||||||
|
}
|
||||||
@@ -4,37 +4,39 @@
|
|||||||
# Example: ./scripts/batch_triage.sh google-gemini/maintainers-gemini-cli
|
# Example: ./scripts/batch_triage.sh google-gemini/maintainers-gemini-cli
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
REPO="${1:-google-gemini/gemini-cli}"
|
REPO="${1:-google-gemini/gemini-cli}"
|
||||||
WORKFLOW="gemini-automated-issue-triage.yml"
|
WORKFLOW="gemini-automated-issue-triage.yml"
|
||||||
|
|
||||||
echo "🔍 Searching for open issues in '$REPO' that need triage (missing 'area/' label)..."
|
echo "🔍 Searching for open issues in '${REPO}' that need triage (missing 'area/' label)..."
|
||||||
|
|
||||||
# Fetch open issues with number, title, and labels
|
# Fetch open issues with number, title, and labels
|
||||||
# We fetch up to 1000 issues.
|
# We fetch up to 1000 issues.
|
||||||
ISSUES_JSON=$(gh issue list --repo "$REPO" --state open --limit 1000 --json number,title,labels)
|
ISSUES_JSON=$(gh issue list --repo "${REPO}" --state open --limit 1000 --json number,title,labels)
|
||||||
|
|
||||||
# Filter issues that DO NOT have a label starting with 'area/'
|
# Filter issues that DO NOT have a label starting with 'area/'
|
||||||
TARGET_ISSUES=$(echo "$ISSUES_JSON" | jq '[.[] | select(.labels | map(.name) | any(startswith("area/")) | not)]')
|
TARGET_ISSUES=$(echo "${ISSUES_JSON}" | jq '[.[] | select(.labels | map(.name) | any(startswith("area/")) | not)]')
|
||||||
|
|
||||||
COUNT=$(echo "$TARGET_ISSUES" | jq '. | length')
|
# Avoid masking return value
|
||||||
|
COUNT=$(jq '. | length' <<< "${TARGET_ISSUES}")
|
||||||
|
|
||||||
if [ "$COUNT" -eq 0 ]; then
|
if [[ "${COUNT}" -eq 0 ]]; then
|
||||||
echo "✅ No issues found needing triage in '$REPO'."
|
echo "✅ No issues found needing triage in '${REPO}'."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "🚀 Found $COUNT issues to triage."
|
echo "🚀 Found ${COUNT} issues to triage."
|
||||||
|
|
||||||
# Loop through and trigger workflow
|
# Loop through and trigger workflow
|
||||||
echo "$TARGET_ISSUES" | jq -r '.[] | "\(.number)|\(.title)"' | while IFS="|" read -r number title; do
|
echo "${TARGET_ISSUES}" | jq -r '.[] | "\(.number)|\(.title)"' | while IFS="|" read -r number title; do
|
||||||
echo "▶️ Triggering triage for #$number: $title"
|
echo "▶️ Triggering triage for #${number}: ${title}"
|
||||||
|
|
||||||
# Trigger the workflow dispatch event
|
# Trigger the workflow dispatch event
|
||||||
gh workflow run "$WORKFLOW" --repo "$REPO" -f issue_number="$number"
|
gh workflow run "${WORKFLOW}" --repo "${REPO}" -f issue_number="${number}"
|
||||||
|
|
||||||
# Sleep briefly to be nice to the API
|
# Sleep briefly to be nice to the API
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "🎉 All triage workflows triggered!"
|
echo "🎉 All triage workflows triggered!"
|
||||||
@@ -3,40 +3,42 @@
|
|||||||
# Usage: ./scripts/relabel_issues.sh <old-label> <new-label> [repository]
|
# Usage: ./scripts/relabel_issues.sh <old-label> <new-label> [repository]
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
OLD_LABEL="$1"
|
OLD_LABEL="${1}"
|
||||||
NEW_LABEL="$2"
|
NEW_LABEL="${2}"
|
||||||
REPO="${3:-google-gemini/gemini-cli}"
|
REPO="${3:-google-gemini/gemini-cli}"
|
||||||
|
|
||||||
if [ -z "$OLD_LABEL" ] || [ -z "$NEW_LABEL" ]; then
|
if [[ -z "${OLD_LABEL}" ]] || [[ -z "${NEW_LABEL}" ]]; then
|
||||||
echo "Usage: $0 <old-label> <new-label> [repository]"
|
echo "Usage: $0 <old-label> <new-label> [repository]"
|
||||||
echo "Example: $0 'area/models' 'area/agent'"
|
echo "Example: $0 'area/models' 'area/agent'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "🔍 Searching for open issues in '$REPO' with label '$OLD_LABEL'..."
|
echo "🔍 Searching for open issues in '${REPO}' with label '${OLD_LABEL}'..."
|
||||||
|
|
||||||
# Fetch issues with the old label
|
# Fetch issues with the old label
|
||||||
ISSUES=$(gh issue list --repo "$REPO" --label "$OLD_LABEL" --state open --limit 1000 --json number,title)
|
ISSUES=$(gh issue list --repo "${REPO}" --label "${OLD_LABEL}" --state open --limit 1000 --json number,title)
|
||||||
|
|
||||||
COUNT=$(echo "$ISSUES" | jq '. | length')
|
# Avoid masking return value
|
||||||
|
COUNT=$(jq '. | length' <<< "${ISSUES}")
|
||||||
|
|
||||||
if [ "$COUNT" -eq 0 ]; then
|
if [[ "${COUNT}" -eq 0 ]]; then
|
||||||
echo "✅ No issues found with label '$OLD_LABEL'."
|
echo "✅ No issues found with label '${OLD_LABEL}'."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "found $COUNT issues to relabel."
|
echo "found ${COUNT} issues to relabel."
|
||||||
|
|
||||||
# Iterate and update
|
# Iterate and update
|
||||||
echo "$ISSUES" | jq -r '.[] | "\(.number) \(.title)"' | while read -r number title; do
|
echo "${ISSUES}" | jq -r '.[] | "\(.number) \(.title)"' | while read -r number title; do
|
||||||
echo "🔄 Processing #$number: $title"
|
echo "🔄 Processing #${number}: ${title}"
|
||||||
echo " - Removing: $OLD_LABEL"
|
echo " - Removing: ${OLD_LABEL}"
|
||||||
echo " + Adding: $NEW_LABEL"
|
echo " + Adding: ${NEW_LABEL}"
|
||||||
|
|
||||||
gh issue edit "$number" --repo "$REPO" --add-label "$NEW_LABEL" --remove-label "$OLD_LABEL"
|
gh issue edit "${number}" --repo "${REPO}" --add-label "${NEW_LABEL}" --remove-label "${OLD_LABEL}"
|
||||||
|
|
||||||
echo " ✅ Done."
|
echo " ✅ Done."
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "🎉 All issues relabeled!"
|
echo "🎉 All issues relabeled!"
|
||||||
@@ -30,9 +30,10 @@
|
|||||||
set -e -E
|
set -e -E
|
||||||
|
|
||||||
# Load environment variables from .env if it exists
|
# Load environment variables from .env if it exists
|
||||||
if [ -f ".env" ]; then
|
if [[ -f ".env" ]]; then
|
||||||
echo "Loading environment variables from .env file..."
|
echo "Loading environment variables from .env file..."
|
||||||
set -a # Automatically export all variables
|
set -a # Automatically export all variables
|
||||||
|
# shellcheck source=/dev/null
|
||||||
source .env
|
source .env
|
||||||
set +a
|
set +a
|
||||||
fi
|
fi
|
||||||
@@ -49,32 +50,32 @@ STREAM_MODE=false
|
|||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
while [[ "$#" -gt 0 ]]; do
|
while [[ "$#" -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--payload) PAYLOAD_FILE="$2"; shift ;;
|
--payload) PAYLOAD_FILE="${2}"; shift ;;
|
||||||
--model) MODEL_ID="$2"; shift ;;
|
--model) MODEL_ID="${2}"; shift ;;
|
||||||
--stream) STREAM_MODE=true ;;
|
--stream) STREAM_MODE=true ;;
|
||||||
*) echo "Unknown parameter passed: $1"; usage ;;
|
*) echo "Unknown parameter passed: ${1}"; usage ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
# Validate inputs
|
# Validate inputs
|
||||||
if [ -z "$PAYLOAD_FILE" ] || [ -z "$MODEL_ID" ]; then
|
if [[ -z "${PAYLOAD_FILE}" ]] || [[ -z "${MODEL_ID}" ]]; then
|
||||||
echo "Error: Missing required arguments."
|
echo "Error: Missing required arguments."
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$GEMINI_API_KEY" ]; then
|
if [[ -z "${GEMINI_API_KEY}" ]]; then
|
||||||
echo "Error: GEMINI_API_KEY environment variable is not set."
|
echo "Error: GEMINI_API_KEY environment variable is not set."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "$PAYLOAD_FILE" ]; then
|
if [[ ! -f "${PAYLOAD_FILE}" ]]; then
|
||||||
echo "Error: Payload file '$PAYLOAD_FILE' does not exist."
|
echo "Error: Payload file '${PAYLOAD_FILE}' does not exist."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# API Endpoint definition
|
# API Endpoint definition
|
||||||
if [ "$STREAM_MODE" = true ]; then
|
if [[ "${STREAM_MODE}" = true ]]; then
|
||||||
GENERATE_CONTENT_API="streamGenerateContent"
|
GENERATE_CONTENT_API="streamGenerateContent"
|
||||||
echo "Mode: Streaming"
|
echo "Mode: Streaming"
|
||||||
else
|
else
|
||||||
@@ -82,16 +83,18 @@ else
|
|||||||
echo "Mode: Non-streaming (Default)"
|
echo "Mode: Non-streaming (Default)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Sending request to model: $MODEL_ID"
|
echo "Sending request to model: ${MODEL_ID}"
|
||||||
echo "Using payload from: $PAYLOAD_FILE"
|
echo "Using payload from: ${PAYLOAD_FILE}"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
|
|
||||||
# Make the cURL request. If non-streaming, pipe through jq for readability if available.
|
# Make the cURL request. If non-streaming, pipe through jq for readability if available.
|
||||||
if [ "$STREAM_MODE" = false ] && command -v jq &> /dev/null; then
|
if [[ "${STREAM_MODE}" = false ]] && command -v jq &> /dev/null; then
|
||||||
curl -s -X POST \
|
# Invoke curl separately to avoid masking its return value
|
||||||
|
output=$(curl -s -X POST \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:${GENERATE_CONTENT_API}?key=${GEMINI_API_KEY}" \
|
"https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:${GENERATE_CONTENT_API}?key=${GEMINI_API_KEY}" \
|
||||||
-d "@${PAYLOAD_FILE}" | jq .
|
-d "@${PAYLOAD_FILE}")
|
||||||
|
echo "${output}" | jq .
|
||||||
else
|
else
|
||||||
curl -X POST \
|
curl -X POST \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
|||||||
Reference in New Issue
Block a user