Compare commits
2 Commits
v0.0.9
...
v0.1.1-beta.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c5773e4fd | |||
| ece7377fc8 |
+12
-6
@@ -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} 筆)`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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: 嚴重問題檢查');
|
||||||
|
|||||||
Reference in New Issue
Block a user