fix(diagnostics): 處理 ai review findings #32

Open
jiantw83 wants to merge 85 commits from develop into master
Showing only changes of commit 4a68743b66 - Show all commits
+36 -4
View File
@@ -13,6 +13,38 @@ const templates = require('./templates');
const PER_FILE_DIFF_LIMIT = 16_000;
const TOTAL_DIFF_LIMIT = 160_000;
/**
* 從 `runAgent` 的失敗結果組出可診斷的一行摘要:退出碼/訊號/stderr/stdout 片段。
*
* AI CLI 失敗時常見 stderr 為空、真正原因印在 stdout(例如 CLI 用法錯誤、未認證或額度提示),
* 若只記 `error.message` 會看不出原因。本函式把 exit code、stderr、stdout 各截前 500 字併成一行,
* 供各失敗點的 WRN log 使用。純函式、不拋例外。
*
* @param {{error: (Error & {code?: number|string, signal?: string, killed?: boolean})|null, stderr?: string, output?: string}} res
* `runAgent` 的回傳物件。
* @returns {string} 單行診斷摘要(各段以「|」分隔);無任何資訊時回傳 error.message 或「未知錯誤」。
* @remarks
* 使用情境:{@link runAttackers}{@link runDefenders}{@link fillPurposes}{@link selectLabels}
* 判定 `!res.ok` 時,以本函式把失敗細節寫進 WRN log,讓 CI 記錄能看出 AI CLI 為何失敗。
* 本函式未匯出,僅供模組內部使用。
*/
function agentFailureDetail(res) {
const parts = [];
const err = res && res.error;
if (err) {
if (err.killed) parts.push('已逾時終止');
if (typeof err.code === 'number') parts.push(`exit ${err.code}`);
else if (err.code) parts.push(`code ${err.code}`);
else if (err.signal) parts.push(`signal ${err.signal}`);
}
const stderr = String((res && res.stderr) || '').trim();
if (stderr) parts.push(`stderr${stderr.slice(0, 500)}`);
const stdout = String((res && res.output) || '').trim();
if (stdout) parts.push(`stdout${stdout.slice(0, 500)}`);
if (parts.length === 0) parts.push((err && err.message) || '未知錯誤');
return parts.join('');
}
/**
* 讀取工作目錄下的 `.reviewignore`,解析為忽略路徑前綴清單。
*
@@ -140,7 +172,7 @@ ${sections}
- 不得輸出個資(PII)。`;
const res = await runAgent(tool, { model, prompt, cwd, timeoutMs: 300_000 });
if (!res.ok) {
log('步驟4', 'WRN', '檔案用途摘要產生失敗,以「—」代替。');
log('步驟4', 'WRN', `檔案用途摘要產生失敗,以「—」代替${agentFailureDetail(res)}`);
return;
}
const parsed = extractJson(res.output);
@@ -279,7 +311,7 @@ async function runAttackers({ tool, model, cwd, attackers, diffRows }) {
log('步驟6', 'INF', `攻擊方 ${role.meta.name} 開始分析。`);
const res = await runAgent(tool, { model, prompt: buildAttackPrompt(role, diffRows), cwd });
if (!res.ok) {
log('步驟6', 'WRN', `攻擊方 ${role.meta.name} 執行失敗:${(res.error && res.error.message) || '未知錯誤'}`);
log('步驟6', 'WRN', `攻擊方 ${role.meta.name} 執行失敗:${agentFailureDetail(res)}`);
return [];
}
const parsed = extractJson(res.output);
@@ -459,7 +491,7 @@ async function runDefenders({ tool, model, cwd, defenders, findings }) {
});
const verdicts = new Map();
if (!res.ok) {
log('步驟8', 'WRN', `防守方 ${role.meta.name} 執行失敗,該角色視為全部保留。`);
log('步驟8', 'WRN', `防守方 ${role.meta.name} 執行失敗,該角色視為全部保留${agentFailureDetail(res)}`);
return { role: role.meta.name, verdicts };
}
const parsed = extractJson(res.output);
@@ -633,7 +665,7 @@ ${JSON.stringify(brief)}
- 沒有適合的標籤時輸出 []。`;
const res = await runAgent(tool, { model, prompt, cwd, timeoutMs: 300_000 });
if (!res.ok) {
log('建問題', 'WRN', '標籤挑選失敗,issue 不掛標籤。');
log('建問題', 'WRN', `標籤挑選失敗,issue 不掛標籤${agentFailureDetail(res)}`);
return [];
}
const parsed = extractJson(res.output);