From 5c5773e4fd052d4c3a915dd976293ef7550a51f5 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 15 May 2026 06:10:09 +0000 Subject: [PATCH 1/3] fix: write findings to review dir --- app/comments.js | 18 ++++++++++++------ app/comments.test.js | 22 ++++++++++++++++++++++ app/main.js | 7 ++++--- 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 app/comments.test.js diff --git a/app/comments.js b/app/comments.js index a8f2198..286a845 100644 --- a/app/comments.js +++ b/app/comments.js @@ -16,13 +16,19 @@ function buildTable(findings) { } /** - * 寫入 findings.json 到 workspace + * 寫入 findings.json。 + * 預設寫到 workspace;若提供 mirrorDir,則同步寫入另一份供 repo commit 使用。 */ -export function saveFindings(workspace, findings) { - const fullPath = path.join(workspace, FINDINGS_PATH); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, JSON.stringify(findings, null, 2) + '\n', 'utf8'); - console.log(` ✅ findings 寫入: ${fullPath} (${findings.length} 筆)`); +export function saveFindings(workspace, findings, mirrorDir = null) { + const targets = [workspace]; + if (mirrorDir && mirrorDir !== workspace) targets.push(mirrorDir); + + for (const targetDir of targets) { + const fullPath = path.join(targetDir, FINDINGS_PATH); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, JSON.stringify(findings, null, 2) + '\n', 'utf8'); + console.log(` ✅ findings 寫入: ${fullPath} (${findings.length} 筆)`); + } } /** diff --git a/app/comments.test.js b/app/comments.test.js new file mode 100644 index 0000000..27927bd --- /dev/null +++ b/app/comments.test.js @@ -0,0 +1,22 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { saveFindings } from './comments.js'; +import { FINDINGS_PATH } from './config.js'; + +describe('saveFindings', () => { + it('writes findings to workspace and mirror dirs when provided', () => { + const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'findings-ws-')); + const mirrorDir = fs.mkdtempSync(path.join(os.tmpdir(), 'findings-mirror-')); + const findings = [{ level: 'warning', role: 'Leo', location: 'file.js:1', suggestion: 'test' }]; + + saveFindings(workspace, findings, mirrorDir); + + const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8'); + const mirrorText = fs.readFileSync(path.join(mirrorDir, FINDINGS_PATH), 'utf8'); + assert.equal(workspaceText, JSON.stringify(findings, null, 2) + '\n'); + assert.equal(mirrorText, JSON.stringify(findings, null, 2) + '\n'); + }); +}); diff --git a/app/main.js b/app/main.js index ae25281..1850642 100644 --- a/app/main.js +++ b/app/main.js @@ -88,7 +88,8 @@ async function main() { // Step6: 寫入 findings.json,依序發布 comment console.log('\n📝 Step5: Findings 寫入與 Comment 發布'); - saveFindings(WORKSPACE, filtered); + const reviewDir = repoDir || WORKSPACE; + saveFindings(WORKSPACE, filtered, reviewDir); try { await postOldFindingsComment(filtered); await postNewNonCriticalComment(filtered); @@ -102,7 +103,7 @@ async function main() { console.log('\n🔎 Step6: JSON 格式驗證'); const missingPaths = []; for (const relPath of [FINDINGS_PATH, EXCLUSIONS_PATH]) { - const fullPath = path.join(repoDir || WORKSPACE, relPath); + const fullPath = path.join(reviewDir, relPath); try { const result = await validateJSONArrayFile(fullPath, relPath); if (!result.exists) missingPaths.push({ fullPath, relPath }); @@ -117,7 +118,7 @@ async function main() { // Step7: commit/push findings.json 到來源分支 console.log('\n💾 Step7: 記憶區 Commit/Push'); - await commitAndPush(WORKSPACE, repoDir); + await commitAndPush(WORKSPACE, repoDir || WORKSPACE); // Step9: 有 critical 問題則 exit 1 console.log('\n🚦 Step8: 嚴重問題檢查'); From 78ec8f6d6a1c405a6694d94bd16b31a8d2e0bc23 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 15 May 2026 06:17:09 +0000 Subject: [PATCH 2/3] test: cover saveFindings temp dir cases --- app/comments.test.js | 65 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/app/comments.test.js b/app/comments.test.js index 27927bd..539ac2b 100644 --- a/app/comments.test.js +++ b/app/comments.test.js @@ -1,15 +1,22 @@ -import { describe, it } from 'node:test'; +import { describe, it, afterEach } from 'node:test'; import assert from 'node:assert/strict'; -import fs from 'fs'; -import os from 'os'; -import path from 'path'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import { saveFindings } from './comments.js'; import { FINDINGS_PATH } from './config.js'; describe('saveFindings', () => { + const tempDirs = []; + const makeTempDir = prefix => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + tempDirs.push(dir); + return dir; + }; + it('writes findings to workspace and mirror dirs when provided', () => { - const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'findings-ws-')); - const mirrorDir = fs.mkdtempSync(path.join(os.tmpdir(), 'findings-mirror-')); + const workspace = makeTempDir('findings-ws-'); + const mirrorDir = makeTempDir('findings-mirror-'); const findings = [{ level: 'warning', role: 'Leo', location: 'file.js:1', suggestion: 'test' }]; saveFindings(workspace, findings, mirrorDir); @@ -19,4 +26,50 @@ describe('saveFindings', () => { assert.equal(workspaceText, JSON.stringify(findings, null, 2) + '\n'); assert.equal(mirrorText, JSON.stringify(findings, null, 2) + '\n'); }); + + it('writes only to workspace when mirrorDir is omitted', () => { + const workspace = makeTempDir('findings-ws-'); + const findings = [{ level: 'info', role: 'Maya', location: 'file.js:2', suggestion: 'note' }]; + + saveFindings(workspace, findings); + + const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8'); + assert.equal(workspaceText, JSON.stringify(findings, null, 2) + '\n'); + }); + + it('does not duplicate writes when mirrorDir matches workspace', () => { + const workspace = makeTempDir('findings-same-'); + const findings = []; + const writeCalls = []; + const originalWriteFileSync = fs.writeFileSync; + + fs.writeFileSync = (...args) => { + writeCalls.push(args[0]); + return originalWriteFileSync(...args); + }; + + try { + saveFindings(workspace, findings, workspace); + } finally { + fs.writeFileSync = originalWriteFileSync; + } + + assert.equal(writeCalls.length, 1); + assert.equal(writeCalls[0], path.join(workspace, FINDINGS_PATH)); + }); + + it('writes an empty JSON array when findings is empty', () => { + const workspace = makeTempDir('findings-empty-'); + + saveFindings(workspace, []); + + const workspaceText = fs.readFileSync(path.join(workspace, FINDINGS_PATH), 'utf8'); + assert.equal(workspaceText, '[]\n'); + }); + + afterEach(() => { + while (tempDirs.length > 0) { + fs.rmSync(tempDirs.pop(), { recursive: true, force: true }); + } + }); }); From 4a29c4aaa36ff23dcd01bbf4b8be035bbf5c7ee0 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 15 May 2026 06:23:07 +0000 Subject: [PATCH 3/3] fix: refresh repo before staging review files --- app/git.js | 12 +++++++++++- app/git.test.js | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/git.js b/app/git.js index d7fc88e..1f71689 100644 --- a/app/git.js +++ b/app/git.js @@ -68,6 +68,10 @@ export async function commitAndPush(workspace, repoDir, _spawnSync = spawnSync, await withAskpass(workspace, async credEnv => { run(['config', 'user.email', 'ai-review[bot]@gitea'], repoDir); run(['config', 'user.name', 'AI Review Bot'], repoDir); + if (PR_HEAD_BRANCH) { + run(['fetch', 'origin', PR_HEAD_BRANCH], repoDir, credEnv); + run(['reset', '--hard', `origin/${PR_HEAD_BRANCH}`], repoDir); + } const existingSyncPaths = []; @@ -86,8 +90,14 @@ export async function commitAndPush(workspace, repoDir, _spawnSync = spawnSync, if (existingSyncPaths.length > 0) { run(['add', ...existingSyncPaths], repoDir); } - const generatedSyncPaths = GENERATED_SYNC_PATHS.filter(relPath => fs.existsSync(path.join(repoDir, relPath))); + const generatedSyncPaths = GENERATED_SYNC_PATHS.filter(relPath => fs.existsSync(path.join(workspace, relPath))); if (generatedSyncPaths.length > 0) { + for (const relPath of generatedSyncPaths) { + const src = path.join(workspace, relPath); + const dest = path.join(repoDir, relPath); + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(src, dest); + } run(['add', ...generatedSyncPaths], repoDir); } diff --git a/app/git.test.js b/app/git.test.js index c519d15..a86cd34 100644 --- a/app/git.test.js +++ b/app/git.test.js @@ -95,6 +95,9 @@ describe('commitAndPush', () => { it('adds skill and entry files together with findings', async () => { const repoDir = path.join(workspace, 'repo'); + fs.mkdirSync(path.join(workspace, '.gitea/ai-review'), { recursive: true }); + fs.writeFileSync(path.join(workspace, '.gitea/ai-review/findings.json'), '[]\n'); + fs.writeFileSync(path.join(workspace, '.gitea/ai-review/exclusions.json'), '[]\n'); fs.mkdirSync(path.join(repoDir, '.gitea/ai-review'), { recursive: true }); fs.writeFileSync(path.join(repoDir, '.gitea/ai-review/findings.json'), '[]\n'); fs.writeFileSync(path.join(repoDir, '.gitea/ai-review/exclusions.json'), '[]\n');