diff --git a/.gitea/workflows/review.yaml b/.gitea/workflows/review.yaml index 73e5f6e..7a0fe30 100644 --- a/.gitea/workflows/review.yaml +++ b/.gitea/workflows/review.yaml @@ -5,36 +5,9 @@ on: - master types: [opened, synchronize] jobs: - detect-bot-commit: - name: 偵測自動提交 - runs-on: ubuntu - outputs: - skip: ${{ steps.detect.outputs.skip }} - steps: - - name: 檢查 head commit marker - id: detect - env: - GITEA_API_URL: ${{ github.api_url }} - GITEA_REPOSITORY: ${{ github.repository }} - GITEA_SHA: ${{ github.sha }} - GITEA_TOKEN: ${{ github.token }} - run: | - set -e - commit_json="$(curl -fsSL -H "Authorization: token ${GITEA_TOKEN}" "${GITEA_API_URL}/repos/${GITEA_REPOSITORY}/git/commits/${GITEA_SHA}")" || { - echo "skip=false" >> "$GITHUB_OUTPUT" - exit 0 - } - if printf '%s' "$commit_json" | grep -q '\[ai-review-bot\]'; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "偵測到 AI Review Bot commit,跳過 review workflow" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi version: name: 計算版本號 runs-on: ubuntu - needs: [detect-bot-commit] - if: needs.detect-bot-commit.outputs.skip != 'true' outputs: version: ${{ steps.version.outputs.version }} steps: @@ -52,8 +25,7 @@ jobs: code-review: name: Code Review runs-on: ubuntu - needs: [detect-bot-commit, version] - if: needs.detect-bot-commit.outputs.skip != 'true' + needs: [version] steps: - name: AI Code Review uses: https://gitea.jsc.idv.tw/actions/code-review@v${{ needs.version.outputs.version }} diff --git a/README.md b/README.md index 92b5dd9..1de299c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ 2. 在 `.gitea/workflows` 資料夾中建立 `ai-review.yaml' 3. 在 `ai-review.yaml` 中填入以下內容(選擇一個使用): -> **自動提交排除說明**:此 Action 會將自己的 commit message 標記為 `[ai-review-bot]`。建議在 review workflow 的最前面先檢查 head commit 是否含有這個 marker,若有就直接成功結束,避免 bot commit 造成重複觸發。 +> **自動提交排除說明**:此 Action 會將自己的 commit message 標記為 `[ai-review-bot]`,而且 action 執行時也會先檢查 head commit 是否含有這個 marker,若有就直接成功結束,避免 bot commit 造成重複觸發。若外層 workflow 也能先檢查一次,效果最好。 > **權限說明**:此 Action 需要 `contents: write`(寫入 findings.json)、`pull-requests: write`(發佈 PR comment)、`issues: write`(發佈 issue comment)三項權限,為正常運作所必要,無法縮減。 diff --git a/app/git.js b/app/git.js index a8c16e2..855cbca 100644 --- a/app/git.js +++ b/app/git.js @@ -59,6 +59,15 @@ export function getRepoState(repoDir, _spawnSync = spawnSync) { return { repoDir, branch, headSha, shortSha, commitTime }; } +export function getHeadCommitMessage(repoDir, _spawnSync = spawnSync) { + const run = makeRunner(_spawnSync); + return readGitOutput(run, ['show', '-s', '--format=%B', 'HEAD'], repoDir); +} + +export function isBotAutoCommit(repoDir, _spawnSync = spawnSync) { + return getHeadCommitMessage(repoDir, _spawnSync).includes(BOT_COMMIT_MARKER); +} + /** * Clone PR head branch to workspace/repo (idempotent) */ diff --git a/app/git.test.js b/app/git.test.js index bfb21b7..23c628b 100644 --- a/app/git.test.js +++ b/app/git.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'fs'; import os from 'os'; import path from 'path'; -import { commitAndPush, cloneRepo, SYNC_PATHS, BOT_COMMIT_MARKER } from './git.js'; +import { commitAndPush, cloneRepo, SYNC_PATHS, BOT_COMMIT_MARKER, getHeadCommitMessage, isBotAutoCommit } from './git.js'; // --- helpers --- function makeTmpWorkspace() { @@ -241,4 +241,13 @@ describe('cloneRepo', () => { const result = cloneRepo(workspace, spawn); assert.equal(result, path.join(workspace, 'repo')); }); + + it('reads head commit message and detects bot auto commits', () => { + const spawn = makeSpawn({ + show: () => ({ status: 0, stdout: `chore: update ai-review findings ${BOT_COMMIT_MARKER}\n`, stderr: '', error: null }), + }); + + assert.ok(getHeadCommitMessage(workspace, spawn).includes(BOT_COMMIT_MARKER)); + assert.equal(isBotAutoCommit(workspace, spawn), true); + }); }); diff --git a/app/main.js b/app/main.js index 7d7a568..252d456 100644 --- a/app/main.js +++ b/app/main.js @@ -4,7 +4,7 @@ import { loadRoles, getRoleIntro } from './roles.js'; import { getPRDiff, postComment } from './gitea.js'; import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions, filterFalsePositivesWithAI } from './findings.js'; import { saveFindings, postOldFindingsComment, postNewNonCriticalComment, postNewCriticalComments } from './comments.js'; -import { cloneRepo, commitAndPush, getRepoState } from './git.js'; +import { cloneRepo, commitAndPush, getRepoState, isBotAutoCommit } from './git.js'; import { validateJSONArrayFile, ensureJSONArrayFileExists } from './json.js'; const WORKSPACE = process.env.GITHUB_WORKSPACE || '/workspace'; @@ -15,6 +15,12 @@ async function main() { console.log(` repo=${GITEA_REPOSITORY} PR=#${PR_NUMBER}`); console.log(` ${PR_HEAD_BRANCH} -> ${PR_BASE_BRANCH}`); + if (isBotAutoCommit(WORKSPACE)) { + console.log(' 🤖 偵測到 [ai-review-bot] 自動提交,直接完成 action'); + console.log('='.repeat(60)); + process.exit(0); + } + const { provider, baseURL, model } = getLLMConfig(); if (!provider) { console.error('❌ 未設定任何 LLM API Key,請檢查 action inputs');