Author SHA1 Message Date
Jeffery 44c33b6c1e chore(ai-review 狀態): 回寫本輪已處理 findings
node-actions/template: CI / BUILD (pull_request) Successful in 5s
CI / TEST (Antigravity) (pull_request) Successful in 1m1s
CI / TEST (Codex) (pull_request) Successful in 3m30s
CI / TEST (Claude) (pull_request) Successful in 24s
2026-07-21 17:21:57 +08:00
Jeffery 449b17480b fix(ai-review): 結果推送失敗時回報工作流失敗 2026-07-21 17:21:57 +08:00
8 changed files with 85 additions and 157 deletions
@@ -47,45 +47,6 @@
"suggestion": "補單元測試攔截 git 參數/env:斷言以 token 認證推送、推送目標 refspec 正確、呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。屬測試架構決策。", "suggestion": "補單元測試攔截 git 參數/env:斷言以 token 認證推送、推送目標 refspec 正確、呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。屬測試架構決策。",
"suggestedCode": "" "suggestedCode": ""
}, },
{
"id": "F005",
"reviewer": "Bard",
"focus": "style",
"badge": "🎼",
"severity": "建議",
"file": "action.yml",
"startLine": 3,
"endLine": 3,
"problem": "檔頭「更新時間」為手動維護的固定字串,與實際檔案更新時間不一致;散落各檔的手動時間戳容易走調,讀者無法判斷可信度。",
"suggestion": "屬 jsc spec-time-log 慣例(各檔頭「更新時間」由 doc-funcs 流程統一產生/同步)。是否移除改用版控紀錄、或如何統一更新,宜由 doc-funcs 流程處理,不在 resolve 逐條硬改。",
"suggestedCode": ""
},
{
"id": "F006",
"reviewer": "Bard",
"focus": "style",
"badge": "🎼",
"severity": "建議",
"file": "readme.md",
"startLine": 3,
"endLine": 3,
"problem": "README 檔頭手動「更新時間」與實際更新時間不符,並與 action.yml、src/index.js 重複保存同類易過期資訊,形成多個不一致的真相來源。",
"suggestion": "同 F005:屬 jsc spec-time-log 慣例,交 doc-funcs 流程統一維護(移除或自動注入)。",
"suggestedCode": ""
},
{
"id": "F007",
"reviewer": "Bard",
"focus": "style",
"badge": "🎼",
"severity": "建議",
"file": "src/index.js",
"startLine": 7,
"endLine": 7,
"problem": "啟動橫幅硬編碼的「更新時間」與程式實際更新時間不一致,每次改程式都要人工校準,製造噪音並讓執行日誌呈現失真版本資訊。",
"suggestion": "同 F005:屬 jsc spec-time-log 慣例,交 doc-funcs 流程統一維護;若日誌需辨識版本,可改顯示 CI 注入的 commit SHA/版本號(屬慣例調整)。",
"suggestedCode": ""
},
{ {
"id": "F008", "id": "F008",
"reviewer": "Bard", "reviewer": "Bard",
@@ -7,103 +7,7 @@
"version": "codex-cli 0.144.6", "version": "codex-cli 0.144.6",
"model": "gpt-5.5" "model": "gpt-5.5"
}, },
"findings": [ "findings": [],
{
"reviewer": "Mage",
"focus": "logic",
"badge": "🔮",
"severity": "嚴重",
"file": "src/index.js",
"startLine": 134,
"endLine": 139,
"problem": "這裡把「本輪審查」改成即使找到嚴重 finding 也回傳 0,並依賴後續 `[ai-review-bot][failure]` 結果 commit 觸發下一輪才讓檢查失敗。最小重現情境:PR 內有 1 條嚴重問題,但 `commitFindings()` 因分支保護、token 無 push 權限、遠端競態或 PAT 無法觸發 CI 而回傳 false;本輪仍成功結束,且沒有下一輪 failure commit 可被步驟 1 讀到,嚴重問題就被靜默放行。未驗證「結果 commit 一定成功且一定觸發下一輪」這個假設時,它就是流程成敗判定的單點失效。",
"suggestion": "嚴重 finding 已確認後,若 failure 結果 commit/push 沒有成功產生,就應在本輪直接回傳 1;只有在確認 failure commit 已成功推送時,才可把失敗狀態交給下一輪快速回報。也就是:`severe.length > 0 && !resultCommitPushed` 必須阻擋。",
"suggestedCode": "const result = severe.length === 0 ? 'success' : 'failure';\nlet pushed = false;\nif (filesToCommit.length > 0) {\n pushed = commitFindings({ cwd, ctx, files: filesToCommit, result });\n}\n\nif (severe.length > 0 && !pushed) {\n log('收尾', 'ERR', '已有嚴重問題,但 failure 結果 commit 未成功產生;本輪直接以失敗收場。');\n return 1;\n}\n\nreturn 0;",
"id": "F009",
"verdicts": {
"Paladin": {
"exclude": false,
"reason": "保留。此條指控嚴重 finding 依賴結果 commit 觸發下一輪才失敗,且 commit/push 失敗時本輪可能靜默通過;既有排除事項未涵蓋此成敗判定風險。"
}
}
},
{
"reviewer": "Mage",
"focus": "logic",
"badge": "🔮",
"severity": "警告",
"file": "src/index.js",
"startLine": 201,
"endLine": 209,
"problem": "`createIssueAndFlushBufferedComments()` 先建立 `trackingIssue`,再逐則寫入暫存留言;但只要其中一則留言失敗,外層 catch 會呼叫 `fallbackToPrComments()`,把整批 `pendingIssueCommentBodies` 全部改貼回 PR。最小重現情境:追蹤 issue 建立成功,第一則工具留言也成功寫入 issue,第二則 diff 留言 API 回 500;流程降級後 PR 會收到全部暫存留言,而已建立的 issue 仍殘留第一則留言、沒有後續 finding、也可能沒有 PR 連結或相依關係。這會留下部分成功、部分降級的不一致狀態。",
"suggestion": "flush 時應在每則留言成功後立刻從 pending 佇列移除,並明確處理「issue 已建立但 flush 失敗」的狀態:要嘛繼續沿用已建立 issue 並讓後續失敗冒泡,要嘛在降級前補一則 PR 診斷/連結並避免重貼已成功寫入 issue 的留言。",
"suggestedCode": "while (pendingIssueCommentBodies.length > 0) {\n const body = pendingIssueCommentBodies[0];\n await gitea.createCommentOnIssue(ctx, trackingIssue.number, body);\n pendingIssueCommentBodies.shift();\n}",
"id": "F010",
"verdicts": {
"Paladin": {
"exclude": false,
"reason": "保留。既有排除事項涵蓋 issue 建立後留言失敗導致重跑重複 issue,但本條另指出 flush 部分成功後 fallback 會把整批 pending 留言改貼 PR,造成 issue 與 PR 狀態不一致;不是單純重複。"
}
}
},
{
"reviewer": "Bard",
"focus": "style",
"badge": "🎼",
"severity": "建議",
"file": "action.yml",
"startLine": 3,
"endLine": 3,
"problem": "手動維護的「更新時間」已與本次檔案標示的最後更新時間不同步;樂譜開頭的拍號一錯,讀者後面每段註解都會多一分懷疑。同樣的不協調也出現在 `readme.md` 與 `src/index.js` 的 banner/文件時間。",
"suggestion": "移除這類容易走調的手動時間戳,或改由發布流程自動產生。若一定要保留,請讓所有檔案的時間標示與本次變更一致。",
"suggestedCode": "",
"id": "F001",
"verdicts": {
"Paladin": {
"exclude": false,
"reason": "保留。已知排除事項與歷史 findings 未涵蓋手動更新時間不同步;目前也無足夠原始碼證據可判定為誤報。"
}
}
},
{
"reviewer": "Bard",
"focus": "style",
"badge": "🎼",
"severity": "建議",
"file": "src/index.js",
"startLine": 199,
"endLine": 199,
"problem": "`createIssueAndFlushBufferedComments` 這個名稱把「建立 issue」與「flush 暫存留言」兩個實作細節硬串在一起,像一句過長的歌詞;呼叫點讀起來偏機械,沒有清楚表達業務意圖。",
"suggestion": "改成較語意化的命名,例如 `openTrackingIssue` 或 `createTrackingIssueWithContext`,讓讀者先理解目的,再從函式內容看見 flush 的細節。",
"suggestedCode": "",
"id": "F004",
"verdicts": {
"Paladin": {
"exclude": false,
"reason": "保留。此條針對 createIssueAndFlushBufferedComments 的命名語意與可讀性,未明確命中既有排除事項;與先前 ensureIssueCreated 暗示冪等的問題不完全相同。"
}
}
},
{
"reviewer": "Maya",
"focus": "testing",
"badge": "🧪",
"severity": "建議",
"file": "src/lib/gitref.js",
"startLine": 12,
"endLine": 24,
"problem": "assertSafeBranchRef 目前只測了一個正常分支與一個 ../../ 路徑穿越案例;但這個函式承擔 refspec 安全邊界,新增的空值、前後斜線、反斜線、以及 git check-ref-format 拒絕的格式都沒有案例。邊界沒被驗證時,未來調整條件很容易放過不合法 ref。",
"suggestion": "補上表格測試,至少涵蓋空字串、純空白、/feature、feature/、feature\\\\x、feature..x、feature.lock、feature@{x},並斷言錯誤訊息能區分「不安全」與「不合法 git 分支名稱」。",
"suggestedCode": "",
"id": "F014",
"verdicts": {
"Paladin": {
"exclude": false,
"reason": "保留。此條針對 assertSafeBranchRef 的 refspec 安全邊界測試不足,未見已知排除事項或歷史 findings 明確涵蓋;證據不足以排除。"
}
}
}
],
"excluded": [ "excluded": [
{ {
"reviewer": "Bard", "reviewer": "Bard",
+1 -1
View File
@@ -1,6 +1,6 @@
# ============================================================================ # ============================================================================
# 用途:定義 AI Code Review Node action 的名稱、輸入參數與 Node.js 24 進入點,供 Gitea / GitHub workflow 以 uses 引用。 # 用途:定義 AI Code Review Node action 的名稱、輸入參數與 Node.js 24 進入點,供 Gitea / GitHub workflow 以 uses 引用。
# 更新時間:2026/07/17 18:49:58 # 更新時間:2026/07/21 17:19:15
# ============================================================================ # ============================================================================
# Gitea / GitHub node action 的 manifestaction.yml): # Gitea / GitHub node action 的 manifestaction.yml):
# 定義本 action 的名稱、說明、輸入參數(inputs)與執行方式(runs), # 定義本 action 的名稱、說明、輸入參數(inputs)與執行方式(runs),
+1 -1
View File
@@ -1,6 +1,6 @@
# AI Code Review # AI Code Review
> 更新時間:2026/07/17 18:49:58 > 更新時間:2026/07/21 17:19:15
AI 多角色 code review 的 Gitea **node action**`node24`、零外部相依):以攻擊方六角色(🔮 Mage 邏輯、🗡️ Assassin 安全、⚡ Rogue 效率、🎼 Bard 風格、🧪 Maya 測試、🧰 Leo 可維護性)並行找問題、防守方(🛡️ Paladin)裁決誤報,結果留言到 PR、保存 findings,並以 bot commit 標記審查結果(`[success]``[failure]`)供下次觸發快速回報。 AI 多角色 code review 的 Gitea **node action**`node24`、零外部相依):以攻擊方六角色(🔮 Mage 邏輯、🗡️ Assassin 安全、⚡ Rogue 效率、🎼 Bard 風格、🧪 Maya 測試、🧰 Leo 可維護性)並行找問題、防守方(🛡️ Paladin)裁決誤報,結果留言到 PR、保存 findings,並以 bot commit 標記審查結果(`[success]``[failure]`)供下次觸發快速回報。
+18 -19
View File
@@ -4,7 +4,7 @@
console.log('================================================'); console.log('================================================');
console.log('Action : AI Code Review'); console.log('Action : AI Code Review');
console.log('用途 : AI 多角色 code review:攻擊方找問題、防守方裁決誤報,結果留言到 PR 並保存 findings'); console.log('用途 : AI 多角色 code review:攻擊方找問題、防守方裁決誤報,結果留言到 PR 並保存 findings');
console.log('更新時間: 2026/07/17 18:49:58'); console.log('更新時間: 2026/07/21 17:19:15');
console.log('================================================'); console.log('================================================');
const fs = require('fs'); const fs = require('fs');
@@ -126,15 +126,14 @@ function commitFindings({ cwd, ctx, files, result }) {
* 建問題模式會把審查情境與每條 finding 發到追蹤 issue;沒有保留 finding 時不建立 issue、PR 也不留言。 * 建問題模式會把審查情境與每條 finding 發到追蹤 issue;沒有保留 finding 時不建立 issue、PR 也不留言。
* 嚴重 finding 會寫入 failure 結果 commit,警告與建議只建立追蹤資訊,不直接阻擋合併。 * 嚴重 finding 會寫入 failure 結果 commit,警告與建議只建立追蹤資訊,不直接阻擋合併。
* *
* @returns {Promise<number>} process exit code本輪「審查」一律回傳 0(不因嚴重問題直接讓檢查失敗—— * @returns {Promise<number>} process exit code沒有嚴重問題且結果 commit 已成功推送時回傳 0;
* 失敗改由推出的 `[ai-review-bot][failure]` 結果 commit,於下一輪在步驟 1 讀 commit 訊息時回報); * 若步驟 1 偵測到 `[ai-review-bot][failure]`、前置條件不足,或需要推送結果檔卻未成功推送,
* 回傳 1 僅發生於:步驟 1 偵測到 `[ai-review-bot][failure]` 結果 commit,或前置條件不足 * 回傳 1,避免 token 權限不足或遠端競態讓 workflow 靜默通過。
* (缺 PR 編號/token、找不到 AI 工具)等無法進行審查的情況。
* @remarks * @remarks
* 使用情境:由本檔尾端的頂層呼叫端執行 —— `main().then((code) => process.exit(code))` * 使用情境:由本檔尾端的頂層呼叫端執行 —— `main().then((code) => process.exit(code))`
* 非預期例外由頂層 `catch` 記 ERR log 後以 exit code 1 收場,且刻意不 commit 結果標記, * 非預期例外由頂層 `catch` 記 ERR log 後以 exit code 1 收場,且刻意不 commit 結果標記,
* 讓下一次 workflow 觸發時重新完整審查。警告+建議等級不影響結果標記,只有「嚴重」會使結果 commit * 讓下一次 workflow 觸發時重新完整審查。警告+建議等級不影響結果標記,只有「嚴重」會使結果 commit
* 標記為 failure而「失敗檢查(exit 1)」只由步驟 1 讀到該 failure 結果 commit 時產生,審查本輪直接 exit 1。 * 標記為 failure若結果檔需要 push 卻失敗,本輪直接 exit 1,避免缺權限時無聲放行
* 建問題模式只改變問題明細的落地方式(issue 留言取代 findings 進版控),不改變上述結果標記判定。 * 建問題模式只改變問題明細的落地方式(issue 留言取代 findings 進版控),不改變上述結果標記判定。
*/ */
async function main() { async function main() {
@@ -193,7 +192,7 @@ async function main() {
return created; return created;
}; };
/** /**
* 建問題模式:建立追蹤 issue(標題=PR 標題、本文=PR 描述+回溯 PR 的引言,連同挑好的標籤一次建立), * 建問題模式:開啟追蹤 issue(標題=PR 標題、本文=PR 描述+回溯 PR 的引言,連同挑好的標籤一次建立),
* 並把 `pendingIssueCommentBodies` 內暫存的情境留言依流程順序寫入 issue; * 並把 `pendingIssueCommentBodies` 內暫存的情境留言依流程順序寫入 issue;
* 設定閉包變數 `trackingIssue` 供後續留言直接發到 issue。 * 設定閉包變數 `trackingIssue` 供後續留言直接發到 issue。
* 僅於「確定有保留問題」時呼叫一次。標籤於建立時一次帶入,省去「先建空標籤 issue 再補掛」的多餘 API 往返。 * 僅於「確定有保留問題」時呼叫一次。標籤於建立時一次帶入,省去「先建空標籤 issue 再補掛」的多餘 API 往返。
@@ -202,17 +201,18 @@ async function main() {
* 空陣列或省略時不掛任何標籤(`gitea.createIssue` 對空陣列不帶 labels 欄位)。 * 空陣列或省略時不掛任何標籤(`gitea.createIssue` 對空陣列不帶 labels 欄位)。
* @returns {Promise<void>} 無回傳值;結果反映在閉包變數 `trackingIssue` 與 issue 留言。 * @returns {Promise<void>} 無回傳值;結果反映在閉包變數 `trackingIssue` 與 issue 留言。
*/ */
const createIssueAndFlushBufferedComments = async (labelIds = []) => { const openTrackingIssue = async (labelIds = []) => {
trackingIssue = await gitea.createIssue(ctx, { trackingIssue = await gitea.createIssue(ctx, {
title: ctx.prTitle || `AI Code ReviewPR #${ctx.prNumber}`, title: ctx.prTitle || `AI Code ReviewPR #${ctx.prNumber}`,
body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }), body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }),
labels: labelIds, labels: labelIds,
}); });
log('建問題', 'INF', `已建立追蹤 issue #${trackingIssue.number},寫入 ${pendingIssueCommentBodies.length} 則情境留言。`); log('建問題', 'INF', `已建立追蹤 issue #${trackingIssue.number},寫入 ${pendingIssueCommentBodies.length} 則情境留言。`);
for (const body of pendingIssueCommentBodies) { while (pendingIssueCommentBodies.length > 0) {
const body = pendingIssueCommentBodies[0];
await gitea.createCommentOnIssue(ctx, trackingIssue.number, body); await gitea.createCommentOnIssue(ctx, trackingIssue.number, body);
pendingIssueCommentBodies.shift();
} }
pendingIssueCommentBodies.length = 0;
}; };
/** /**
* 建問題模式降級:追蹤 issue 無法建立或寫入時,改把已暫存的情境留言發回 PR,後續沿用一般模式。 * 建問題模式降級:追蹤 issue 無法建立或寫入時,改把已暫存的情境留言發回 PR,後續沿用一般模式。
@@ -337,7 +337,7 @@ async function main() {
log('建問題', 'WRN', `標籤挑選失敗(${err.message}),issue 不掛標籤。`); log('建問題', 'WRN', `標籤挑選失敗(${err.message}),issue 不掛標籤。`);
} }
try { try {
await createIssueAndFlushBufferedComments(labelIds); await openTrackingIssue(labelIds);
} catch (err) { } catch (err) {
log('建問題', 'WRN', `建立或寫入追蹤 issue 失敗(${err.message}),改用 PR 留言與 findings 檔流程。`); log('建問題', 'WRN', `建立或寫入追蹤 issue 失敗(${err.message}),改用 PR 留言與 findings 檔流程。`);
await fallbackToPrComments(); await fallbackToPrComments();
@@ -377,7 +377,7 @@ async function main() {
} }
// ── 建問題模式收束:在 PR 回貼 issue 連結(雙向關聯);僅在有嚴重問題時才讓 PR 相依於該 issue ─ // ── 建問題模式收束:在 PR 回貼 issue 連結(雙向關聯);僅在有嚴重問題時才讓 PR 相依於該 issue ─
// 標籤已於建立 issue 時一次帶入(見上方 selectLabels → createIssueAndFlushBufferedComments),此處不再補掛。 // 標籤已於建立 issue 時一次帶入(見上方 selectLabels → openTrackingIssue),此處不再補掛。
if (issueModeActive && trackingIssue) { if (issueModeActive && trackingIssue) {
await review.resolveOldIssueLinkComments({ ctx, gitea }); await review.resolveOldIssueLinkComments({ ctx, gitea });
await gitea.createIssueComment( await gitea.createIssueComment(
@@ -420,14 +420,13 @@ async function main() {
} else { } else {
log('收尾', 'INF', '建問題模式且 exclusions.json 無變更,略過 commit/push。'); log('收尾', 'INF', '建問題模式且 exclusions.json 無變更,略過 commit/push。');
} }
// 本輪「審查」一律以成功收場、不直接讓檢查失敗;有嚴重問題時已推出 [failure] 結果 commit // 需要推送結果檔卻沒成功時直接失敗;這通常代表 token 權限不足、非快轉或分支保護阻擋。
// 由它再觸發的下一輪在步驟 1 讀 commit 訊息時才回報失敗(exit 1)。如此失敗檢查落在帶有結果 if (review.shouldFailMissingResultCommit({ filesToCommit, resultCommitted })) {
// 標記的最新 head 上,與合併判定一致。(result 僅用於上方 commit 訊息的結果標記。) log('收尾', 'ERR', '審查結果檔需要 push 但未成功產生結果 commit;直接回報失敗避免缺權限時靜默通過。');
return 1;
}
// 嚴重問題已推出 [failure] 結果 commit 時,由它再觸發的下一輪在步驟 1 讀 commit 訊息回報失敗。
if (result === 'failure') { if (result === 'failure') {
if (!resultCommitted) {
log('收尾', 'ERR', '本輪有嚴重問題,但未成功產生 [failure] 結果 commit;直接回報失敗避免 fail-open。');
return 1;
}
log('收尾', 'INF', '本輪有嚴重問題:已標記結果 commit 為 [failure],失敗檢查由下一輪步驟 1 讀 commit 訊息回報。'); log('收尾', 'INF', '本輪有嚴重問題:已標記結果 commit 為 [failure],失敗檢查由下一輪步驟 1 讀 commit 訊息回報。');
} }
return 0; return 0;
+13
View File
@@ -579,6 +579,18 @@ function resultFilesToCommit({ createIssue, severeCount, relativePath, exclusion
return files; return files;
} }
/**
* 判斷本輪是否因結果 commit 未成功推送而必須失敗。
*
* @param {Object} params - 解構參數。
* @param {string[]} params.filesToCommit - 本輪預期寫回遠端分支的結果檔清單。
* @param {boolean} params.resultCommitted - 結果檔是否已成功 commit 並 push。
* @returns {boolean} 需要寫回結果檔卻沒有成功推送時回傳 true。
*/
function shouldFailMissingResultCommit({ filesToCommit, resultCommitted }) {
return filesToCommit.length > 0 && !resultCommitted;
}
/** /**
* 就地排序 findings:依 嚴重→警告→建議、再依檔案路徑、再依起始行遞增。 * 就地排序 findings:依 嚴重→警告→建議、再依檔案路徑、再依起始行遞增。
* *
@@ -914,6 +926,7 @@ module.exports = {
sortFindings, sortFindings,
appendExclusions, appendExclusions,
resultFilesToCommit, resultFilesToCommit,
shouldFailMissingResultCommit,
selectLabels, selectLabels,
postSevereToIssue, postSevereToIssue,
postOthersToIssue, postOthersToIssue,
+27
View File
@@ -17,6 +17,33 @@ test('assertSafeBranchRef 拒絕路徑穿越分支名稱', () => {
); );
}); });
test('assertSafeBranchRef 拒絕空白分支名稱', () => {
for (const value of ['', ' ', null, undefined]) {
assert.throws(
() => gitref.assertSafeBranchRef(value, 'baseRef'),
/baseRef 不可為空/,
);
}
});
test('assertSafeBranchRef 拒絕不安全的 refspec 分支名稱', () => {
for (const value of ['/feature', 'feature/', 'feature\\x', 'feature..x']) {
assert.throws(
() => gitref.assertSafeBranchRef(value, 'baseRef'),
/不是安全的分支名稱/,
);
}
});
test('assertSafeBranchRef 拒絕 git 不合法的分支名稱', () => {
for (const value of ['feature.lock', 'feature@{x}', 'feature~x', 'feature^x']) {
assert.throws(
() => gitref.assertSafeBranchRef(value, 'baseRef'),
/不是合法的 git 分支名稱/,
);
}
});
test('resolveMergeBase 會在 git fetch 前拒絕不安全 baseRef', () => { test('resolveMergeBase 會在 git fetch 前拒絕不安全 baseRef', () => {
assert.throws( assert.throws(
() => gitrepo.resolveMergeBase(process.cwd(), '../../hooks/pre-push'), () => gitrepo.resolveMergeBase(process.cwd(), '../../hooks/pre-push'),
+24
View File
@@ -98,6 +98,30 @@ test('resultFilesToCommit 在建問題模式無嚴重問題時只提交 exclusio
); );
}); });
test('shouldFailMissingResultCommit 在結果檔需要 push 但未成功時回報失敗', () => {
assert.equal(
review.shouldFailMissingResultCommit({
filesToCommit: ['.gitea/ai-review/findings/run.json'],
resultCommitted: false,
}),
true,
);
assert.equal(
review.shouldFailMissingResultCommit({
filesToCommit: ['.gitea/ai-review/findings/run.json'],
resultCommitted: true,
}),
false,
);
assert.equal(
review.shouldFailMissingResultCommit({
filesToCommit: [],
resultCommitted: false,
}),
false,
);
});
test('resolveOldIssueLinkComments 只標註舊追蹤 issue 連結', async () => { test('resolveOldIssueLinkComments 只標註舊追蹤 issue 連結', async () => {
const edited = []; const edited = [];
const fakeGitea = { const fakeGitea = {