From f43ba63f0fa515c62273098c7f590dc92cfb0ae4 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 15 May 2026 06:39:17 +0000 Subject: [PATCH 1/2] fix: support wrapped exclusions schema --- app/findings.js | 22 ++++++++++++++++++- app/findings.test.js | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 app/findings.test.js diff --git a/app/findings.js b/app/findings.js index d045941..82ae388 100644 --- a/app/findings.js +++ b/app/findings.js @@ -34,6 +34,12 @@ function readJSONArray(fullPath, label) { } } +function normalizeExclusions(data) { + if (Array.isArray(data)) return data; + if (data && Array.isArray(data.excluded_findings)) return data.excluded_findings; + return []; +} + /** * 讀取舊 findings(從 workspace 的 FINDINGS_PATH) */ @@ -107,7 +113,21 @@ export async function deduplicateWithAI(findings) { * 讀取排除問題檔案(從 workspace 的 EXCLUSIONS_PATH) */ export function loadExclusions(workspace) { - const exclusions = readJSONArray(path.join(workspace, EXCLUSIONS_PATH), '排除問題'); + const fullPath = path.join(workspace, EXCLUSIONS_PATH); + if (!fs.existsSync(fullPath)) { + console.log(' 排除問題檔案不存在,視為空'); + console.log(' 讀取排除問題: 0 筆'); + return []; + } + + let exclusions = []; + try { + const data = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + exclusions = normalizeExclusions(data); + } catch (e) { + console.log(` ⚠️ 讀取排除問題失敗: ${e.message},視為空`); + exclusions = []; + } console.log(` 讀取排除問題: ${exclusions.length} 筆`); return exclusions; } diff --git a/app/findings.test.js b/app/findings.test.js new file mode 100644 index 0000000..d163a2c --- /dev/null +++ b/app/findings.test.js @@ -0,0 +1,50 @@ +import { describe, it, beforeEach, 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 { loadExclusions, applyExclusions } from './findings.js'; +import { EXCLUSIONS_PATH } from './config.js'; + +describe('findings exclusions', () => { + let workspace; + + beforeEach(() => { + workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'findings-test-')); + }); + + afterEach(() => { + fs.rmSync(workspace, { recursive: true, force: true }); + }); + + it('loads excluded_findings wrapper format', () => { + const fullPath = path.join(workspace, EXCLUSIONS_PATH); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, JSON.stringify({ + excluded_findings: [ + { location: 'entrypoint.sh:180', title: 'fetch_package_versions jq overhead' }, + ], + }, null, 2)); + + const exclusions = loadExclusions(workspace); + + assert.equal(exclusions.length, 1); + assert.equal(exclusions[0].location, 'entrypoint.sh:180'); + assert.equal(exclusions[0].title, 'fetch_package_versions jq overhead'); + }); + + it('applies exclusions loaded from wrapper format', () => { + const findings = [ + { location: 'entrypoint.sh:180', role: 'Maya', suggestion: 'keep' }, + { location: 'README.md:12', role: 'Maya', suggestion: 'keep' }, + ]; + const exclusions = [ + { location: 'entrypoint.sh:180', title: 'fetch_package_versions jq overhead' }, + ]; + + const filtered = applyExclusions(findings, exclusions); + + assert.equal(filtered.length, 1); + assert.equal(filtered[0].location, 'README.md:12'); + }); +}); From c88c0d02c8fb79e1219a71cd2921d75deb146063 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 15 May 2026 06:43:20 +0000 Subject: [PATCH 2/2] docs: clarify source branch review files --- README.md | 8 ++++---- app/findings.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5092701..b61d00f 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ 1. 服務名稱、模型名稱、角色資訊(個性、符合個性的英文名稱、工作內容),Comment 到 Push Request 2. 每個角色個別分析 Git Diff 的內容產生新問題表格(問題等級、角色名稱、問題位置或行數、修改建議) -3. 讀取所有未解決的舊問題(問題檔案 `.gitea/ai-review/findings.json` 存在於使用此 Action 的專案固定位置)加上新問題後,去除重複產生本次 Push Request 的問題表格(PR問題表格)覆蓋問題檔案 -4. 讀取排除問題檔案(`.gitea/ai-review/exclusions.json` 存在於使用此 Action 的專案固定位置),用來過濾PR問題表格中不需要處理的問題 +3. 讀取來源分支中的所有未解決舊問題(問題檔案 `.gitea/ai-review/findings.json`)加上新問題後,去除重複產生本次 Push Request 的問題表格(PR問題表格)覆蓋問題檔案 +4. 讀取來源分支中的排除問題檔案(`.gitea/ai-review/exclusions.json`),用來過濾PR問題表格中不需要處理的問題 5. 從PR問題表格中取出所有舊問題,依照等級排序後 Comment 到 Push Request 6. 從PR問題表格中取出所有新問題,排除嚴重等級的問題後 Comment 到 Push Request 7. 從PR問題表格中取出所有新問題,將每個嚴重等級的問題 Comment 到 Push Request @@ -23,7 +23,7 @@ 5. 將提示詞放到 ./app/prompts 內供程式讀取 6. API Key 支援逗號分隔傳入多個,隨機順序各嘗試一次,全部失敗則 exit 1 7. 讀取 Git Diff 時排除 `.gitea/`、`.amazonq/`、`.claude/`、`.codex/`、`.gemini/`、`.github/` 資料夾,以及 `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 用量 # 使用說明 @@ -227,4 +227,4 @@ Amazon Q:直接輸入 `triage-findings 問題原始檔(文字或截圖)` ### 版本包含 -提交時一併包含 `triage-findings` skill 與各平台入口檔;已存在檔案一律覆蓋,同步到最新內容;若 workspace 沒有某個同步檔,記憶區會保留原檔,不做刪除。寫入 `.gitea/ai-review/exclusions.json` 時,盡量保留原始問題文字的語言與語意,避免過度改寫。未來若新增任何 skill 或新增其他平台的 skill 入口,必須同時把對應檔案複製進 Docker image,並把同步清單更新到會使用此 action 的目標專案,避免 action 與目標專案內容脫節。 +提交時一併包含 `triage-findings` skill 與各平台入口檔;已存在檔案一律覆蓋,同步到最新內容;若 workspace 沒有某個同步檔,記憶區會保留原檔,不做刪除。`findings.json` 與 `exclusions.json` 都從使用此 action 的存取庫來源分支讀取,而不是從 action 本地 workspace 讀取。寫入 `.gitea/ai-review/exclusions.json` 時,盡量保留原始問題文字的語言與語意,避免過度改寫。未來若新增任何 skill 或新增其他平台的 skill 入口,必須同時把對應檔案複製進 Docker image,並把同步清單更新到會使用此 action 的目標專案,避免 action 與目標專案內容脫節。 diff --git a/app/findings.js b/app/findings.js index 82ae388..41db736 100644 --- a/app/findings.js +++ b/app/findings.js @@ -41,7 +41,7 @@ function normalizeExclusions(data) { } /** - * 讀取舊 findings(從 workspace 的 FINDINGS_PATH) + * 讀取舊 findings(從來源分支的 cloned repoDir 中的 FINDINGS_PATH) */ export function loadOldFindings(workspace) { const old = readJSONArray(path.join(workspace, FINDINGS_PATH), '舊 findings ').map(f => ({ ...f, is_new: false })); @@ -110,7 +110,7 @@ export async function deduplicateWithAI(findings) { } /** - * 讀取排除問題檔案(從 workspace 的 EXCLUSIONS_PATH) + * 讀取排除問題檔案(從來源分支的 cloned repoDir 中的 EXCLUSIONS_PATH) */ export function loadExclusions(workspace) { const fullPath = path.join(workspace, EXCLUSIONS_PATH);