feat: implement git repository synchronization and automated commit functionality for AI review findings

This commit is contained in:
Jeffery
2026-05-21 10:17:01 +08:00
parent 43ebc81f1d
commit e99236b893
6 changed files with 17 additions and 3 deletions
+2
View File
@@ -12,9 +12,11 @@ RUN cd /action/app && npm install
COPY .amazonq/ /action/.amazonq/ COPY .amazonq/ /action/.amazonq/
COPY .codex/ /action/.codex/ COPY .codex/ /action/.codex/
COPY .agents/ /action/.agents/
COPY .claude/ /action/.claude/ COPY .claude/ /action/.claude/
COPY .gemini/ /action/.gemini/ COPY .gemini/ /action/.gemini/
COPY .github/ /action/.github/ COPY .github/ /action/.github/
COPY AGENTS.md /action/
COPY CLAUDE.md /action/ COPY CLAUDE.md /action/
COPY GEMINI.md /action/ COPY GEMINI.md /action/
+1 -1
View File
@@ -22,7 +22,7 @@
4. 盡量將應用程式放在 ./app,修改 entrypoint.sh 與 Dockerfile 讓程式可以正常運行 4. 盡量將應用程式放在 ./app,修改 entrypoint.sh 與 Dockerfile 讓程式可以正常運行
5. 將提示詞放到 ./app/prompts 內供程式讀取 5. 將提示詞放到 ./app/prompts 內供程式讀取
6. API Key 支援逗號分隔傳入多個,隨機順序各嘗試一次,全部失敗則 exit 1 6. API Key 支援逗號分隔傳入多個,隨機順序各嘗試一次,全部失敗則 exit 1
7. 讀取 Git Diff 時排除 `.gitea/``.amazonq/``.antigravity/``.claude/``.codex/``.gemini/``.github/` 資料夾,以及 `ANTIGRAVITY.md``CLAUDE.md``GEMINI.md``TODO.md``README.md`,避免 AI 分析 workflow 設定、skill 入口與文件等非業務程式碼 7. 讀取 Git Diff 時排除 `.gitea/``.amazonq/``.agents/``.antigravity/``.claude/``.codex/``.gemini/``.github/` 資料夾,以及 `AGENTS.md``ANTIGRAVITY.md``CLAUDE.md``GEMINI.md``TODO.md``README.md`,避免 AI 分析 workflow 設定、skill 入口與文件等非業務程式碼
8. 階段七驗證來源分支中的 `findings.json``exclusions.json` 是否為合法 JSON 格式,格式錯誤時先嘗試透過 AI 修正內容,再重新驗證;修正後仍不合法才 exit 1;之後才檢查檔案是否存在,不存在則建立並寫入 `[]` 8. 階段七驗證來源分支中的 `findings.json``exclusions.json` 是否為合法 JSON 格式,格式錯誤時先嘗試透過 AI 修正內容,再重新驗證;修正後仍不合法才 exit 1;之後才檢查檔案是否存在,不存在則建立並寫入 `[]`
9. 傳給 AI 的 findings 只保留必要欄位(level、role、location、suggestion),排除 `is_new` 等內部欄位;system prompt 精簡為指令核心;exclusions hint 只傳 location 與 suggestion,減少 token 用量 9. 傳給 AI 的 findings 只保留必要欄位(level、role、location、suggestion),排除 `is_new` 等內部欄位;system prompt 精簡為指令核心;exclusions hint 只傳 location 與 suggestion,減少 token 用量
10. 執行時會額外記錄來源分支狀態、`findings.json` / `exclusions.json` 的檔案路徑、大小、mtime 與 raw/normalized 筆數,方便追查讀檔與分支內容不一致的問題 10. 執行時會額外記錄來源分支狀態、`findings.json` / `exclusions.json` 的檔案路徑、大小、mtime 與 raw/normalized 筆數,方便追查讀檔與分支內容不一致的問題
+4
View File
@@ -11,6 +11,7 @@ const remoteUrl = `${GITEA_SERVER_URL.replace(/\/$/, '')}/${GITEA_REPOSITORY}.gi
export const BOT_COMMIT_MARKER = '[ai-review-bot]'; export const BOT_COMMIT_MARKER = '[ai-review-bot]';
export const SYNC_PATHS = [ export const SYNC_PATHS = [
'.amazonq/rules/triage-findings.md', '.amazonq/rules/triage-findings.md',
'.agents/skills/triage-findings/SKILL.md',
'.antigravity/skills/triage-findings/SKILL.md', '.antigravity/skills/triage-findings/SKILL.md',
'.codex/skills/triage-findings/SKILL.md', '.codex/skills/triage-findings/SKILL.md',
'.codex/skills/triage-findings/agents/openai.yaml', '.codex/skills/triage-findings/agents/openai.yaml',
@@ -18,17 +19,20 @@ export const SYNC_PATHS = [
'.gemini/skills/triage-findings/SKILL.md', '.gemini/skills/triage-findings/SKILL.md',
'.github/copilot-instructions.md', '.github/copilot-instructions.md',
'.github/skills/triage-findings/SKILL.md', '.github/skills/triage-findings/SKILL.md',
'AGENTS.md',
'ANTIGRAVITY.md', 'ANTIGRAVITY.md',
'CLAUDE.md', 'CLAUDE.md',
'GEMINI.md', 'GEMINI.md',
]; ];
const FORCE_SYNC_FILE_PATHS = [ const FORCE_SYNC_FILE_PATHS = [
'.github/copilot-instructions.md', '.github/copilot-instructions.md',
'AGENTS.md',
'ANTIGRAVITY.md', 'ANTIGRAVITY.md',
'CLAUDE.md', 'CLAUDE.md',
'GEMINI.md', 'GEMINI.md',
]; ];
const SYNC_TREE_PATHS = [ const SYNC_TREE_PATHS = [
'.agents/skills/triage-findings',
'.antigravity/skills/triage-findings', '.antigravity/skills/triage-findings',
'.codex/skills/triage-findings', '.codex/skills/triage-findings',
'.claude/skills/triage-findings', '.claude/skills/triage-findings',
+6
View File
@@ -130,11 +130,13 @@ describe('commitAndPush', () => {
assert.ok(generatedAddCall, 'expected git add for generated review files'); assert.ok(generatedAddCall, 'expected git add for generated review files');
assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/SKILL.md'));
assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/agents/openai.yaml')); assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/agents/openai.yaml'));
assert.ok(skillAddCall.args.includes('.agents/skills/triage-findings/SKILL.md'));
assert.ok(skillAddCall.args.includes('.claude/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall.args.includes('.claude/skills/triage-findings/SKILL.md'));
assert.ok(skillAddCall.args.includes('.gemini/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall.args.includes('.gemini/skills/triage-findings/SKILL.md'));
assert.ok(skillAddCall.args.includes('.antigravity/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall.args.includes('.antigravity/skills/triage-findings/SKILL.md'));
assert.ok(skillAddCall.args.includes('.github/copilot-instructions.md')); assert.ok(skillAddCall.args.includes('.github/copilot-instructions.md'));
assert.ok(skillAddCall.args.includes('.amazonq/rules/triage-findings.md')); assert.ok(skillAddCall.args.includes('.amazonq/rules/triage-findings.md'));
assert.ok(skillAddCall.args.includes('AGENTS.md'));
assert.ok(skillAddCall.args.includes('ANTIGRAVITY.md')); assert.ok(skillAddCall.args.includes('ANTIGRAVITY.md'));
assert.ok(skillAddCall.args.includes('CLAUDE.md')); assert.ok(skillAddCall.args.includes('CLAUDE.md'));
assert.ok(skillAddCall.args.includes('GEMINI.md')); assert.ok(skillAddCall.args.includes('GEMINI.md'));
@@ -159,7 +161,9 @@ describe('commitAndPush', () => {
it('overwrites existing repo copies with workspace files', async () => { it('overwrites existing repo copies with workspace files', async () => {
const repoDir = path.join(workspace, 'repo'); const repoDir = path.join(workspace, 'repo');
fs.writeFileSync(path.join(repoDir, '.agents/skills/triage-findings/SKILL.md'), 'stale');
fs.writeFileSync(path.join(repoDir, '.github/skills/triage-findings/SKILL.md'), 'stale'); fs.writeFileSync(path.join(repoDir, '.github/skills/triage-findings/SKILL.md'), 'stale');
fs.writeFileSync(path.join(repoDir, 'AGENTS.md'), 'stale');
fs.writeFileSync(path.join(repoDir, 'ANTIGRAVITY.md'), 'stale'); fs.writeFileSync(path.join(repoDir, 'ANTIGRAVITY.md'), 'stale');
fs.writeFileSync(path.join(repoDir, 'CLAUDE.md'), 'stale'); fs.writeFileSync(path.join(repoDir, 'CLAUDE.md'), 'stale');
fs.writeFileSync(path.join(repoDir, 'GEMINI.md'), 'stale'); fs.writeFileSync(path.join(repoDir, 'GEMINI.md'), 'stale');
@@ -168,6 +172,8 @@ describe('commitAndPush', () => {
await commitAndPush(workspace, repoDir, makeSpawn(), sourceRoot); await commitAndPush(workspace, repoDir, makeSpawn(), sourceRoot);
assert.equal(fs.readFileSync(path.join(repoDir, '.github/skills/triage-findings/SKILL.md'), 'utf8'), '.github/skills/triage-findings/SKILL.md'); assert.equal(fs.readFileSync(path.join(repoDir, '.github/skills/triage-findings/SKILL.md'), 'utf8'), '.github/skills/triage-findings/SKILL.md');
assert.equal(fs.readFileSync(path.join(repoDir, '.agents/skills/triage-findings/SKILL.md'), 'utf8'), '.agents/skills/triage-findings/SKILL.md');
assert.equal(fs.readFileSync(path.join(repoDir, 'AGENTS.md'), 'utf8'), 'AGENTS.md');
assert.equal(fs.readFileSync(path.join(repoDir, 'ANTIGRAVITY.md'), 'utf8'), 'ANTIGRAVITY.md'); assert.equal(fs.readFileSync(path.join(repoDir, 'ANTIGRAVITY.md'), 'utf8'), 'ANTIGRAVITY.md');
assert.equal(fs.readFileSync(path.join(repoDir, 'CLAUDE.md'), 'utf8'), 'CLAUDE.md'); assert.equal(fs.readFileSync(path.join(repoDir, 'CLAUDE.md'), 'utf8'), 'CLAUDE.md');
assert.equal(fs.readFileSync(path.join(repoDir, 'GEMINI.md'), 'utf8'), 'GEMINI.md'); assert.equal(fs.readFileSync(path.join(repoDir, 'GEMINI.md'), 'utf8'), 'GEMINI.md');
+2
View File
@@ -26,12 +26,14 @@ export async function getPRDiff() {
const resp = await axios.get(api(`/repos/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}.diff`), { headers: headers(), timeout: 60000, httpsAgent }); const resp = await axios.get(api(`/repos/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}.diff`), { headers: headers(), timeout: 60000, httpsAgent });
return filterDiff(resp.data, [ return filterDiff(resp.data, [
'.amazonq/', '.amazonq/',
'.agents/',
'.antigravity/', '.antigravity/',
'.claude/', '.claude/',
'.codex/', '.codex/',
'.gemini/', '.gemini/',
'.gitea/', '.gitea/',
'.github/', '.github/',
'AGENTS.md',
'ANTIGRAVITY.md', 'ANTIGRAVITY.md',
'CLAUDE.md', 'CLAUDE.md',
'GEMINI.md', 'GEMINI.md',
+2 -2
View File
@@ -119,8 +119,8 @@ describe('filterDiff', () => {
}); });
it('returns empty string when all blocks are excluded', () => { it('returns empty string when all blocks are excluded', () => {
const diff = block('.gitea/workflows/review.yaml') + block('.gitea/ai-review/findings.json') + block('CLAUDE.md'); const diff = block('.gitea/workflows/review.yaml') + block('.gitea/ai-review/findings.json') + block('.agents/skills/triage-findings/SKILL.md');
const result = filterDiff(diff, ['.gitea/', 'CLAUDE.md']); const result = filterDiff(diff, ['.gitea/', '.agents/']);
assert.equal(result, ''); assert.equal(result, '');
}); });