diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 09d669b6ff..007708c4bf 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -17,70 +17,53 @@ jobs: with: fetch-depth: 1 - - 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 }} - run: | - 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 - - - name: Post comment and label issue - env: - 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 + OPENCODE_PERMISSION: | + { + "bash": "deny", + "webfetch": "deny", + "edit": "deny", + "write": "deny" } + ISSUE_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + run: | + ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title --jq .title) + ISSUE_BODY=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json body --jq .body) - 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}`, - }) + PROMPT=$(cat <")) return + CURRENT_ISSUE_NUMBER: $ISSUE_NUMBER - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - labels: ["needs:compliance"], - }) + Title: $ISSUE_TITLE + + Description: + $ISSUE_BODY + EOF + ) + + COMMENT=$(opencode run --agent duplicate-issue "$PROMPT") + + if [ "$COMMENT" = "No action required" ]; then + exit 0 + fi + + BODY="_The following comment was made by an LLM, it may be inaccurate:_ + + $COMMENT" + + gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$BODY" + + if [[ "$COMMENT" == *""* ]]; then + gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label needs:compliance + fi recheck-compliance: if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') @@ -94,111 +77,63 @@ jobs: with: fetch-depth: 1 - - uses: ./.github/actions/setup-bun - - - name: Install dependencies - run: bun install - - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash - - name: Build recheck prompt - uses: actions/github-script@v8 - with: - script: | - const fs = require("fs") - const issue = context.payload.issue - const body = issue.body ?? "" - const text = [ - "Recheck this edited issue for compliance:", - "", - "MODE: recheck-compliance", - `CURRENT_ISSUE_NUMBER: ${issue.number}`, - "", - `Title: ${issue.title}`, - "", - "Description:", - body, - ].join("\n") - - fs.writeFileSync("issue_info.txt", text) - - name: Recheck compliance env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENCODE_PERMISSION: | + { + "bash": "deny", + "webfetch": "deny", + "edit": "deny", + "write": "deny" + } + ISSUE_NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} run: | - bun script/duplicate-issue.ts -f issue_info.txt "Recheck compliance for this edited issue and return either No action required or a compliance comment body" > issue_comment.txt + ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json title --jq .title) + ISSUE_BODY=$(gh issue view "$ISSUE_NUMBER" --repo "$REPO" --json body --jq .body) - - name: Update compliance state - env: - COMMENT: ${{ github.workspace }}/issue_comment.txt - uses: actions/github-script@v8 - with: - script: | - const fs = require("fs") - const marker = "" - const comment = fs.readFileSync(process.env.COMMENT, "utf8").trim() - const issue_number = context.payload.issue.number + PROMPT=$(cat < (x.body ?? "").includes(marker)) + Title: $ISSUE_TITLE - if (comment === "No action required") { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number, - name: "needs:compliance", - }) - } catch {} + Description: + $ISSUE_BODY + EOF + ) - for (const entry of compliance) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: entry.id, - }) - } + COMMENT=$(opencode run --agent duplicate-issue "$PROMPT") - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number, - body: "Thanks for updating your issue. It now meets our contributing guidelines. :+1:", - }) - return - } + if [ "$COMMENT" = "No action required" ]; then + gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --remove-label needs:compliance || true - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number, - labels: ["needs:compliance"], - }) + IDS=$(gh api "repos/$REPO/issues/$ISSUE_NUMBER/comments" --jq '.[] | select(.body | contains("")) | .id') + for id in $IDS; do + gh api -X DELETE "repos/$REPO/issues/comments/$id" + done - const body = `_The following comment was made by an LLM, it may be inaccurate:_\n\n${comment}` - const existing = compliance.at(-1) - if (!existing) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number, - body, - }) - return - } + gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "Thanks for updating your issue. It now meets our contributing guidelines. :+1:" + exit 0 + fi - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }) + gh issue edit "$ISSUE_NUMBER" --repo "$REPO" --add-label needs:compliance + + BODY="_The following comment was made by an LLM, it may be inaccurate:_ + + $COMMENT" + + EXISTING=$(gh api "repos/$REPO/issues/$ISSUE_NUMBER/comments" --jq '[.[] | select(.body | contains("")) | .id] | last // empty') + if [ -n "$EXISTING" ]; then + gh api -X PATCH "repos/$REPO/issues/comments/$EXISTING" -f body="$BODY" + exit 0 + fi + + gh issue comment "$ISSUE_NUMBER" --repo "$REPO" --body "$BODY" diff --git a/script/duplicate-issue.ts b/script/duplicate-issue.ts deleted file mode 100644 index 8e47f6fbb6..0000000000 --- a/script/duplicate-issue.ts +++ /dev/null @@ -1,79 +0,0 @@ -#!/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()