feat: 階段二 - Findings 產生與合併
- app/findings.js: 各角色分析 diff、讀取舊 findings、合併去重、等級排序 - app/main.js: 實作 Step2/Step3,log findings 統計
This commit is contained in:
@@ -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));
|
||||
}
|
||||
+21
-4
@@ -1,6 +1,9 @@
|
||||
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig } from './config.js';
|
||||
import { loadRoles, getRoleIntro } from './roles.js';
|
||||
import { getPRDiff, postComment } from './gitea.js';
|
||||
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel } from './findings.js';
|
||||
|
||||
const WORKSPACE = process.env.GITHUB_WORKSPACE || '/workspace';
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(60));
|
||||
@@ -46,11 +49,25 @@ async function main() {
|
||||
console.log(` ⚠️ comment 發布失敗(繼續執行): ${e.message}`);
|
||||
}
|
||||
|
||||
console.log('\n📊 Step2: Findings 產生(待實作)');
|
||||
console.log(' [stub] 各角色分析 diff...');
|
||||
// Step2: 各角色分析 diff 產生新 findings
|
||||
console.log('\n📊 Step2: Findings 產生');
|
||||
const newFindings = [];
|
||||
for (const role of roles) {
|
||||
try {
|
||||
const found = await analyzeWithRole(role, diff);
|
||||
newFindings.push(...found);
|
||||
} catch (e) {
|
||||
console.log(` ⚠️ [${role.name}] 分析失敗(跳過): ${e.message}`);
|
||||
}
|
||||
}
|
||||
console.log(` Step2 完成: 新 findings 總計 ${newFindings.length} 筆`);
|
||||
|
||||
console.log('\n🔀 Step3: Findings 合併與去重(待實作)');
|
||||
console.log(' [stub] 合併新舊 findings...');
|
||||
// Step3: 讀取舊 findings,合併去重
|
||||
console.log('\n🔀 Step3: Findings 合併');
|
||||
const oldFindings = loadOldFindings(WORKSPACE);
|
||||
const mergedFindings = mergeFindings(oldFindings, newFindings);
|
||||
const sorted = sortByLevel(mergedFindings);
|
||||
console.log(` Step3 merged findings total=${sorted.length} (critical=${sorted.filter(f=>f.level==='critical').length} warning=${sorted.filter(f=>f.level==='warning').length} info=${sorted.filter(f=>f.level==='info').length})`);
|
||||
|
||||
console.log('\n📝 Step4: Findings 寫入與 Comment 發布(待實作)');
|
||||
console.log(' [stub] 寫入 findings.json,發布 comment...');
|
||||
|
||||
Reference in New Issue
Block a user