diff --git a/.github/workflows/issue-manage.yml b/.github/workflows/issue-manage.yml index 653d120..0467fd6 100644 --- a/.github/workflows/issue-manage.yml +++ b/.github/workflows/issue-manage.yml @@ -6,8 +6,6 @@ permissions: on: issues: types: [opened, labeled] - issue_comment: - types: [created] jobs: welcome-new-issues: @@ -28,30 +26,6 @@ jobs: - 💬 加入[QQ交流群](https://qm.qq.com/q/TpeP9Uv2yk) - 🔍 搜索[现有issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues) 看是否有类似问题 - handle-help-wanted: - if: contains(github.event.issue.labels.*.name, 'question') - runs-on: ubuntu-latest - steps: - - name: Add help resources - uses: peter-evans/create-or-update-comment@v5 - with: - issue-number: ${{ github.event.issue.number }} - body: | - 🆘 需要帮助? - - 这里有一些可能对您有用的资源: - - 📚 **文档资源** - - [项目文档](https://neteasecloudmusicapienhanced.js.org) - - 🔍 **常见问题** - - 搜索[已关闭的issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues?q=is%3Aissue+is%3Aclosed) 看看是否有类似的问题已经被解答。 - - 💬 **即时帮助** - - 加入QQ群:https://qm.qq.com/q/TpeP9Uv2yk - - 如果以上资源无法解决您的问题,请提供更多详细信息,我们会尽快为您解答! - stale-issues: if: github.event.action == 'opened' runs-on: ubuntu-latest diff --git a/.github/workflows/opencode-auto.yml b/.github/workflows/opencode-auto.yml new file mode 100644 index 0000000..eadcf95 --- /dev/null +++ b/.github/workflows/opencode-auto.yml @@ -0,0 +1,59 @@ +name: OpenCode Automation + +on: + issues: + types: [opened] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: write + issues: write + pull-requests: write + +concurrency: + group: auto-${{ github.event.issue.number || github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + automate: + # 排除机器人自己的触发,防止自我循环 + if: | + github.actor != 'takanashi-hoshino-agent[bot]' && + github.actor != 'github-actions[bot]' && + github.actor != 'opencode-agent[bot]' + runs-on: ubuntu-latest + steps: + - name: Generate bot token + id: token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ steps.token.outputs.token }} + fetch-depth: 0 + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Install opencode + run: npm i -g opencode-ai@latest + + - name: Run automation + env: + GITHUB_TOKEN: ${{ steps.token.outputs.token }} + MODEL: opencode/deepseek-v4-flash-free + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + ISSUE_PROMPT: ${{ secrets.OPENCODE_PROMPT }} + PR_PROMPT: | + ${{ secrets.OPENCODE_PROMPT }} + ${{ secrets.PR_REVIEW_PROMPT }} + run: node scripts/opencode-automation.mjs diff --git a/.github/workflows/opencode.yml b/.github/workflows/opencode.yml index c682552..9972ac7 100644 --- a/.github/workflows/opencode.yml +++ b/.github/workflows/opencode.yml @@ -5,8 +5,6 @@ on: types: [created] pull_request_review_comment: types: [created] - issues: - types: [opened] permissions: contents: write @@ -16,10 +14,13 @@ permissions: jobs: bot: if: | - contains(github.event.comment.body, ' /oc') || - startsWith(github.event.comment.body, '/oc') || - contains(github.event.comment.body, ' /opencode') || - startsWith(github.event.comment.body, '/opencode') + (contains(github.event.comment.body, ' /oc') || + startsWith(github.event.comment.body, '/oc') || + contains(github.event.comment.body, ' /opencode') || + startsWith(github.event.comment.body, '/opencode')) && + github.actor != 'opencode-agent[bot]' && + github.actor != 'takanashi-hoshino-agent[bot]' && + github.actor != 'github-actions[bot]' runs-on: ubuntu-latest permissions: contents: read @@ -51,4 +52,5 @@ jobs: with: model: opencode/deepseek-v4-flash-free use_github_token: true - prompt: ${{ secrets.OPENCODE_PROMPT }} \ No newline at end of file + prompt: | + ${{ secrets.OPENCODE_PROMPT }} \ No newline at end of file diff --git a/package.json b/package.json index 3657dd3..6ee638a 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,8 @@ "data" ], "dependencies": { - "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.4", - "axios": "^1.18.1", + "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.4.0", + "axios": "^1.19.0", "crypto-js": "^4.2.0", "dotenv": "^17.4.2", "express": "^5.2.1", @@ -89,7 +89,7 @@ "safe-decode-uri-component": "^1.2.1", "tunnel": "^0.0.6", "xml2js": "^0.6.2", - "yargs": "^18.0.0" + "yargs": "^18.1.0" }, "devDependencies": { "@eslint/eslintrc": "^3.3.6", @@ -98,17 +98,17 @@ "@types/express-fileupload": "^1.5.1", "@types/mocha": "^10.0.10", "@types/node": "25.9.2", - "@typescript-eslint/eslint-plugin": "^8.65.0", - "@typescript-eslint/parser": "^8.65.0", + "@typescript-eslint/eslint-plugin": "^8.66.0", + "@typescript-eslint/parser": "^8.66.0", "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", "eslint-plugin-html": "^8.1.4", "eslint-plugin-prettier": "^5.5.6", - "globals": "^17.7.0", + "globals": "^17.9.0", "husky": "^9.1.7", "intelli-espower-loader": "^1.1.0", "lint-staged": "^16.4.0", - "mocha": "^11.7.6", + "mocha": "^11.8.0", "nodemon": "^3.1.14", "pkg": "^5.8.1", "power-assert": "^1.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f6b142..2a49b25 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@neteasecloudmusicapienhanced/unblockmusic-utils': - specifier: ^0.3.4 - version: 0.3.4 + specifier: ^0.4.0 + version: 0.4.0 axios: - specifier: ^1.18.1 - version: 1.18.1 + specifier: ^1.19.0 + version: 1.19.0 crypto-js: specifier: ^4.2.0 version: 4.2.0 @@ -51,8 +51,8 @@ importers: specifier: ^0.6.2 version: 0.6.2 yargs: - specifier: ^18.0.0 - version: 18.0.0 + specifier: ^18.1.0 + version: 18.1.0 devDependencies: '@eslint/eslintrc': specifier: ^3.3.6 @@ -73,11 +73,11 @@ importers: specifier: 25.9.2 version: 25.9.2 '@typescript-eslint/eslint-plugin': - specifier: ^8.65.0 - version: 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) + specifier: ^8.66.0 + version: 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.65.0 - version: 8.65.0(eslint@9.39.5)(typescript@5.9.3) + specifier: ^8.66.0 + version: 8.66.0(eslint@9.39.5)(typescript@5.9.3) eslint: specifier: ^9.39.5 version: 9.39.5 @@ -91,8 +91,8 @@ importers: specifier: ^5.5.6 version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6) globals: - specifier: ^17.7.0 - version: 17.7.0 + specifier: ^17.9.0 + version: 17.9.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -103,8 +103,8 @@ importers: specifier: ^16.4.0 version: 16.4.0 mocha: - specifier: ^11.7.6 - version: 11.7.6 + specifier: ^11.8.0 + version: 11.8.0 nodemon: specifier: ^3.1.14 version: 3.1.14 @@ -222,8 +222,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.4': - resolution: {integrity: sha512-3Lu/eYnDqf9/rHxa5G38TZxM7QVUBZpzKMWBmT6STHaMb6ztFRfrVrejIC1r7IUvtCWkoRulD+8UlllNTEsd6g==} + '@neteasecloudmusicapienhanced/unblockmusic-utils@0.4.0': + resolution: {integrity: sha512-KUDdBEe3IK4tPSt2UHdFweYbhoZUYGBxbt8FuqQu5bZdpymfpecZxLf+a8G/JvIYHph4H+KxYlq6+MD9oXCfjg==} hasBin: true '@nodelib/fs.scandir@2.1.5': @@ -271,8 +271,8 @@ packages: '@types/express-fileupload@1.5.1': resolution: {integrity: sha512-DllImBVI1lCyjl2klky/TEwk60mbNebgXv1669h66g9TfptWSrEFq5a/raHSutaFzjSm1tmn9ypdNfu4jPSixQ==} - '@types/express-serve-static-core@5.1.2': - resolution: {integrity: sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==} + '@types/express-serve-static-core@5.1.3': + resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} '@types/express@5.0.6': resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} @@ -301,63 +301,63 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@typescript-eslint/eslint-plugin@8.65.0': - resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + '@typescript-eslint/eslint-plugin@8.66.0': + resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.65.0 + '@typescript-eslint/parser': ^8.66.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.65.0': - resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + '@typescript-eslint/parser@8.66.0': + resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.65.0': - resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + '@typescript-eslint/project-service@8.66.0': + resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.65.0': - resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + '@typescript-eslint/scope-manager@8.66.0': + resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.65.0': - resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + '@typescript-eslint/tsconfig-utils@8.66.0': + resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.65.0': - resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + '@typescript-eslint/type-utils@8.66.0': + resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.65.0': - resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + '@typescript-eslint/types@8.66.0': + resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.65.0': - resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + '@typescript-eslint/typescript-estree@8.66.0': + resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.65.0': - resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + '@typescript-eslint/utils@8.66.0': + resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.65.0': - resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + '@typescript-eslint/visitor-keys@8.66.0': + resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unblockneteasemusic/server@0.28.0': @@ -386,8 +386,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -485,8 +485,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.18.1: - resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + axios@1.19.0: + resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -517,14 +517,14 @@ packages: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} - brace-expansion@1.1.16: - resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} - brace-expansion@2.1.2: - resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} - brace-expansion@5.0.8: - resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} braces@3.0.3: @@ -1153,8 +1153,8 @@ packages: flatstr@1.0.12: resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==} - flatted@3.4.3: - resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} follow-redirects@1.16.0: resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} @@ -1262,8 +1262,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@17.7.0: - resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + globals@17.9.0: + resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} engines: {node: '>=18'} globalthis@1.0.4: @@ -1400,8 +1400,8 @@ packages: resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==} engines: {node: '>=10'} - ip-address@10.2.0: - resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + ip-address@10.4.0: + resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -1565,8 +1565,8 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true jsesc@2.5.2: @@ -1699,8 +1699,8 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimatch@3.1.5: @@ -1720,8 +1720,8 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mocha@11.7.6: - resolution: {integrity: sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==} + mocha@11.8.0: + resolution: {integrity: sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true @@ -2158,8 +2158,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} engines: {node: '>=11.0.0'} secure-json-parse@2.7.0: @@ -2400,8 +2400,8 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -2655,8 +2655,8 @@ packages: resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} engines: {node: '>=12'} - yargs@18.0.0: - resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + yargs@18.1.0: + resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} yocto-queue@0.1.0: @@ -2718,7 +2718,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -2772,10 +2772,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.4': + '@neteasecloudmusicapienhanced/unblockmusic-utils@0.4.0': dependencies: '@unblockneteasemusic/server': 0.28.0 - axios: 1.18.1 + axios: 1.19.0 dotenv: 17.4.2 express: 4.22.2 https: 1.0.0 @@ -2831,7 +2831,7 @@ snapshots: '@types/busboy': 1.5.4 '@types/express': 5.0.6 - '@types/express-serve-static-core@5.1.2': + '@types/express-serve-static-core@5.1.3': dependencies: '@types/node': 25.9.2 '@types/qs': 6.15.1 @@ -2841,7 +2841,7 @@ snapshots: '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.2 + '@types/express-serve-static-core': 5.1.3 '@types/serve-static': 2.2.0 '@types/http-errors@2.0.5': {} @@ -2867,14 +2867,14 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 25.9.2 - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/parser': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/type-utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.66.0 eslint: 9.39.5 ignore: 7.0.6 natural-compare: 1.4.0 @@ -2883,41 +2883,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.66.0 debug: 4.4.3 eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.65.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.66.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3) + '@typescript-eslint/types': 8.66.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.65.0': + '@typescript-eslint/scope-manager@8.66.0': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 - '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.66.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.66.0(eslint@9.39.5)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.5 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -2925,16 +2925,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.65.0': {} + '@typescript-eslint/types@8.66.0': {} - '@typescript-eslint/typescript-estree@8.65.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.66.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.65.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/project-service': 8.66.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3) + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/visitor-keys': 8.66.0 debug: 4.4.3 - minimatch: 10.2.5 + minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -2942,20 +2942,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@9.39.5)(typescript@5.9.3)': + '@typescript-eslint/utils@8.66.0(eslint@9.39.5)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.66.0 + '@typescript-eslint/types': 8.66.0 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3) eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.65.0': + '@typescript-eslint/visitor-keys@8.66.0': dependencies: - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/types': 8.66.0 eslint-visitor-keys: 5.0.1 '@unblockneteasemusic/server@0.28.0': @@ -2976,13 +2976,13 @@ snapshots: acorn-es7-plugin@1.1.7: {} - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 acorn@5.7.4: {} - acorn@8.17.0: {} + acorn@8.18.0: {} agent-base@6.0.2: dependencies: @@ -3072,7 +3072,7 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axios@1.18.1: + axios@1.19.0: dependencies: follow-redirects: 1.16.0 form-data: 4.0.6 @@ -3129,16 +3129,16 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.16: + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.1.2: + brace-expansion@2.1.4: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.8: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -3765,8 +3765,8 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 4.2.1 esprima@2.7.3: {} @@ -3959,14 +3959,14 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.3 + flatted: 3.4.4 keyv: 4.5.4 flat@5.0.2: {} flatstr@1.0.12: {} - flatted@3.4.3: {} + flatted@3.4.4: {} follow-redirects@1.16.0: {} @@ -4085,7 +4085,7 @@ snapshots: globals@14.0.0: {} - globals@17.7.0: {} + globals@17.9.0: {} globalthis@1.0.4: dependencies: @@ -4219,7 +4219,7 @@ snapshots: from2: 2.3.0 p-is-promise: 3.0.0 - ip-address@10.2.0: {} + ip-address@10.4.0: {} ipaddr.js@1.9.1: {} @@ -4378,7 +4378,7 @@ snapshots: joycon@3.1.1: {} - js-yaml@4.3.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 @@ -4418,7 +4418,7 @@ snapshots: listr2: 9.0.5 picomatch: 4.0.5 string-argv: 0.3.2 - tinyexec: 1.2.4 + tinyexec: 1.3.0 yaml: 2.9.0 listr2@9.0.5: @@ -4498,17 +4498,17 @@ snapshots: mimic-response@3.1.0: {} - minimatch@10.2.5: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.8 + brace-expansion: 5.0.9 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.16 + brace-expansion: 1.1.18 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.1.4 minimist@1.2.8: {} @@ -4516,7 +4516,7 @@ snapshots: mkdirp-classic@0.5.3: {} - mocha@11.7.6: + mocha@11.8.0: dependencies: browser-stdout: 1.3.1 chokidar: 4.0.3 @@ -4527,7 +4527,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 4.3.0 + js-yaml: 4.3.1 log-symbols: 4.1.0 minimatch: 9.0.9 ms: 2.1.3 @@ -4602,7 +4602,7 @@ snapshots: chokidar: 3.6.0 debug: 4.4.3(supports-color@5.5.0) ignore-by-default: 1.0.1 - minimatch: 10.2.5 + minimatch: 10.2.6 pstree.remy: 1.1.8 semver: 7.8.5 simple-update-notifier: 2.0.0 @@ -5073,7 +5073,7 @@ snapshots: safer-buffer@2.1.2: {} - sax@1.6.0: {} + sax@1.6.1: {} secure-json-parse@2.7.0: {} @@ -5233,7 +5233,7 @@ snapshots: socks@2.8.9: dependencies: - ip-address: 10.2.0 + ip-address: 10.4.0 smart-buffer: 4.2.0 sonic-boom@1.4.1: @@ -5387,7 +5387,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tinyexec@1.2.4: {} + tinyexec@1.3.0: {} tinyglobby@0.2.17: dependencies: @@ -5620,7 +5620,7 @@ snapshots: xml2js@0.6.2: dependencies: - sax: 1.6.0 + sax: 1.6.1 xmlbuilder: 11.0.1 xml@1.0.1: {} @@ -5687,12 +5687,12 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yargs@18.0.0: + yargs@18.1.0: dependencies: cliui: 9.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - string-width: 7.2.0 + string-width: 8.2.2 y18n: 5.0.8 yargs-parser: 22.0.0 diff --git a/scripts/opencode-automation.mjs b/scripts/opencode-automation.mjs new file mode 100644 index 0000000..654dc25 --- /dev/null +++ b/scripts/opencode-automation.mjs @@ -0,0 +1,204 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process' +import { readFileSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +const eventName = process.env.GITHUB_EVENT_NAME +const eventPath = process.env.GITHUB_EVENT_PATH +const token = process.env.GITHUB_TOKEN +const repo = process.env.GITHUB_REPOSITORY +const model = process.env.MODEL || 'opencode/deepseek-v4-flash-free' +const botLogin = process.env.BOT_LOGIN || 'takanashi-hoshino-agent[bot]' + +if (!eventPath || !token || !repo) { + console.error( + 'Missing required env: GITHUB_EVENT_PATH / GITHUB_TOKEN / GITHUB_REPOSITORY', + ) + process.exit(1) +} +if (!process.env.OPENCODE_API_KEY) { + console.error('Missing OPENCODE_API_KEY') + process.exit(1) +} + +const event = JSON.parse(readFileSync(eventPath, 'utf8')) +const [owner, repoName] = repo.split('/') +const api = `https://api.github.com/repos/${owner}/${repoName}` +const headers = { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'opencode-automation', +} + +async function get(path) { + const res = await fetch(`${api}${path}`, { headers }) + if (!res.ok) throw new Error(`GET ${path}: HTTP ${res.status}`) + return res.json() +} + +async function post(path, body) { + const res = await fetch(`${api}${path}`, { + method: 'POST', + headers, + body: JSON.stringify(body), + }) + const text = await res.text() + if (!res.ok) throw new Error(`POST ${path}: HTTP ${res.status} ${text}`) + return text ? JSON.parse(text) : null +} + +/** 无头模式跑 opencode,返回模型输出的文本 */ +function runOpencode(prompt, contextFile) { + const args = ['run', '--auto', '-m', model, '--format', 'default', prompt] + if (contextFile) args.push('-f', contextFile) + console.log( + `Running: opencode run --auto -m ${model} (prompt ${prompt.length} chars${contextFile ? ', context attached' : ''})`, + ) + const res = spawnSync('opencode', args, { + encoding: 'utf8', + maxBuffer: 128 * 1024 * 1024, + }) + if (res.error) throw res.error + if (res.status !== 0) { + if (res.stderr) console.error('opencode stderr:\n' + res.stderr) + throw new Error(`opencode exited with code ${res.status}`) + } + return (res.stdout || '').trim() +} + +/** 该 issue/PR 是否已有 bot 评论(用于去重,避免 synchronize 重复评论) */ +async function hasBotComment(target) { + const comments = await get(`/issues/${target}/comments?per_page=100`) + return comments.some((c) => c.user?.login === botLogin) +} + +async function comment(target, body) { + if (!body) { + console.log('No output from agent, skip comment') + return + } + if (body.length > 60000) body = body.slice(0, 60000) + '\n\n_...(truncated)_' + await post(`/issues/${target}/comments`, { body }) + console.log(`Commented on #${target}`) +} + +async function handleIssue(issue) { + const n = issue.number + console.log(`Handling issue #${n}`) + if (await hasBotComment(n)) { + console.log(`Already handled by bot, skip`) + return + } + + const comments = await get(`/issues/${n}/comments?per_page=100`) + const thread = comments + .filter((c) => c.user?.login !== botLogin) + .map( + (c) => + `- **${c.user?.login}** (${c.created_at}):\n ${(c.body || '').replace(/\n/g, '\n ')}`, + ) + .join('\n') + + const ctx = [ + `# Issue #${n}: ${issue.title}`, + ``, + `- State: ${issue.state}`, + `- Author: ${issue.user?.login}`, + `- Created: ${issue.created_at}`, + ``, + `## Body`, + ``, + issue.body || '(empty)', + ...(thread ? [`\n## Comments\n\n${thread}`] : []), + ].join('\n') + + const ctxFile = join(tmpdir(), 'opencode-issue-context.md') + writeFileSync(ctxFile, ctx) + + const prompt = + process.env.ISSUE_PROMPT || + [ + `You are a maintainer bot for the ${repo} repository.`, + `Read the attached file for the issue context.`, + `If the issue has a clear fix or points to relevant docs, reply with documentation links and/or`, + `error-handling guidance for code examples. If nothing actionable, reply with exactly "NO_ACTION".`, + `Do NOT modify any files, do NOT commit, do NOT push. Output only the comment text.`, + ].join('\n') + + const output = runOpencode(prompt, ctxFile) + if (output === 'NO_ACTION' || output.startsWith('NO_ACTION')) { + console.log('Agent decided no action needed') + return + } + await comment(n, output) +} + +async function handlePullRequest(pr) { + const n = pr.number + console.log(`Handling PR #${n}`) + if (await hasBotComment(n)) { + console.log(`Already handled by bot, skip`) + return + } + + let files = [] + try { + files = await get(`/pulls/${n}/files?per_page=100`) + } catch (e) { + console.warn(`Failed to fetch changed files: ${e.message}`) + } + const fileList = files + .map((f) => `- ${f.status} ${f.filename} (+${f.additions}/-${f.deletions})`) + .join('\n') + + const ctx = [ + `# PR #${n}: ${pr.title}`, + ``, + `- State: ${pr.state}`, + `- Author: ${pr.user?.login}`, + `- Base: ${pr.base?.ref} <- Head: ${pr.head?.ref}`, + `- Created: ${pr.created_at}`, + `- Stats: +${pr.additions}/-${pr.deletions}, ${pr.changed_files} files`, + ``, + `## Body`, + ``, + pr.body || '(empty)', + ...(fileList ? [`\n## Changed files\n\n${fileList}`] : []), + ].join('\n') + + const ctxFile = join(tmpdir(), 'opencode-pr-context.md') + writeFileSync(ctxFile, ctx) + + const prompt = + process.env.PR_PROMPT || + [ + `You are a maintainer bot for the ${repo} repository.`, + `Read the attached file for the PR context. The PR branch is already checked out.`, + `Run \`git diff HEAD^1 HEAD\` (or \`git diff origin/${pr.base?.ref}...HEAD\`) to see the changes.`, + `Review the code changes: point out bugs, risks and improvements, and suggest fixes for obvious issues.`, + `If the PR looks good, say so concisely.`, + `Do NOT modify any files, do NOT commit, do NOT push. Output only the review comment text.`, + ].join('\n') + + const output = runOpencode(prompt, ctxFile) + await comment(n, output) +} + +async function main() { + console.log(`Event: ${eventName} on ${repo}`) + if (eventName === 'issues') { + await handleIssue(event.issue) + } else if (eventName === 'pull_request') { + await handlePullRequest(event.pull_request) + } else { + console.log(`Unhandled event type: ${eventName}`) + } +} + +main().catch((e) => { + console.error(`Automation failed: ${e.message}`) + process.exit(1) +})