diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 6c1943fe7b..bc393269f9 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -2,11 +2,10 @@ name: duplicate-issues on: issues: - types: [opened, edited] + types: [opened] jobs: check-duplicates: - if: github.event.action == 'opened' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read @@ -19,159 +18,65 @@ jobs: - uses: ./.github/actions/setup-bun + - name: Install dependencies + run: bun install + - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash + - name: Build prompt + uses: actions/github-script@v8 + with: + script: | + const fs = require("fs") + const issue = context.payload.issue + const body = issue.body ?? "" + const text = [ + "Check this new issue for compliance and duplicates:", + "", + `CURRENT_ISSUE_NUMBER: ${issue.number}`, + "", + `Title: ${issue.title}`, + "", + "Description:", + body, + ].join("\n") + + fs.writeFileSync("issue_info.txt", text) + - name: Check duplicates and compliance env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" - } run: | - opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created: + bun script/duplicate-issue.ts -f issue_info.txt "Check the attached file for issue details and return either a comment body or No action required" > issue_comment.txt - Issue number: ${{ github.event.issue.number }} - - Lookup this issue with gh issue view ${{ github.event.issue.number }}. - - You have TWO tasks. Perform both, then post a SINGLE comment (if needed). - - --- - - TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK - - Check whether the issue follows our contributing guidelines and issue templates. - - This project has three issue templates that every issue MUST use one of: - - 1. Bug Report - requires a Description field with real content - 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: - 3. Question - requires the Question field with real content - - Additionally check: - - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) - - The issue has real content, not just template placeholder text left unchanged - - Bug reports should include some context about how to reproduce - - Feature requests should explain the problem or need - - We want to push for having the user provide system description & information - - Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. - - --- - - TASK 2: DUPLICATE CHECK - - Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates. - Consider: - 1. Similar titles or descriptions - 2. Same error messages or symptoms - 3. Related functionality or components - 4. Similar feature requests - - Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997. - - --- - - POSTING YOUR COMMENT: - - Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows: - - If the issue is NOT compliant, start the comment with: - - Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance - - If duplicates were found, include a section about potential duplicates with links. - - If the issue mentions keybinds/keyboard shortcuts, include a note about #4997. - - If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all. - - Use this format for the comment: - - [If not compliant:] - - This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md). - - **What needs to be fixed:** - - [specific reasons] - - Please edit this issue to address the above within **2 hours**, or it will be automatically closed. - - [If duplicates found, add:] - --- - This issue might be a duplicate of existing issues. Please check: - - #[issue_number]: [brief description of similarity] - - [If keybind-related, add:] - For keybind-related issues, please also check our pinned keybinds documentation: #4997 - - [End with if not compliant:] - If you believe this was flagged incorrectly, please let a maintainer know. - - Remember: post at most ONE comment combining all findings. If everything is fine, post nothing." - - recheck-compliance: - if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ./.github/actions/setup-bun - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Recheck compliance + - name: Post comment and label issue env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENCODE_PERMISSION: | - { - "bash": { - "*": "deny", - "gh issue*": "allow" - }, - "webfetch": "deny" + COMMENT: ${{ github.workspace }}/issue_comment.txt + uses: actions/github-script@v8 + with: + script: | + const fs = require("fs") + const comment = fs.readFileSync(process.env.COMMENT, "utf8").trim() + + if (comment === "No action required") { + core.info("No comment needed") + return } - run: | - opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited. - Lookup this issue with gh issue view ${{ github.event.issue.number }}. + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `_The following comment was made by an LLM, it may be inaccurate:_\n\n${comment}`, + }) - Re-check whether the issue now follows our contributing guidelines and issue templates. + if (!comment.includes("")) return - This project has three issue templates that every issue MUST use one of: - - 1. Bug Report - requires a Description field with real content - 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: - 3. Question - requires the Question field with real content - - Additionally check: - - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) - - The issue has real content, not just template placeholder text left unchanged - - Bug reports should include some context about how to reproduce - - Feature requests should explain the problem or need - - We want to push for having the user provide system description & information - - Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. - - If the issue is NOW compliant: - 1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance - 2. Find and delete the previous compliance comment (the one containing ) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id} - 3. Post a short comment thanking them for updating the issue. - - If the issue is STILL not compliant: - Post a comment explaining what still needs to be fixed. Keep the needs:compliance label." + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: ["needs:compliance"], + }) diff --git a/.opencode/agent/duplicate-issue.md b/.opencode/agent/duplicate-issue.md index 2f2a3d196e..efad62b455 100644 --- a/.opencode/agent/duplicate-issue.md +++ b/.opencode/agent/duplicate-issue.md @@ -6,22 +6,81 @@ color: "#E67E22" tools: "*": false "github-issue-search": true - "github-pr-search": true --- You are a duplicate issue detection agent. When an issue is opened, your job is to search for potentially duplicate or related open issues. -Use the github-issue-search tool to search for issues that might be addressing the same issue or feature. You also have the github-pr-search tool to search for PRs that might be addressing the same issue or feature. +You have two jobs: -IMPORTANT: The input will contain a line `CURRENT_PR_NUMBER: NNNN`. This is the current PR number, you should not mark that the current PR as a duplicate of itself. +1. Check if the issue follows our issue templates/contributing requirements. +2. Check for potential duplicate issues. -Search using keywords from the PR title and description. Try multiple searches with different relevant terms. +Use the github-issue-search tool to find potentially related issues. -If you find potential duplicates: +IMPORTANT: The input will contain a line `CURRENT_ISSUE_NUMBER: NNNN`. Never mark that issue as a duplicate of itself. -- List them with their titles and URLs -- Briefly explain why they might be related +## Compliance checks -If no duplicates are found, say so clearly. BUT ONLY SAY "No duplicate PRs found" (don't say anything else if no dups) +This project has three issue templates: -Keep your response concise and actionable. +1. Bug Report - needs a Description field with real content. +2. Feature Request - title should start with `[FEATURE]:` and include verification checkbox + meaningful description. +3. Question - needs a Question field with real content. + +Also check: + +- no AI-generated walls of text +- required sections are not placeholder-only / unchanged template text +- bug reports include some repro context +- feature requests explain the problem/need +- encourage system information where relevant + +Do not be nitpicky about optional fields. Only flag real issues (missing template/required content, placeholder-only content, obviously AI-generated wall of text, empty/nonsensical issue). + +## Duplicate checks + +Search for duplicates by trying multiple keyword combinations from the issue title/body. Prioritize: + +- similar title/description +- same error/symptoms +- same component/feature area + +If the issue mentions keybinds, keyboard shortcuts, or key bindings, include a note to check pinned issue #4997. + +## Output rules + +If the issue is compliant AND no duplicates are found AND no keybind note is needed, output exactly: + +No action required + +Otherwise output exactly one markdown comment body with this structure: + +- If non-compliant, start with: + + + +This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md). + +**What needs to be fixed:** + +- [specific reason] + +Please edit this issue to address the above within **2 hours**, or it will be automatically closed. + +- If duplicates were found, add: + +--- + +This issue might be a duplicate of existing issues. Please check: + +- #1234: [brief reason] + +- If keybind-related, add: + +For keybind-related issues, please also check our pinned keybinds documentation: #4997 + +- If non-compliant, end with: + +If you believe this was flagged incorrectly, please let a maintainer know. + +Keep output concise. Do not wrap output in code fences. diff --git a/script/duplicate-issue.ts b/script/duplicate-issue.ts new file mode 100644 index 0000000000..8e47f6fbb6 --- /dev/null +++ b/script/duplicate-issue.ts @@ -0,0 +1,79 @@ +#!/usr/bin/env bun + +import path from "path" +import { pathToFileURL } from "bun" +import { createOpencode } from "@opencode-ai/sdk" +import { parseArgs } from "util" + +async function main() { + const { values, positionals } = parseArgs({ + args: Bun.argv.slice(2), + options: { + file: { type: "string", short: "f" }, + help: { type: "boolean", short: "h", default: false }, + }, + allowPositionals: true, + }) + + if (values.help) { + console.log(` +Usage: bun script/duplicate-issue.ts [options] + +Options: + -f, --file File to attach to the prompt + -h, --help Show this help message + +Examples: + bun script/duplicate-issue.ts -f issue_info.txt "Check the attached file for issue details" +`) + process.exit(0) + } + + const message = positionals.join(" ") + if (!message) { + console.error("Error: message is required") + process.exit(1) + } + + const opencode = await createOpencode({ port: 0 }) + + try { + const parts: Array<{ type: "text"; text: string } | { type: "file"; url: string; filename: string; mime: string }> = + [] + + if (values.file) { + const resolved = path.resolve(process.cwd(), values.file) + const file = Bun.file(resolved) + if (!(await file.exists())) { + console.error(`Error: file not found: ${values.file}`) + process.exit(1) + } + parts.push({ + type: "file", + url: pathToFileURL(resolved).href, + filename: path.basename(resolved), + mime: "text/plain", + }) + } + + parts.push({ type: "text", text: message }) + + const session = await opencode.client.session.create() + const result = await opencode.client.session + .prompt({ + path: { id: session.data!.id }, + body: { + agent: "duplicate-issue", + parts, + }, + signal: AbortSignal.timeout(120_000), + }) + .then((x) => x.data?.parts?.find((y) => y.type === "text")?.text ?? "") + + console.log(result.trim()) + } finally { + opencode.server.close() + } +} + +main()