finalize to copy the style of the PR review for enhanced security

pull/14229/head
Ryan Vogel 2026-02-18 21:23:08 -05:00
parent ba02738d34
commit 7560913b7d
3 changed files with 196 additions and 153 deletions

View File

@ -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:
<!-- issue-compliance -->
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:]
<!-- issue-compliance -->
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("<!-- issue-compliance -->")) 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 <!-- issue-compliance -->) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"<!-- issue-compliance -->\")) | .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"],
})

View File

@ -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:
<!-- issue-compliance -->
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.

View File

@ -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] <message>
Options:
-f, --file <path> 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()