Merge pull request 'fix: write findings to review dir' (#110) from develop into master

Reviewed-on: #110
This commit is contained in:
2026-05-15 06:25:29 +00:00
5 changed files with 105 additions and 10 deletions
+12 -6
View File
@@ -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} 筆)`);
}
}
/**
+75
View File
@@ -0,0 +1,75 @@
import { describe, it, afterEach } from 'node:test';
import assert from 'node:assert/strict';
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 = makeTempDir('findings-ws-');
const mirrorDir = makeTempDir('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');
});
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 });
}
});
});
+11 -1
View File
@@ -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);
}
+3
View File
@@ -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');
+4 -3
View File
@@ -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: 嚴重問題檢查');