feat(ai-code-review): 建問題模式導向 issue,並整併流程調整、文件與 CI #6
+36
-4
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user