Compare commits

...

2 Commits

Author SHA1 Message Date
jiantw83 5c5773e4fd fix: write findings to review dir 2026-05-15 06:10:09 +00:00
jiantw83 ece7377fc8 fix: stage generated review files 2026-05-15 05:47:06 +00:00
5 changed files with 64 additions and 23 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) { export function saveFindings(workspace, findings, mirrorDir = null) {
const fullPath = path.join(workspace, FINDINGS_PATH); const targets = [workspace];
fs.mkdirSync(path.dirname(fullPath), { recursive: true }); if (mirrorDir && mirrorDir !== workspace) targets.push(mirrorDir);
fs.writeFileSync(fullPath, JSON.stringify(findings, null, 2) + '\n', 'utf8');
console.log(` ✅ findings 寫入: ${fullPath} (${findings.length} 筆)`); 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} 筆)`);
}
} }
/** /**
+22
View File
@@ -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');
});
});
+5 -1
View File
@@ -5,9 +5,9 @@ import { fileURLToPath } from 'url';
import { GITEA_SERVER_URL, GITEA_REPOSITORY, GITEA_TOKEN, PR_HEAD_BRANCH, FINDINGS_PATH } from './config.js'; import { GITEA_SERVER_URL, GITEA_REPOSITORY, GITEA_TOKEN, PR_HEAD_BRANCH, FINDINGS_PATH } from './config.js';
const ACTION_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const ACTION_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const GENERATED_SYNC_PATHS = [FINDINGS_PATH, '.gitea/ai-review/exclusions.json'];
const remoteUrl = `${GITEA_SERVER_URL.replace(/\/$/, '')}/${GITEA_REPOSITORY}.git`; const remoteUrl = `${GITEA_SERVER_URL.replace(/\/$/, '')}/${GITEA_REPOSITORY}.git`;
export const SYNC_PATHS = [ export const SYNC_PATHS = [
FINDINGS_PATH,
'.amazonq/rules/triage-findings.md', '.amazonq/rules/triage-findings.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',
@@ -86,6 +86,10 @@ export async function commitAndPush(workspace, repoDir, _spawnSync = spawnSync,
if (existingSyncPaths.length > 0) { if (existingSyncPaths.length > 0) {
run(['add', ...existingSyncPaths], repoDir); run(['add', ...existingSyncPaths], repoDir);
} }
const generatedSyncPaths = GENERATED_SYNC_PATHS.filter(relPath => fs.existsSync(path.join(repoDir, relPath)));
if (generatedSyncPaths.length > 0) {
run(['add', ...generatedSyncPaths], repoDir);
}
const status = run(['status', '--porcelain'], repoDir); const status = run(['status', '--porcelain'], repoDir);
if (!status) { if (!status) {
+21 -13
View File
@@ -94,20 +94,28 @@ describe('commitAndPush', () => {
}); });
it('adds skill and entry files together with findings', async () => { it('adds skill and entry files together with findings', async () => {
const repoDir = path.join(workspace, 'repo');
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');
const spawn = makeSpawn(); const spawn = makeSpawn();
await commitAndPush(workspace, path.join(workspace, 'repo'), spawn, sourceRoot); await commitAndPush(workspace, repoDir, spawn, sourceRoot);
const addCall = spawn.calls.find(c => c.args[0] === 'add'); const addCalls = spawn.calls.filter(c => c.args[0] === 'add');
assert.ok(addCall, 'expected git add to run'); const skillAddCall = addCalls.find(c => c.args.includes('.github/skills/triage-findings/SKILL.md'));
assert.ok(addCall.args.includes('.github/skills/triage-findings/SKILL.md')); const generatedAddCall = addCalls.find(c => c.args.includes('.gitea/ai-review/exclusions.json'));
assert.ok(addCall.args.includes('.codex/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall, 'expected git add for synced skill files');
assert.ok(addCall.args.includes('.codex/skills/triage-findings/agents/openai.yaml')); assert.ok(generatedAddCall, 'expected git add for generated review files');
assert.ok(addCall.args.includes('.claude/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/SKILL.md'));
assert.ok(addCall.args.includes('.gemini/skills/triage-findings/SKILL.md')); assert.ok(skillAddCall.args.includes('.codex/skills/triage-findings/agents/openai.yaml'));
assert.ok(addCall.args.includes('.github/copilot-instructions.md')); assert.ok(skillAddCall.args.includes('.claude/skills/triage-findings/SKILL.md'));
assert.ok(addCall.args.includes('.amazonq/rules/triage-findings.md')); assert.ok(skillAddCall.args.includes('.gemini/skills/triage-findings/SKILL.md'));
assert.ok(addCall.args.includes('CLAUDE.md')); assert.ok(skillAddCall.args.includes('.github/copilot-instructions.md'));
assert.ok(addCall.args.includes('GEMINI.md')); assert.ok(skillAddCall.args.includes('.amazonq/rules/triage-findings.md'));
assert.ok(!addCall.args.includes('README.md')); assert.ok(skillAddCall.args.includes('CLAUDE.md'));
assert.ok(skillAddCall.args.includes('GEMINI.md'));
assert.ok(!skillAddCall.args.includes('README.md'));
assert.ok(generatedAddCall.args.includes('.gitea/ai-review/findings.json'));
assert.ok(generatedAddCall.args.includes('.gitea/ai-review/exclusions.json'));
}); });
it('keeps repo copies when the source sync file is missing', async () => { it('keeps repo copies when the source sync file is missing', async () => {
+4 -3
View File
@@ -88,7 +88,8 @@ async function main() {
// Step6: 寫入 findings.json,依序發布 comment // Step6: 寫入 findings.json,依序發布 comment
console.log('\n📝 Step5: Findings 寫入與 Comment 發布'); console.log('\n📝 Step5: Findings 寫入與 Comment 發布');
saveFindings(WORKSPACE, filtered); const reviewDir = repoDir || WORKSPACE;
saveFindings(WORKSPACE, filtered, reviewDir);
try { try {
await postOldFindingsComment(filtered); await postOldFindingsComment(filtered);
await postNewNonCriticalComment(filtered); await postNewNonCriticalComment(filtered);
@@ -102,7 +103,7 @@ async function main() {
console.log('\n🔎 Step6: JSON 格式驗證'); console.log('\n🔎 Step6: JSON 格式驗證');
const missingPaths = []; const missingPaths = [];
for (const relPath of [FINDINGS_PATH, EXCLUSIONS_PATH]) { for (const relPath of [FINDINGS_PATH, EXCLUSIONS_PATH]) {
const fullPath = path.join(repoDir || WORKSPACE, relPath); const fullPath = path.join(reviewDir, relPath);
try { try {
const result = await validateJSONArrayFile(fullPath, relPath); const result = await validateJSONArrayFile(fullPath, relPath);
if (!result.exists) missingPaths.push({ fullPath, relPath }); if (!result.exists) missingPaths.push({ fullPath, relPath });
@@ -117,7 +118,7 @@ async function main() {
// Step7: commit/push findings.json 到來源分支 // Step7: commit/push findings.json 到來源分支
console.log('\n💾 Step7: 記憶區 Commit/Push'); console.log('\n💾 Step7: 記憶區 Commit/Push');
await commitAndPush(WORKSPACE, repoDir); await commitAndPush(WORKSPACE, repoDir || WORKSPACE);
// Step9: 有 critical 問題則 exit 1 // Step9: 有 critical 問題則 exit 1
console.log('\n🚦 Step8: 嚴重問題檢查'); console.log('\n🚦 Step8: 嚴重問題檢查');