From 49a02ebb6bb6eacacca46917c586a61ed03c4d71 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 13 May 2026 01:02:33 +0000 Subject: [PATCH] feat: add JSON format validation for findings and exclusions after processing --- .gitea/ai-review/exclusions.json | 5 +++++ README.md | 3 +++ TODO.md | 5 +++++ app/gitea.js | 4 ++++ app/main.js | 23 ++++++++++++++++++++++- 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json index 3743a5a..ac80e9f 100644 --- a/.gitea/ai-review/exclusions.json +++ b/.gitea/ai-review/exclusions.json @@ -149,6 +149,11 @@ "location": "app/llm.test.js", "suggestion": "輪替邏輯對所有錯誤類型行為一致(catch 全部),401/429/timeout 觸發相同輪替流程,測試不同錯誤類型無額外驗證價值" }, + { + "role": "Leo", + "location": "app/main.js", + "suggestion": "main.js 中的 Step 標題註解為 pipeline 流程說明,非待整理的 TODO,不需要轉換為具體任務" + }, { "role": "Rex", "location": "app/package.json", diff --git a/README.md b/README.md index f6b6658..6392812 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ 5. 將提示詞放到 ./app/prompts 內供程式讀取 6. API Key 支援逗號分隔傳入多個,隨機順序各嘗試一次,全部失敗則 exit 1 7. 讀取 Git Diff 時排除 `.gitea/` 資料夾內的所有檔案,避免 AI 分析 workflow 設定等非業務程式碼 +8. 階段五完成後驗證 `findings.json` 與 `exclusions.json` 是否為合法 JSON 格式,驗證失敗則 exit 1 # 使用說明 @@ -30,6 +31,8 @@ 2. 在 `.gitea/workflows` 資料夾中建立 `ai-review.yaml' 3. 在 `ai-review.yaml` 中填入以下內容(選擇一個使用): +> **權限說明**:此 Action 需要 `contents: write`(寫入 findings.json)、`pull-requests: write`(發佈 PR comment)、`issues: write`(發佈 issue comment)三項權限,為正常運作所必要,無法縮減。 + ### 1. OpenAI ```yaml name: AI diff --git a/TODO.md b/TODO.md index 255c9ce..ff6ca44 100644 --- a/TODO.md +++ b/TODO.md @@ -45,6 +45,11 @@ - 驗收:PR 中有 `.gitea/` 路徑的變更時,diff 內容不包含該路徑的區塊,AI 分析結果不含 `.gitea/` 相關問題。 - 完成 +## 階段十:階段五後驗證 JSON 格式 +- 目標:階段五完成後驗證 `findings.json` 與 `exclusions.json` 是否為合法 JSON 格式,驗證失敗則 exit 1。 +- 驗收:log 中能看到兩個檔案的驗證結果(成功或失敗),格式錯誤時有明確錯誤訊息且 workflow 狀態為失敗。 +- 完成 + --- 所有階段驗收通過。 diff --git a/app/gitea.js b/app/gitea.js index e35ccab..b7a048b 100644 --- a/app/gitea.js +++ b/app/gitea.js @@ -11,6 +11,10 @@ export async function getPRDiff() { return filterDiff(resp.data, ['.gitea/']); } +/** + * 過濾 diff 內容,移除路徑符合 excludePrefixes 的區塊。 + * 每個區塊以 "diff --git a/" 開頭判斷,使用 startsWith 精確比對前綴。 + */ function filterDiff(diff, excludePrefixes) { return diff.split(/(?=^diff --git )/m) .filter(block => !excludePrefixes.some(p => block.startsWith(`diff --git a/${p}`))) diff --git a/app/main.js b/app/main.js index e25b839..8d552fe 100644 --- a/app/main.js +++ b/app/main.js @@ -1,4 +1,6 @@ -import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig } from './config.js'; +import fs from 'fs'; +import path from 'path'; +import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig, FINDINGS_PATH, EXCLUSIONS_PATH } from './config.js'; import { loadRoles, getRoleIntro } from './roles.js'; import { getPRDiff, postComment } from './gitea.js'; import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions, filterFalsePositivesWithAI } from './findings.js'; @@ -60,6 +62,7 @@ async function main() { // Step3: 讀取舊 findings,合併去重(含 AI 語意去重) console.log('\n🔀 Step3: Findings 合併'); + // Clone repo 以讀取舊 findings 與排除清單 let repoDir; try { repoDir = cloneRepo(WORKSPACE); @@ -77,6 +80,7 @@ async function main() { // Step4: 讀取排除問題檔案,過濾 PR 問題表格,並請 AI 判斷誤報 console.log('\n🚫 Step4: AI 排除問題過濾'); + // 輸入至 findings 用於 AI 誤報過濾,exclusions 同時作為已知誤報參考 const exclusions = loadExclusions(repoDir || WORKSPACE); const ruleFiltered = applyExclusions(sorted, exclusions); const filtered = await filterFalsePositivesWithAI(ruleFiltered, exclusions); @@ -94,6 +98,23 @@ async function main() { console.log(` ⚠️ comment 發布失敗(繼續執行): ${e.message}`); } + // Step5b: 驗證 findings.json 與 exclusions.json 為合法 JSON + console.log('\n🔎 Step5b: JSON 格式驗證'); + for (const relPath of [FINDINGS_PATH, EXCLUSIONS_PATH]) { + const fullPath = path.join(repoDir || WORKSPACE, relPath); + if (!fs.existsSync(fullPath)) { + console.log(` ⚠️ ${relPath} 不存在,跳過驗證`); + continue; + } + try { + JSON.parse(fs.readFileSync(fullPath, 'utf8')); + console.log(` ✅ ${relPath} JSON 格式正確`); + } catch (e) { + console.error(` ❌ ${relPath} JSON 格式錯誤: ${e.message}`); + process.exit(1); + } + } + // Step6: commit/push findings.json 到來源分支 console.log('\n💾 Step6: 記憶區 Commit/Push'); await commitAndPush(WORKSPACE);