feat: 階段二 - Findings 產生與合併

- app/findings.js: 各角色分析 diff、讀取舊 findings、合併去重、等級排序
- app/main.js: 實作 Step2/Step3,log findings 統計
This commit is contained in:
2026-05-11 07:52:21 +00:00
parent cdac64e224
commit 46dd8320d1
2 changed files with 84 additions and 4 deletions
+63
View File
@@ -0,0 +1,63 @@
import fs from 'fs';
import path from 'path';
import { chatJSON } from './llm.js';
import { FINDINGS_PATH } from './config.js';
const LEVELS = ['critical', 'warning', 'info'];
/**
* 用單一角色分析 diff,回傳 findings 陣列
*/
export async function analyzeWithRole(role, diff) {
console.log(` [${role.name}] 開始分析...`);
const findings = await chatJSON(role.system_prompt, `以下是 Git Diff 內容:\n\n${diff}`);
// 確保每筆都有必要欄位,並標記為新問題
const valid = findings.filter(f => f.level && f.role && f.location && f.suggestion)
.map(f => ({ ...f, is_new: true }));
console.log(` [${role.name}] 找到 ${valid.length} 個問題`);
return valid;
}
/**
* 讀取舊 findings(從 workspace 的 FINDINGS_PATH
*/
export function loadOldFindings(workspace) {
const fullPath = path.join(workspace, FINDINGS_PATH);
if (!fs.existsSync(fullPath)) {
console.log(' 舊 findings 檔案不存在,視為空');
return [];
}
try {
const data = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
const old = (Array.isArray(data) ? data : []).map(f => ({ ...f, is_new: false }));
console.log(` 讀取舊 findings: ${old.length}`);
return old;
} catch (e) {
console.log(` ⚠️ 讀取舊 findings 失敗: ${e.message},視為空`);
return [];
}
}
/**
* 合併新舊 findings,以 (role + location + suggestion前50字) 為 key 去除重複
* 舊問題保留,新問題若與舊問題重複則捨棄
*/
export function mergeFindings(oldFindings, newFindings) {
const key = f => `${f.role}|${f.location}|${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;
seen.add(key(f));
return true;
});
const merged = [...oldFindings, ...deduped];
console.log(` 合併結果: 舊=${oldFindings.length} 新(去重後)=${deduped.length} 總計=${merged.length}`);
return merged;
}
/**
* 依等級排序(critical > warning > info
*/
export function sortByLevel(findings) {
return [...findings].sort((a, b) => LEVELS.indexOf(a.level) - LEVELS.indexOf(b.level));
}