mirror of
https://github.com/google-gemini/gemini-cli
synced 2026-04-21 13:37:17 +00:00
Merge f6aa8f70f4 into a38e2f0048
This commit is contained in:
commit
29e2730d65
4 changed files with 396 additions and 0 deletions
39
.gemini/skills/github-issue-triage/SKILL.md
Normal file
39
.gemini/skills/github-issue-triage/SKILL.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
name: github-issue-triage
|
||||
description: Analyzes and cleans up GitHub issues. DO NOT trigger this skill automatically. ONLY use when the user explicitly mentions "github-issue-triage", or explicitly asks to "triage issues", "clean up old issues", or "triage this issue".
|
||||
---
|
||||
|
||||
# GitHub Issue Triage
|
||||
|
||||
This skill provides workflows for finding, analyzing, and triaging GitHub issues to maintain a clean and actionable backlog.
|
||||
|
||||
## Phase 1: Discovery (Optional)
|
||||
|
||||
If the user asks you to "triage issues" or "clean up old issues" without providing a specific issue URL, you must first find candidate issues.
|
||||
|
||||
Run the following script to get a list of issues:
|
||||
`node scripts/find_issues.cjs <owner/repo>` (e.g., `node scripts/find_issues.cjs google-gemini/gemini-cli`)
|
||||
|
||||
You may optionally pass a custom search string and limit.
|
||||
`node scripts/find_issues.cjs <owner/repo> "<search_string>" <limit>`
|
||||
|
||||
Pick the first issue from the list to triage and proceed to Phase 2. If the user provided a specific issue URL, start at Phase 2 directly.
|
||||
|
||||
## Phase 2: Analysis
|
||||
|
||||
For the target issue, you must run the analysis script to gather metadata and determine staleness/inactivity heuristics.
|
||||
|
||||
Run:
|
||||
`node scripts/analyze_issue.cjs <issue_url> "<optional_comma_separated_maintainers>"`
|
||||
|
||||
Read the JSON output carefully.
|
||||
- If `is_stale` is `true`, the issue has already been marked as stale and should be closed according to the rules in Phase 3.
|
||||
- Take note of `inactive_over_30_days`, `inactive_over_60_days`, `is_epic`, and other boolean flags.
|
||||
|
||||
## Phase 3: Triage Execution
|
||||
|
||||
After analyzing the issue and receiving the JSON output, you MUST consult the detailed triage rules to determine the next steps.
|
||||
|
||||
Read the rules in [references/triage_rules.md](references/triage_rules.md) and execute the appropriate steps. You must follow the steps sequentially.
|
||||
|
||||
If a step instructs you to **STOP EXECUTION**, you must conclude your work on this issue and not proceed to subsequent steps. If you are triaging a batch of issues, you may move on to the next issue in the list.
|
||||
162
.gemini/skills/github-issue-triage/references/triage_rules.md
Normal file
162
.gemini/skills/github-issue-triage/references/triage_rules.md
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# Issue Triage Rules
|
||||
|
||||
**Important Note on CLI Commands**: When posting comments via the `gh` CLI that contain newlines (e.g. `\n`), you MUST use bash command substitution with `echo -e` so the newlines are rendered correctly. For example: `gh issue comment <issue_url> --body "$(echo -e "### Triage Summary\n\n<your summary>")"`.
|
||||
|
||||
When executing triage on an issue, you must evaluate the following steps sequentially using the data provided from `scripts/analyze_issue.cjs` and the issue comments (`gh issue view <issue_url> --json comments`).
|
||||
|
||||
## Categorization Guide: Help-wanted
|
||||
When categorizing an issue, determine if it is a good candidate for community contributions. Only use the **Help-wanted** label for these types of issues. Everything else remains unassigned to a specific whitelist label. Examples of **Help-wanted** issues include:
|
||||
* Small, well-defined features.
|
||||
* Easy-to-fix bugs (where the root cause might be identified but the fix isn't trivially "simple" enough to just patch immediately).
|
||||
* Tasks that are clearly scoped and ready for external help.
|
||||
* Issues that DO NOT require deep architectural knowledge, significant maintainer review time, modifications to core sensitive business logic (telemetry, security, billing), or sweeping UI/UX changes.
|
||||
|
||||
Conversely, do **NOT** use the `help wanted` label for issues such as:
|
||||
* Easily reproducible bugs with a simple identified fix.
|
||||
* Epics or roadmap initiatives.
|
||||
* Changes to core architecture, sensitive security fixes, or internal tasks.
|
||||
* Issues requiring deep investigation.
|
||||
* Changes to key UI/UX that affect all users.
|
||||
* Modifications to core internal data structures and IPC mechanisms.
|
||||
* Changes to token/billing logic.
|
||||
* Changes to key model and diff/edit functionalities.
|
||||
* Features or changes that touch multiple parts of the codebase and would require significant reviewer time from maintainers.
|
||||
* Changes touching key workflows (like the core stream or execution workflows) that affect all users and require careful architectural consideration.
|
||||
* Changes touching sensitive business logic, telemetry, or API usage statistics/tracking.
|
||||
* Proposals affecting community guidelines, contributor frameworks, or project governance that require significant maintainer discretion and alignment.
|
||||
* Complex new subsystems, such as background LLM integration tasks, AI-driven automation, or new agentic behaviors that require significant design validation.
|
||||
|
||||
## Step 1: Resolution Check
|
||||
**CRITICAL MISTAKE PREVENTION**: You MUST thoroughly read every single comment. Look explicitly for phrases like "appears to fix", "fixes this", "might be fixed", "resolved by", "no longer an issue", or links to other PRs/issues that suggest a resolution. Do not skim. If a user (even a non-maintainer) suggests a fix or PR exists, and the original reporter has not contradicted them, you MUST treat the issue as resolved.
|
||||
|
||||
Read ALL the comments from the issue carefully, and review the JSON output for `cross_references`.
|
||||
Check if ANY of the following conditions are met:
|
||||
1. Is there a cross-referenced PR in `cross_references` where `is_pr` is `true` and `is_merged` is `true`? If so, use `gh pr view <pr_url> --json title,body` to verify that the PR actually addresses and resolves the issue's request. If it does not, treat condition 1 as NOT met.
|
||||
2. Is it fixed, resolved, no longer reproducible, or functioning properly in the comments? (e.g., someone mentions it `might be fixed`, `should be fixed`, or `no longer an issue`) AND the reporter has not replied afterward to contradict this?
|
||||
3. Is there a workaround provided in the comments?
|
||||
4. Does someone state the issue is actually unrelated to this repository/project, or is an external problem (like a terminal emulator bug)?
|
||||
|
||||
- If condition 1 is met: Execute `gh issue close <issue_url> --comment "Closing because this issue was referenced by a merged pull request. Feel free to reopen if the problem persists or if the PR did not fully resolve this." --reason "completed"` and **STOP EXECUTION**.
|
||||
- If condition 2 or 3 is met: Execute `gh issue close <issue_url> --comment "Closing because the comments indicate this issue might be fixed, has a workaround, is no longer an issue, or is resolved. Feel free to reopen if the problem persists." --reason "completed"` and **STOP EXECUTION**.
|
||||
- If condition 4 is met: Execute `gh issue close <issue_url> --comment "Closing because the comments indicate this issue is unrelated to this project or is an external problem." --reason "not planned"` and **STOP EXECUTION**.
|
||||
- If NONE of these conditions are met: Proceed to Step 1.1.
|
||||
|
||||
## Step 1.1: Existing Feature Check
|
||||
**CRITICAL MISTAKE PREVENTION**: If the issue describes a feature request or enhancement (regardless of whether the JSON `is_feature_request` flag is true or false), you **MUST explicitly search the codebase** to verify if it is already implemented. You cannot skip this step for feature requests.
|
||||
1. Use the `grep_search` tool to look for relevant keywords related to the feature in files like `schemas/settings.schema.json`, `packages/cli/src/config/config.ts`, command definitions, or UI components.
|
||||
- **Hotkeys & UI Actions**: If the user asks for a way to expand text, pause output, copy text, or perform a UI action, explicitly check `packages/cli/src/ui/key/keyBindings.ts` and the `Command` enum. Many interactive features (e.g., `Ctrl+O` for expanding truncated tool confirmations or output) already exist natively.
|
||||
2. If you verify that the requested functionality (e.g., a setting, flag, hotkey, or command) already exists natively:
|
||||
- Execute `gh issue close <issue_url> --comment "This feature is actually already implemented! <Provide a brief explanation of how to use the feature, such as the command to run, the setting to change, or the hotkey to press>.\n\nI'm going to close this issue since the functionality already exists natively. Let us know if you run into any other issues!" --reason "completed"`
|
||||
- **STOP EXECUTION**.
|
||||
3. If the feature does NOT exist, proceed to Step 1.2.
|
||||
|
||||
## Step 1.2: Closed PR Re-evaluation
|
||||
If there is a cross-referenced PR in `cross_references` where `is_pr` is `true` and `is_merged` is `false` (and its state is `closed` or it has an automated closure comment):
|
||||
1. Use `gh pr view <pr_url> --json author,comments,state,title,body` to analyze the PR.
|
||||
2. Check the comments to see if it was closed by an automated bot (e.g., `gemini-cli` bot closing it automatically due to missing labels like 'help wanted' after 14 days).
|
||||
3. Analyze the PR's title, body, and comments to determine if it implements a valid and useful feature/fix and is worth resuming.
|
||||
4. **Existing Feature Check**: Check if the requested feature is actually already implemented natively in the codebase (e.g., an existing hotkey, setting, or command). Search the codebase using `grep_search` if unsure.
|
||||
- If the feature is ALREADY IMPLEMENTED:
|
||||
a. Do NOT reopen the PR.
|
||||
b. Execute `gh issue close <issue_url> --comment "This feature is actually already implemented! <Provide a brief explanation of how to use the feature>.\n\nI'm going to close this issue since the functionality already exists natively. Let us know if you run into any other issues!" --reason "completed"`
|
||||
c. Comment on the PR: `gh pr comment <pr_url> --body "@<author_username>, thank you for your contribution! We've reviewed this again and decided to keep it closed. The core feature requested in the parent issue is actually already implemented natively in the CLI. We appreciate the effort, but this specific PR is no longer needed!"`
|
||||
d. **STOP EXECUTION**.
|
||||
5. Critically evaluate the PR's approach for correctness and safety. Ensure it does not introduce breaking changes, make false assumptions (e.g., making an optional configuration mandatory for all users), or negatively impact other workflows.
|
||||
- If the PR's approach is flawed or introduces breaking changes:
|
||||
a. Do NOT reopen the PR.
|
||||
b. Determine if the issue itself should be **Help-wanted** (using the Categorization Guide).
|
||||
c. If **Help-wanted**, run `gh issue edit <issue_url> --remove-label "status/need-triage" --add-label "help wanted"`. If not, just remove `status/need-triage`.
|
||||
d. Execute `gh issue comment <issue_url> --body "### Triage Summary\n\nWhile this issue is valid, the proposed fix in PR <pr_url> introduces potential breaking changes or relies on incorrect assumptions (e.g., <brief explanation of the flaw>). Therefore, we will not reopen that PR, but we still welcome a different approach to fix this issue!"`
|
||||
e. Comment on the PR: `gh pr comment <pr_url> --body "@<author_username>, thank you for your contribution! We've reviewed the approach in this PR and decided to keep it closed because <brief explanation of the flaw>. We still welcome improvements here using a different approach! Feel free to open a new PR if you're interested."`
|
||||
f. **STOP EXECUTION**.
|
||||
- If it is worth resuming AND was closed by a bot:
|
||||
a. Determine if the issue should be **Help-wanted** (using the Categorization Guide).
|
||||
b. If it should NOT be **Help-wanted**:
|
||||
- Execute `gh issue edit <issue_url> --remove-label "status/need-triage"`
|
||||
- Execute `gh issue comment <issue_url> --body "$(echo -e "### Triage Summary\n\n<brief explanation of why this issue requires maintainer attention and is not suitable for community contribution>")"`
|
||||
- **STOP EXECUTION**. (Do not reopen the PR).
|
||||
c. If it should be **Help-wanted**:
|
||||
- Reopen the PR: `gh pr reopen <pr_url>`
|
||||
- Assign the PR to the author: `gh pr edit <pr_url> --add-assignee <author_username>`
|
||||
- Assign the issue to the author: `gh issue edit <issue_url> --add-assignee <author_username>`
|
||||
- Add the help wanted label to the issue to prevent the bot from closing the PR again: `gh issue edit <issue_url> --add-label "help wanted"`
|
||||
- Comment on the issue: `gh issue comment <issue_url> --body "### Triage Summary\n\n<brief explanation of why this is a help-wanted task>\n\n@<author_username>, apologies! It looks like your PR <pr_url> was incorrectly closed by our bot. I have reopened it and assigned this issue to you. Would you like to continue working on it?"`
|
||||
- Comment on the PR: `gh pr comment <pr_url> --body "@<author_username>, apologies for the bot closing this PR! We have reopened it. Please sync your branch to the latest \`main\` and we will have someone review it shortly."`
|
||||
- Execute `gh issue edit <issue_url> --remove-label "status/need-triage"`
|
||||
- **STOP EXECUTION**.
|
||||
- If NOT met: Proceed to Step 1.5.
|
||||
|
||||
## Step 1.5: Pending Response Check
|
||||
Read ALL the comments from the issue carefully.
|
||||
1. Check if the most recent comments include a request for more information, clarification, or reproduction steps directed at the reporter from any other user (maintainer or community member).
|
||||
2. Check if the reporter has NOT replied to that request.
|
||||
3. Check if that request was made over 14 days ago. (You can check the date of the comment vs today's date).
|
||||
4. **CRITICAL**: Before closing, verify if OTHER users have chimed in after the request to provide the necessary context, answer the question on behalf of the reporter, or confirm the bug's existence. If they have, the issue is NO LONGER pending response and you must proceed to Step 2.
|
||||
|
||||
- If conditions 1, 2, and 3 are met AND condition 4 is false: Execute `gh issue close <issue_url> --comment "Closing because more information was requested over 2 weeks ago and we haven't received a response. Feel free to reopen if you can provide the requested details." --reason "not planned"` and **STOP EXECUTION**.
|
||||
- If NOT met: Proceed to Step 2.
|
||||
|
||||
## Step 2: Assignee and Inactivity Handling
|
||||
Use the JSON output from `analyze_issue.cjs` to determine necessary actions.
|
||||
|
||||
**CRITICAL VERIFICATION**: Before proceeding, explicitly verify the exact boolean values of `inactive_over_60_days`, `inactive_over_30_days`, and `is_feature_request` in the JSON output. Do not guess these values based on the date. Note that `is_feature_request` might be `false` even for feature requests if the title/labels lack specific keywords; if the body clearly asks for a new feature/enhancement, treat it as a feature request regardless of the JSON flag.
|
||||
|
||||
1. **Assignee Check:** If an assignee is a contributor and hasn't made any updates on the issue for over 2 weeks, execute `gh issue edit <issue_url> --remove-assignee <username>` to remove them. (Do this before proceeding further).
|
||||
2. **Inactivity Check:**
|
||||
- If `inactive_over_60_days` is `true`:
|
||||
a. Formulate a comment to the reporter (@<reporter_username>). Evaluate the issue description and whether it is an Epic (`is_epic`):
|
||||
- Always mention that the issue is being closed or pinged because it has been inactive for over 60 days.
|
||||
- IF IT IS AN EPIC: Ask the reporter if it is still in progress or complete, and if it is complete, ask them to close it.
|
||||
- IF IT IS NOT AN EPIC:
|
||||
- If it's a feature/enhancement request and the description is relatively vague, ask: 1) if it is still needed and 2) if they can provide more details on the feature request.
|
||||
- If the issue was mentioned by another issue that is closed as completed or by a pull request that is merged/closed (check `cross_references`), mention this cross-reference (e.g., "I see this issue was mentioned by #123 which is closed as completed...") and ask if this means it is resolved. Do NOT mention cross-references that are still open or closed as "not planned".
|
||||
- If it's a feature/enhancement request but well-described, just ask if it's still needed.
|
||||
- If it's a bug, ask if they can reproduce it with the latest build and provide detailed reproduction steps.
|
||||
- If the issue has assignees, append a ping to the assignees to check in.
|
||||
- If it is NOT an Epic AND `is_tracked_by_epic` is `false`, append "Feel free to reopen this issue." to the comment.
|
||||
b. Execute `gh issue edit <issue_url> --remove-label "status/need-triage"`. If it is NOT an Epic, also append `--add-label "status/needs-info"`.
|
||||
c. Execute `gh issue comment <issue_url> --body "<your formulated comment>"`
|
||||
d. If it is NOT an Epic AND `is_tracked_by_epic` is `false`, execute `gh issue close <issue_url> --reason "not planned"`.
|
||||
- After executing these actions, **STOP EXECUTION**.
|
||||
- If `inactive_over_30_days` is `true` AND it is a bug report (`is_feature_request` is `false`) AND it is NOT an Epic (`is_epic` is `false`):
|
||||
a. Execute `gh issue edit <issue_url> --remove-label "status/need-triage" --add-label "status/needs-info"`.
|
||||
b. Execute `gh issue comment <issue_url> --body "@<reporter_username>, this issue has been inactive for over a month. Could you please try reproducing it with the latest nightly build and let us know if it still occurs? If we don't hear back, we will close this issue on <deadline_date>."`
|
||||
- After executing these actions, **STOP EXECUTION**.
|
||||
- If neither condition is met, proceed to Step 3.
|
||||
|
||||
## Step 3: Vagueness Check
|
||||
**CRITICAL MISTAKE PREVENTION**: You MUST NOT skip this step just because you recognize the problem domain as complex or requiring maintainer attention. No matter how obvious the problem domain might seem, a bug report without explicit, step-by-step reproduction instructions must be halted and marked as vague first.
|
||||
|
||||
Is the issue fundamentally missing context AND no one has asked for more information yet?
|
||||
- **For bugs**: Explicit reproduction steps are **REQUIRED**. Even if the user provides logs, error traces, or screenshots, if they do not provide clear, step-by-step instructions on how to reproduce the bug, it MUST be considered vague.
|
||||
- **For feature requests**: If it is just a vague statement without clear use cases or details, it is considered vague.
|
||||
- If YES (it is vague): Execute `gh issue edit <issue_url> --remove-label "status/need-triage" --add-label "status/needs-info"`. Ask the reporter: `gh issue comment <issue_url> --body "@<reporter_username>, thank you for the report! Could you please provide more specific details (e.g., detailed reproduction steps, expected behavior, logs, and environment details)? Closing this as vague if no response is received in a week."` and **STOP EXECUTION**.
|
||||
- If NO: Proceed to Step 4.
|
||||
|
||||
## Step 4: Reproduction & Code Validity
|
||||
1. Review the issue comments. If a community member has already clearly identified the root cause of the bug or answered the feature request, DO NOT investigate the code. Proceed to Step 5.
|
||||
2. Clone the target repository to a temporary directory (`git clone <repo_url> target-repo`).
|
||||
3. Search the `target-repo/` codebase using `grep_search` and `read_file` ONLY. You are explicitly FORBIDDEN from writing new files, running tests, attempting to fix the code, OR attempting to reproduce the bug by executing code or shell commands. Your ONLY goal is to perform STATIC code analysis to determine if the logic for the bug still exists or if the reported behavior is actually intentional by design.
|
||||
- If definitively NO LONGER VALID: Close it: `gh issue close <issue_url> --comment "Closing because I have verified this works correctly in the latest codebase. <brief explanation>"` and **STOP EXECUTION**.
|
||||
- If INTENTIONAL BY DESIGN: Close it: `gh issue close <issue_url> --reason "not planned" --comment "Closing this issue as the reported behavior is intentional by design. <brief explanation of the design logic>"` and **STOP EXECUTION**.
|
||||
- If still valid: Proceed to Step 5.
|
||||
|
||||
## Step 5: Duplicates
|
||||
Search for duplicates using `gh issue list --search "<keywords>" --repo <owner/repo> --state all`.
|
||||
- **CRITICAL**: Pay special attention to newer issues that might already have active pull requests or more detailed context. If a duplicate exists that already has an active PR or more maintainer engagement, close the *current* issue you are triaging in favor of the active one.
|
||||
- If found: `gh issue close <issue_url> --reason "not planned" --comment "Closing as duplicate of #<duplicate_number>."` and **STOP EXECUTION**.
|
||||
- If no duplicates: Proceed to Step 6.
|
||||
|
||||
## Step 6: Triage Summary
|
||||
Review the issue comments to see if a community member has already identified the root cause.
|
||||
- Determine if it should be **Help-wanted** (using the Categorization Guide) and explain why in your summary.
|
||||
- If you categorized the issue as **Help-wanted**, run `gh issue edit <issue_url> --remove-label "status/need-triage" --add-label "help wanted"`.
|
||||
- If it does not fit **Help-wanted**, you must still explain *why* it requires maintainer attention or why it's not a good fit for community contribution (e.g., "This issue touches core architecture and requires significant maintainer review time"). Simply run `gh issue edit <issue_url> --remove-label "status/need-triage"` to mark it triaged without a specific whitelist label.
|
||||
- Action: `gh issue comment <issue_url> --body "### Triage Summary\n\n<your summary>"`
|
||||
- **STOP EXECUTION**.
|
||||
|
||||
## Mandatory Final Step
|
||||
Every issue that is triaged and remains OPEN MUST be assigned at least one of the following labels before you finish your triage process for that issue:
|
||||
- `status/needs-info` (for inactive, vague, or issues requiring more details)
|
||||
- `help wanted` (for well-defined, community-friendly issues)
|
||||
- NONE of the above (If the issue is triaged, valid, but too complex for a community contribution, it should simply remain open without `status/need-triage`). If an issue reaches the end of triage and remains open, you must remove `status/need-triage`.
|
||||
161
.gemini/skills/github-issue-triage/scripts/analyze_issue.cjs
Normal file
161
.gemini/skills/github-issue-triage/scripts/analyze_issue.cjs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
const { execSync } = require('child_process');
|
||||
|
||||
function runCommand(cmd) {
|
||||
try {
|
||||
return execSync(cmd, { encoding: 'utf-8' }).trim();
|
||||
} catch (error) {
|
||||
// Return empty or structured error so the agent can see it rather than failing the whole script
|
||||
return JSON.stringify({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function analyzeIssue(issueLink, maintainersList) {
|
||||
if (!issueLink) {
|
||||
console.error(JSON.stringify({ error: "Issue link is required." }));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const parts = issueLink.split('/');
|
||||
const issueNumberStr = parts[parts.length - 1];
|
||||
const issueNumber = parseInt(issueNumberStr, 10);
|
||||
const repoName = parts[parts.length - 3];
|
||||
const repoOwner = parts[parts.length - 4];
|
||||
const repo = `${repoOwner}/${repoName}`;
|
||||
|
||||
const maintainers = maintainersList ? maintainersList.split(',').map(m => m.trim()) : [];
|
||||
|
||||
const deadline = new Date();
|
||||
deadline.setDate(deadline.getDate() + 14);
|
||||
|
||||
const result = {
|
||||
issue_link: issueLink,
|
||||
repo: repo,
|
||||
issue_number: issueNumber,
|
||||
is_tracked_by_epic: false,
|
||||
is_stale: false,
|
||||
inactive_over_30_days: false,
|
||||
inactive_over_60_days: false,
|
||||
has_assignees: false,
|
||||
is_feature_request: false,
|
||||
is_high_priority: false,
|
||||
is_epic: false,
|
||||
reporter: null,
|
||||
assignees: [],
|
||||
labels: [],
|
||||
cross_references: [],
|
||||
deadline_date: deadline.toISOString().split('T')[0]
|
||||
};
|
||||
|
||||
try {
|
||||
// 1. Fetch Issue Data
|
||||
const issueDataRaw = runCommand(`gh issue view ${issueLink} --json title,body,author,comments,labels,assignees,updatedAt`);
|
||||
if (issueDataRaw.startsWith('{"error"')) {
|
||||
console.error(issueDataRaw);
|
||||
process.exit(1);
|
||||
}
|
||||
const issue = JSON.parse(issueDataRaw);
|
||||
|
||||
result.reporter = issue.author.login;
|
||||
result.assignees = issue.assignees.map(a => a.login);
|
||||
result.has_assignees = result.assignees.length > 0;
|
||||
result.labels = issue.labels.map(l => l.name);
|
||||
|
||||
result.is_feature_request = result.labels.some(l => {
|
||||
const lower = l.toLowerCase();
|
||||
return lower.includes('feature') || lower.includes('enhancement');
|
||||
}) || issue.title.toLowerCase().includes('feature') || issue.title.toLowerCase().includes('proposal');
|
||||
|
||||
result.is_high_priority = result.labels.some(l => {
|
||||
const lower = l.toLowerCase();
|
||||
return lower.includes('priority/p0') || lower.includes('priority/p1');
|
||||
});
|
||||
|
||||
// 2. Fetch Timeline for cross references
|
||||
const timelineCmd = `gh api repos/${repo}/issues/${issueNumber}/timeline --jq '[.[] | select(.event == "cross-referenced" and .source.issue)] | map({issue: .source.issue.number, state: .source.issue.state, state_reason: .source.issue.state_reason, is_pr: (.source.issue.pull_request != null), is_merged: (.source.issue.pull_request.merged_at != null)})'`;
|
||||
const timelineRaw = runCommand(timelineCmd);
|
||||
if (!timelineRaw.startsWith('{"error"')) {
|
||||
result.cross_references = JSON.parse(timelineRaw);
|
||||
}
|
||||
|
||||
// 3. Check if Epic (has sub issues or title starts with [Epic])
|
||||
const epicCmd = `gh api repos/${repo}/issues/${issueNumber} --jq '{is_epic: (.sub_issues_summary.total > 0)}'`;
|
||||
const epicRaw = runCommand(epicCmd);
|
||||
if (!epicRaw.startsWith('{"error"')) {
|
||||
result.is_epic = JSON.parse(epicRaw).is_epic || issue.title.toLowerCase().startsWith('[epic]');
|
||||
} else {
|
||||
result.is_epic = issue.title.toLowerCase().startsWith('[epic]');
|
||||
}
|
||||
|
||||
// 4. Check for Parent Issue via GraphQL
|
||||
const query = `query($owner: String!, $repo: String!, $issueNumber: Int!) { repository(owner: $owner, name: $repo) { issue(number: $issueNumber) { trackedInIssues(first: 1) { totalCount } } } }`;
|
||||
const gqlCmd = `gh api graphql -F owner=${repoOwner} -F repo=${repoName} -F issueNumber=${issueNumber} -f query='${query}' --jq '.data.repository.issue.trackedInIssues.totalCount'`;
|
||||
const parentCountRaw = runCommand(gqlCmd);
|
||||
if (!parentCountRaw.startsWith('{"error"')) {
|
||||
const count = parseInt(parentCountRaw, 10);
|
||||
result.is_tracked_by_epic = !isNaN(count) && count > 0;
|
||||
}
|
||||
|
||||
// Staleness Logic
|
||||
if (result.is_tracked_by_epic) {
|
||||
result.is_stale = true;
|
||||
} else {
|
||||
// Find last maintainer comment
|
||||
const maintainerComments = issue.comments.filter(c => {
|
||||
if (c.author.login === result.reporter) return false;
|
||||
|
||||
const isMaintainer = maintainers.includes(c.author.login) ||
|
||||
['OWNER', 'MEMBER', 'COLLABORATOR'].includes(c.authorAssociation);
|
||||
|
||||
if (maintainers.length > 0) return isMaintainer;
|
||||
// Basic bot exclusion if no maintainers defined
|
||||
return !c.author.login.includes('github-actions') && c.author.login !== 'app/github-actions';
|
||||
});
|
||||
|
||||
if (maintainerComments.length > 0) {
|
||||
const lastMaintainerComment = maintainerComments[maintainerComments.length - 1];
|
||||
|
||||
// Did reporter reply after maintainer?
|
||||
const reporterReplied = issue.comments.some(c => {
|
||||
return c.author.login === result.reporter &&
|
||||
new Date(c.updatedAt) > new Date(lastMaintainerComment.updatedAt);
|
||||
});
|
||||
|
||||
if (!reporterReplied) {
|
||||
const daysAgo = (new Date() - new Date(lastMaintainerComment.updatedAt)) / (1000 * 60 * 60 * 24);
|
||||
if (daysAgo > 7) {
|
||||
result.is_stale = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Age / Inactivity check
|
||||
if (!result.is_stale) {
|
||||
const lastUpdateDaysAgo = (new Date() - new Date(issue.updatedAt)) / (1000 * 60 * 60 * 24);
|
||||
if (lastUpdateDaysAgo > 60) {
|
||||
result.inactive_over_60_days = true;
|
||||
result.inactive_over_30_days = true;
|
||||
} else if (lastUpdateDaysAgo > 30) {
|
||||
result.inactive_over_30_days = true;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify({ error: error.message }));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const issueLink = args[0];
|
||||
const maintainers = args[1] || ""; // Comma separated list of maintainers
|
||||
|
||||
if (!issueLink) {
|
||||
console.log("Usage: node analyze_issue.cjs <issue_link> [maintainers_csv]");
|
||||
console.log("Example: node analyze_issue.cjs https://github.com/owner/repo/issues/123 'user1,user2'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
analyzeIssue(issueLink, maintainers);
|
||||
34
.gemini/skills/github-issue-triage/scripts/find_issues.cjs
Normal file
34
.gemini/skills/github-issue-triage/scripts/find_issues.cjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const { execSync } = require('child_process');
|
||||
|
||||
function findIssues(repo, searchString = "label:area/core,area/extensions,area/site,area/non-interactive sort:updated-asc", limit = 10) {
|
||||
if (!repo) {
|
||||
console.error(JSON.stringify({ error: "Repository is required (e.g., owner/repo)" }));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const cmd = `gh issue list --repo ${repo} --state open --search "${searchString}" --json url --limit ${limit}`;
|
||||
const output = execSync(cmd, { encoding: 'utf-8' });
|
||||
const issues = JSON.parse(output);
|
||||
const urls = issues.map(issue => issue.url);
|
||||
|
||||
console.log(JSON.stringify({ issue_urls: urls }, null, 2));
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify({ error: error.message }));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const repo = args[0];
|
||||
const searchString = args[1];
|
||||
const limitStr = args[2];
|
||||
const limit = limitStr ? parseInt(limitStr, 10) : 10;
|
||||
|
||||
if (!repo) {
|
||||
console.log("Usage: node find_issues.cjs <owner/repo> [search_string] [limit]");
|
||||
console.log("Example: node find_issues.cjs google-gemini/gemini-cli 'label:area/core sort:updated-asc' 10");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
findIssues(repo, searchString, limit);
|
||||
Loading…
Reference in a new issue