fix(review-resolve): 支援 wrapper findings 並修正輸出格式
This commit is contained in:
+24
-33
@@ -26,27 +26,6 @@ export async function analyzeWithRole(role, diff) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 讀取 JSON 陣列檔案;檔案不存在、讀取失敗或內容非陣列時,皆視為空並回傳 []。
|
||||
*
|
||||
* @param {string} fullPath - 欲讀取的 JSON 檔案完整路徑。
|
||||
* @param {string} label - 用於警告訊息中識別此次讀取對象的標籤文字(例如「舊 findings 」)。
|
||||
* @returns {Array<object>} 解析出的陣列;任何失敗情況皆回傳空陣列 []。
|
||||
*/
|
||||
function readJSONArray(fullPath, label) {
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
warn(`${label}檔案不存在,視為空`);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
||||
return Array.isArray(data) ? data : [];
|
||||
} catch (e) {
|
||||
warn(`讀取${label}失敗: ${e.message},視為空`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 將排除設定(頂層陣列、{ exclusions: [] } 或 { excluded_findings: [] })正規化為條目陣列。
|
||||
*
|
||||
@@ -305,7 +284,8 @@ function buildExclusionContext(exclusions) {
|
||||
|
||||
/**
|
||||
* 讀取舊 findings(來源分支 cloned repoDir 下 FINDINGS_PATH 指向的檔案),
|
||||
* 每筆項目一律標記 is_new: false(代表非本次新產生),並記錄檔案大小/修改時間等診斷日誌。
|
||||
* 同時相容舊版頂層陣列與新版 wrapper 物件;每筆項目一律標記 is_new: false
|
||||
*(代表非本次新產生),並記錄檔案大小/修改時間等診斷日誌。
|
||||
* 檔案不存在或讀取失敗時視為空陣列,不拋例外。
|
||||
*
|
||||
* @param {string} workspace - 來源分支 clone 出的工作目錄根路徑,FINDINGS_PATH 會相對此路徑解析。
|
||||
@@ -313,11 +293,20 @@ function buildExclusionContext(exclusions) {
|
||||
*/
|
||||
export function loadOldFindings(workspace) {
|
||||
const fullPath = path.join(workspace, FINDINGS_PATH);
|
||||
const old = readJSONArray(fullPath, '舊 findings ').map(f => ({ ...f, is_new: false }));
|
||||
let old = [];
|
||||
if (fs.existsSync(fullPath)) {
|
||||
const stat = fs.statSync(fullPath);
|
||||
line(`讀取舊 findings 檔案: ${fullPath}`);
|
||||
line(`舊 findings 檔案資訊: bytes=${stat.size} mtime=${formatFileTime(stat.mtimeMs)} path=${path.relative(workspace, fullPath) || fullPath}`);
|
||||
try {
|
||||
const stat = fs.statSync(fullPath);
|
||||
const data = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
|
||||
const sourceFormat = Array.isArray(data) ? 'array' : (data && Array.isArray(data.findings) ? 'wrapper' : 'unknown');
|
||||
const rawFindings = Array.isArray(data) ? data : (data && Array.isArray(data.findings) ? data.findings : []);
|
||||
old = rawFindings.map(f => ({ ...f, is_new: false }));
|
||||
line(`讀取舊 findings 檔案: ${fullPath}`);
|
||||
line(`舊 findings 檔案資訊: bytes=${stat.size} mtime=${formatFileTime(stat.mtimeMs)} source=${sourceFormat} path=${path.relative(workspace, fullPath) || fullPath}`);
|
||||
} catch (e) {
|
||||
warn(`讀取舊 findings 失敗: ${e.message},視為空: ${fullPath}`);
|
||||
old = [];
|
||||
}
|
||||
} else {
|
||||
warn(`舊 findings 檔案不存在: ${fullPath}`);
|
||||
}
|
||||
@@ -326,7 +315,7 @@ export function loadOldFindings(workspace) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 合併新舊 findings:以 (role + location + suggestion 前 50 字) 組成的字串為 key,
|
||||
* 合併新舊 findings:以 (role + location + problem + suggestion 前 50 字) 組成的字串為 key,
|
||||
* 過濾掉 newFindings 中與 oldFindings(或 newFindings 自身先出現的項目)key 相同的重複項。
|
||||
* oldFindings 本身不會互相去重(視為既有基準),回傳陣列為 [...oldFindings, ...去重後的 newFindings]。
|
||||
*
|
||||
@@ -335,7 +324,7 @@ export function loadOldFindings(workspace) {
|
||||
* @returns {Array<object>} 合併後的 findings 陣列,不修改傳入的兩個陣列本身。
|
||||
*/
|
||||
export function mergeFindings(oldFindings, newFindings) {
|
||||
const key = f => `${f.role}|${f.location}|${String(f.suggestion).slice(0, 50)}`;
|
||||
const key = f => `${f.role}|${f.location}|${String(f.problem || '')}|${String(f.suggestion || '').slice(0, 50)}`;
|
||||
const seen = new Set(oldFindings.map(key));
|
||||
const deduped = newFindings.filter(f => {
|
||||
if (seen.has(key(f))) return false;
|
||||
@@ -348,15 +337,17 @@ export function mergeFindings(oldFindings, newFindings) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 依等級排序(critical > warning > info),回傳新陣列,不修改傳入的 findings。
|
||||
* 依等級排序(critical > warning > info,未知等級排最後),回傳新陣列,不修改傳入的 findings。
|
||||
*
|
||||
* @param {Array<object>} findings - 欲排序的 findings 陣列(各筆需含 level 欄位)。
|
||||
* @returns {Array<object>} 依 critical/warning/info 順序排序後的新陣列。
|
||||
* @remarks level 不在 ['critical','warning','info'] 中的項目,因 indexOf 回傳 -1,
|
||||
* 會被排到 critical 之前(最前面)而非最後面;此邊界行為是否為預期設計,需人工確認。
|
||||
* @returns {Array<object>} 依 critical/warning/info 順序排序後的新陣列;未知等級會排在最後。
|
||||
*/
|
||||
export function sortByLevel(findings) {
|
||||
return [...findings].sort((a, b) => LEVELS.indexOf(a.level) - LEVELS.indexOf(b.level));
|
||||
const rank = (level) => {
|
||||
const index = LEVELS.indexOf(level);
|
||||
return index === -1 ? LEVELS.length : index;
|
||||
};
|
||||
return [...findings].sort((a, b) => rank(a.level) - rank(b.level));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user