Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d424447d15 | ||
|
|
9d05af647b |
@@ -59,19 +59,6 @@
|
|||||||
"problem": "推送流程新增 pushToken 分支,但沒有測試驗證兩套認證策略:有 PAT 時略過 origin、PAT 推送失敗不退回其他 token、無 PAT 時 origin 成功不重試、origin 失敗才用一般 token;也沒有案例保護含憑證資訊不出現在錯誤或測試輸出中。(本次已將認證改經 env 傳入並遮蔽 push 錯誤,測試仍待補。)",
|
"problem": "推送流程新增 pushToken 分支,但沒有測試驗證兩套認證策略:有 PAT 時略過 origin、PAT 推送失敗不退回其他 token、無 PAT 時 origin 成功不重試、origin 失敗才用一般 token;也沒有案例保護含憑證資訊不出現在錯誤或測試輸出中。(本次已將認證改經 env 傳入並遮蔽 push 錯誤,測試仍待補。)",
|
||||||
"suggestion": "mock git 執行器與 URL/env 組裝,補測 pushToken 有值/空、origin 成功/失敗、PAT 推送失敗及含特殊字元等案例;斷言 push 目標與呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。屬測試架構決策。",
|
"suggestion": "mock git 執行器與 URL/env 組裝,補測 pushToken 有值/空、origin 成功/失敗、PAT 推送失敗及含特殊字元等案例;斷言 push 目標與呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。屬測試架構決策。",
|
||||||
"suggestedCode": ""
|
"suggestedCode": ""
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "F009",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "style",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "action.yml",
|
|
||||||
"startLine": 47,
|
|
||||||
"endLine": 61,
|
|
||||||
"problem": "push-token 的用途與退回行為在區塊註解、欄位描述、required 與 default 註解中反覆說明,且單行 description 過長,資訊雖完整但重複,日後修改語意易只改到一處。",
|
|
||||||
"suggestion": "保留一段「為何需要 PAT」的必要背景,其餘讓欄位名稱、required、default 自行表意,將 description 收斂成呼叫端真正需要知道的契約。註:本專案採 doc-funcs 高密度註解慣例,是否精簡屬慣例取捨,需維護者確認。",
|
|
||||||
"suggestedCode": " # 專用推送 PAT;以 PAT 推送可重新觸發 CI。留空時沿用 token。\n push-token:\n description: '推送審查結果 commit 的 PAT(留空時沿用 token)'\n required: false\n default: ''"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"excluded": []
|
"excluded": []
|
||||||
|
|||||||
@@ -120,20 +120,6 @@
|
|||||||
"suggestedCode": "",
|
"suggestedCode": "",
|
||||||
"sourceIssue": 15
|
"sourceIssue": 15
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "F015",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/lib/gitea.js",
|
|
||||||
"startLine": 190,
|
|
||||||
"endLine": 197,
|
|
||||||
"problem": "`dependency` 作為參數名太薄,與 `issueNumber` 並列時看不出它也是 issue 編號。讀到 `addIssueDependency(ctx, ctx.prNumber, issue.number)` 時,語意要靠上下文補拍子。",
|
|
||||||
"suggestion": "改用更完整的名稱,例如 `dependencyIssueNumber` 或 `blockingIssueNumber`,讓「誰被誰阻擋」在簽名裡就清楚成形。",
|
|
||||||
"suggestedCode": "```\nfunction addIssueDependency(ctx, issueNumber, dependencyIssueNumber) {\n return api(ctx, 'POST', `/repos/${ctx.owner}/${ctx.repo}/issues/${issueNumber}/dependencies`, {\n index: dependencyIssueNumber,\n owner: ctx.owner,\n repo: ctx.repo,\n });\n}\n```",
|
|
||||||
"sourceIssue": 15
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "F022",
|
"id": "F022",
|
||||||
"reviewer": "Leo",
|
"reviewer": "Leo",
|
||||||
@@ -148,34 +134,6 @@
|
|||||||
"suggestedCode": "",
|
"suggestedCode": "",
|
||||||
"sourceIssue": 18
|
"sourceIssue": 18
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "F023",
|
|
||||||
"reviewer": "Mage",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🔮",
|
|
||||||
"severity": "警告",
|
|
||||||
"file": "src/index.js",
|
|
||||||
"startLine": 374,
|
|
||||||
"endLine": 387,
|
|
||||||
"problem": "建問題模式下只要有任何保留 finding 就會建立 issue,且後續一律把 PR 設為相依於該 issue;但收尾結果仍是 `severe.length === 0 ? 'success' : 'failure'`。最小情境:攻擊方只產生 1 條「建議」,`severe.length` 為 0,action commit `[success]`,但 PR 被 issue dependency 擋住無法合併。這讓「success=可通過」與「非嚴重問題也阻擋合併」兩個語義互相矛盾。",
|
|
||||||
"suggestion": "明確對齊語義:若只有嚴重問題才應阻擋合併,則只在 `severe.length > 0` 時建立 dependency;若所有保留問題都要阻擋合併,則 result/exit code 不應只看嚴重問題。",
|
|
||||||
"suggestedCode": "```\nif (severe.length > 0) {\n try {\n await gitea.addIssueDependency(ctx, ctx.prNumber, issue.number);\n log('建問題', 'INF', `已將 PR #${ctx.prNumber} 設為相依於 issue #${issue.number}。`);\n } catch (err) {\n log('建問題', 'WRN', `設定 PR 相依失敗(可能未啟用「問題相依」功能):${err.message}。`);\n }\n}\n```",
|
|
||||||
"sourceIssue": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "F024",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/index.js",
|
|
||||||
"startLine": 179,
|
|
||||||
"endLine": 181,
|
|
||||||
"problem": "`issueBuffer` 的旋律太含糊:讀者會以為裡面放的是 issue,實際上暫存的是尚未送出的留言 body。命名沒有把資料形狀唱清楚。",
|
|
||||||
"suggestion": "改成能描述內容與用途的名稱,例如 `pendingIssueCommentBodies`,並同步調整註解與迴圈變數。",
|
|
||||||
"suggestedCode": "```\nconst pendingIssueCommentBodies = [];\nlet issue = null;\n```",
|
|
||||||
"sourceIssue": 18
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "F025",
|
"id": "F025",
|
||||||
"reviewer": "Assassin",
|
"reviewer": "Assassin",
|
||||||
@@ -204,34 +162,6 @@
|
|||||||
"suggestedCode": "",
|
"suggestedCode": "",
|
||||||
"sourceIssue": 19
|
"sourceIssue": 19
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "F027",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/index.js",
|
|
||||||
"startLine": 181,
|
|
||||||
"endLine": 185,
|
|
||||||
"problem": "`issue` 這個變數名太素,像樂譜上只寫「音符」卻不說是哪一聲部。此處承載的是建問題模式建立出的追蹤 issue,後面還會與 PR issue 編號、Gitea issue API 參數交錯出現,名稱過泛會讓閱讀節奏變濁。",
|
|
||||||
"suggestion": "改成能表明角色的名稱,例如 `trackingIssue`。對應的 `ensureIssueCreated` 也可改為 `ensureTrackingIssueCreated`,讓閉包狀態與用途一眼對上。",
|
|
||||||
"suggestedCode": "```\nconst issueBuffer = [];\nlet trackingIssue = null;\n\nconst postComment = async (body) => {\n if (ctx.createIssue) {\n if (trackingIssue) return gitea.createCommentOnIssue(ctx, trackingIssue.number, body);\n issueBuffer.push(body);\n return null;\n }\n const created = await gitea.createIssueComment(ctx, body);\n currentRunCommentIds.add(created.id);\n return created;\n};\n```",
|
|
||||||
"sourceIssue": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "F029",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/lib/templates.js",
|
|
||||||
"startLine": 388,
|
|
||||||
"endLine": 405,
|
|
||||||
"problem": "`issueLinkComment` 產生的是 PR 上唯一的建問題模式回貼留言,但名稱少了 PR 的聲部;同檔已有 `issueBody`、`issueFindingComment`,乍看會以為這也是 issue 內留言模板,命名層次不夠分明。",
|
|
||||||
"suggestion": "改名為 `prIssueLinkComment` 或 `trackingIssueLinkComment`,讓模板的投遞位置與用途直接寫在名稱裡,避免與 issue 內文、issue finding 留言混成一團。",
|
|
||||||
"suggestedCode": "```\nfunction prIssueLinkComment({ issueNumber, issueUrl, severeCount, otherCount }) {\n return `${MARK}\n## 🔍 AI Code Review|已建立追蹤問題\n\n本次審查結果已彙整到 issue [#${issueNumber}](${issueUrl})(🔴 嚴重 ${severeCount} 條、🟠🔵 警告+建議 ${otherCount} 條),請至該問題追蹤與討論。`;\n}\n```",
|
|
||||||
"sourceIssue": 19
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "F031",
|
"id": "F031",
|
||||||
"reviewer": "Leo",
|
"reviewer": "Leo",
|
||||||
@@ -246,34 +176,6 @@
|
|||||||
"suggestedCode": "",
|
"suggestedCode": "",
|
||||||
"sourceIssue": 21
|
"sourceIssue": 21
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "F032",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/index.js",
|
|
||||||
"startLine": 183,
|
|
||||||
"endLine": 224,
|
|
||||||
"problem": "`issue` 與 `issueBuffer` 的命名過於泛泛;在 Gitea 裡 PR 也是 issue,追蹤問題也是 issue,單靠 `issue` 這個名字無法唱出它究竟是哪一個聲部。",
|
|
||||||
"suggestion": "建議改成更具語義的名稱,例如 `trackingIssue`、`trackingIssueCommentBuffer`,讓讀者不用回讀 create-issue 模式的整段脈絡。",
|
|
||||||
"suggestedCode": "```\nlet trackingIssue = null;\nconst trackingIssueCommentBuffer = [];\n```",
|
|
||||||
"sourceIssue": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "F033",
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/lib/gitea.js",
|
|
||||||
"startLine": 190,
|
|
||||||
"endLine": 194,
|
|
||||||
"problem": "`addIssueDependency(ctx, issueNumber, dependency)` 的兩個參數名稱太相似,且 `dependency` 少了 issue 語義。這支 API 的方向性本來就容易讀錯,命名再模糊就像兩個音符共用同一個名字。",
|
|
||||||
"suggestion": "建議把參數改成能表達方向的名稱,例如 `blockedIssueNumber` 與 `blockingIssueNumber`,呼叫端也會更清楚是誰被誰擋住。",
|
|
||||||
"suggestedCode": "```\nfunction addIssueDependency(ctx, blockedIssueNumber, blockingIssueNumber) {\n return api(ctx, 'POST', `/repos/${ctx.owner}/${ctx.repo}/issues/${blockedIssueNumber}/dependencies`, {\n index: blockingIssueNumber,\n owner: ctx.owner,\n repo: ctx.repo,\n });\n}\n```",
|
|
||||||
"sourceIssue": 21
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "F040",
|
"id": "F040",
|
||||||
"reviewer": "Assassin",
|
"reviewer": "Assassin",
|
||||||
|
|||||||
@@ -7,141 +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": 149,
|
|
||||||
"endLine": 153,
|
|
||||||
"problem": "這個變更把「本輪審查」改成即使有嚴重問題也回傳 0,並假設後續由 `[ai-review-bot][failure]` 結果 commit 觸發下一輪 CI 再失敗。最小重現:workflow 仍依 action.yml 常見用法傳入自動 token(或任一不會觸發 synchronize CI 的 token)→ AI 找到 1 條嚴重問題 → 本輪成功建立留言與 push 結果 commit,但該 push 不觸發下一輪 → 沒有任何檢查讀到 `[failure]` commit,PR 最終呈現通過。這是未驗證的外部時序假設;嚴重 finding 會被靜默放行。",
|
|
||||||
"suggestion": "不要讓失敗判定完全依賴下一輪 CI。若 `severe.length > 0`,本輪在完成留言與結果落地後仍應回傳 1;或至少提供明確 input 控制 direct-fail,並在無法驗證 token 會觸發 CI 時預設直接 fail。",
|
|
||||||
"suggestedCode": "",
|
|
||||||
"id": "F009",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。此條指控嚴重 finding 需仰賴下一輪 CI 才失敗,且自動 token 可能不觸發 CI,與既有 pushToken 測試缺口或 PAT 風險 finding 不屬同一處同一失效模式;未能排除。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reviewer": "Mage",
|
|
||||||
"focus": "logic",
|
|
||||||
"badge": "🔮",
|
|
||||||
"severity": "警告",
|
|
||||||
"file": "src/lib/review.js",
|
|
||||||
"startLine": 707,
|
|
||||||
"endLine": 718,
|
|
||||||
"problem": "`postOthersToIssue` 以並行方式送出多則 issue 留言時,issue 上的實際留言順序取決於 API 回應與資料庫寫入完成順序,不保證等於已排序的 findings 順序。最小重現:兩條建議分別位於 `a.js:10` 與 `a.js:20`,第二個 API 較快完成,issue 會先出現第 20 行問題,再出現第 10 行問題;使用者逐檔逐行處理時順序會錯亂。",
|
|
||||||
"suggestion": "若 issue 留言順序是介面契約,逐則 `await gitea.createCommentOnIssue(...)` 發送;若要保留並行,需在每則留言標題加入穩定序號,例如 `2/5`,讓非同步完成不破壞閱讀順序。",
|
|
||||||
"suggestedCode": "",
|
|
||||||
"id": "F011",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。歷史 findings 未涵蓋 postOthersToIssue 並行送出導致 issue 留言順序不穩定;目前也無排除事項可套用,故保留。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "style",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "action.yml",
|
|
||||||
"startLine": 22,
|
|
||||||
"endLine": 24,
|
|
||||||
"problem": "manifest 的註解忽然奏起一整段實作細節:PAT、CI 觸發、主程式步驟 1 全擠在 input 說明旁,和同檔其他「介面用途」型註解的節奏不一致。讀者只是想知道 token 該填什麼,卻被迫聽完流程旁白。",
|
|
||||||
"suggestion": "把 action.yml 留給介面契約;細節移到 README 或主流程文件。此處可濃縮成「建議使用可觸發 CI 的 PAT」即可。",
|
|
||||||
"suggestedCode": "# 建議傳入能觸發 CI 的 PAT;自動 token 推送結果 commit 時可能不會再觸發 workflow。",
|
|
||||||
"id": "F002",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。已知排除事項與歷史 findings 主要涵蓋 push-token 說明重複或 PAT 安全風險;本條指向 action.yml 中 token input 註解混入主流程步驟細節,並非同一處同一問題,未能排除。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "style",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "readme.md",
|
|
||||||
"startLine": 40,
|
|
||||||
"endLine": 47,
|
|
||||||
"problem": "mermaid 圖的節點 ID 旋律走岔了:畫面標示是 3→4→5→...→8→2→9,但節點名稱卻用 S3、S4、S2 來回跳。即使流程語意想表達「步驟 2 延後」,節點 ID 與視覺順序不一致,會讓維護者在對照圖與文字時多繞一圈。",
|
|
||||||
"suggestion": "讓節點 ID 維持閱讀順序,將真正的流程步驟放在節點文字裡。例如用 N2、N3 或 A、B 這類中性 ID,避免 S2 看起來像應該排在 S1 後面。",
|
|
||||||
"suggestedCode": "S1 -->|未命中| N3[3 偵測 AI 工具並留言]\n N3 --> N4[4 讀 .reviewignore 整理 diff 並留言]\n N4 --> N5[5 攻擊方登場留言]\n N5 --> N6[6 攻擊方 sub agent 並行找問題]\n N6 --> N7[7 防守方登場留言]\n N7 --> N8[8 防守方裁決 → 保存 findings + 誤判回寫 exclusions.json]\n N8 --> N2[2 延後將舊留言標記解決(成功產生結果後才執行)]\n N2 --> S9[9 嚴重問題逐條掛行留言]",
|
|
||||||
"id": "F003",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。歷史 findings 雖有流程步驟硬編碼與文件同步成本問題,但本條聚焦 mermaid 節點 ID 與視覺流程順序不一致,屬不同可讀性指控,未命中既有排除。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "style",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/index.js",
|
|
||||||
"startLine": 122,
|
|
||||||
"endLine": 139,
|
|
||||||
"problem": "`main()` 的 JSDoc 從函式說明變成流程章回。步驟、例外模式、issue 建立時機、相依關係、commit 規則全塞在同一段,和程式下方已存在的分段註解重複,讀起來像同一旋律被兩把琴同時彈奏。",
|
|
||||||
"suggestion": "JSDoc 保留函式職責、回傳值與關鍵模式差異即可;完整 10 步驟流程交給 README 或下方區塊註解。這會讓 `main()` 開頭更快進入正題。",
|
|
||||||
"suggestedCode": "",
|
|
||||||
"id": "F004",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。歷史 findings 有步驟編號散落與 main() 內閉包 JSDoc 過重等問題,但本條指向 main() 函式 JSDoc 與下方流程註解重複,範圍與主張不完全相同,證據不足以排除。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "style",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "src/lib/diagnostics.js",
|
|
||||||
"startLine": 45,
|
|
||||||
"endLine": 47,
|
|
||||||
"problem": "`res` 這個參數名太短促,和檔內其他 `text`、`parts`、`stderr`、`stdout` 這些直白命名相比顯得含糊。診斷工具本該讓人少猜一點,這裡卻讓讀者先猜它是哪一種 result。",
|
|
||||||
"suggestion": "改用 `result` 或 `agentResult`,讓函式簽名本身就說清楚資料來源。",
|
|
||||||
"suggestedCode": "function agentFailureDetail(agentResult) {\n const parts = [];\n const err = agentResult && agentResult.error;",
|
|
||||||
"id": "F005",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。未見已知排除事項或歷史 finding 涵蓋 diagnostics.js 中 res 參數命名過短的問題;屬新的命名可讀性指控。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reviewer": "Bard",
|
|
||||||
"focus": "style",
|
|
||||||
"badge": "🎼",
|
|
||||||
"severity": "建議",
|
|
||||||
"file": "test/gitea.test.js",
|
|
||||||
"startLine": 8,
|
|
||||||
"endLine": 8,
|
|
||||||
"problem": "`fn` 是一個太倉促的縮寫,放在測試輔助函式裡尤其刺耳;同一行已有 `handler` 這種完整命名,`fn` 顯得像漏拍的音符。",
|
|
||||||
"suggestion": "改成 `callback` 或 `run`,讓呼叫意圖更清楚,也和此專案偏完整語意的命名風格一致。",
|
|
||||||
"suggestedCode": "function withFetchStub(handler, callback) {",
|
|
||||||
"id": "F006",
|
|
||||||
"verdicts": {
|
|
||||||
"Paladin": {
|
|
||||||
"exclude": false,
|
|
||||||
"reason": "保留。未見已知排除事項或歷史 finding 涵蓋 test/gitea.test.js 中 fn 測試輔助參數命名問題;屬新的命名可讀性指控。"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"excluded": [
|
"excluded": [
|
||||||
{
|
{
|
||||||
"reviewer": "Assassin",
|
"reviewer": "Assassin",
|
||||||
|
|||||||
+1
-3
@@ -19,9 +19,7 @@ inputs:
|
|||||||
# Gitea API token:用於對 PR/issue 留言審查結果,以及 push 審查結果檔(findings/exclusions)回 repo。
|
# Gitea API token:用於對 PR/issue 留言審查結果,以及 push 審查結果檔(findings/exclusions)回 repo。
|
||||||
token:
|
token:
|
||||||
# 參數用途說明:secrets/vars context 在 action 內不可用,故由呼叫端 workflow 以 secrets 傳入。
|
# 參數用途說明:secrets/vars context 在 action 內不可用,故由呼叫端 workflow 以 secrets 傳入。
|
||||||
# 建議傳入「能觸發 CI 的 PAT」:以自動 token(gitea.token / GITHUB_TOKEN)推送的結果 commit 不會
|
# 建議傳入能觸發 CI 的 PAT;自動 token 推送結果 commit 時可能不會再觸發 workflow。
|
||||||
# 再觸發 CI,導致新 head 缺檢查而卡合併;改用 PAT 推送會讓 PR 的 synchronize 事件再觸發 CI,
|
|
||||||
# 由主程式步驟 1 快速回報([success]/[failure])廉價地把結果蓋到新 head。
|
|
||||||
description: 'Gitea API token(PR/issue 留言與 push findings 用;建議以能觸發 CI 的 PAT 由 secrets 傳入)'
|
description: 'Gitea API token(PR/issue 留言與 push findings 用;建議以能觸發 CI 的 PAT 由 secrets 傳入)'
|
||||||
# 必填:缺少 token 無法呼叫 Gitea API,action 無法運作。
|
# 必填:缺少 token 無法呼叫 Gitea API,action 無法運作。
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -38,16 +38,16 @@ jobs:
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
S1[1 判斷 bot commit 標記] -->|命中| E0[直接回報 success/failure]
|
S1[1 判斷 bot commit 標記] -->|命中| E0[直接回報 success/failure]
|
||||||
S1 -->|未命中| S3[3 偵測 AI 工具並留言]
|
S1 -->|未命中| N3[3 偵測 AI 工具並留言]
|
||||||
S3 --> S4[4 讀 .reviewignore 整理 diff 並留言]
|
N3 --> N4[4 讀 .reviewignore 整理 diff 並留言]
|
||||||
S4 --> S5[5 攻擊方登場留言]
|
N4 --> N5[5 攻擊方登場留言]
|
||||||
S5 --> S6[6 攻擊方 sub agent 並行找問題]
|
N5 --> N6[6 攻擊方 sub agent 並行找問題]
|
||||||
S6 --> S7[7 防守方登場留言]
|
N6 --> N7[7 防守方登場留言]
|
||||||
S7 --> S8[8 防守方裁決 → 保存 findings + 誤判回寫 exclusions.json]
|
N7 --> N8[8 防守方裁決 → 保存 findings + 誤判回寫 exclusions.json]
|
||||||
S8 --> S2[2 延後將舊留言標記解決(成功產生結果後才執行)]
|
N8 --> N2[2 延後將舊留言標記解決(成功產生結果後才執行)]
|
||||||
S2 --> S9[9 嚴重問題逐條掛行留言]
|
N2 --> N9[9 嚴重問題逐條掛行留言]
|
||||||
S9 --> S10[10 警告+建議彙整表格留言]
|
N9 --> N10[10 警告+建議彙整表格留言]
|
||||||
S10 --> E1[收尾 commit/push + exit code]
|
N10 --> E1[收尾 commit/push + exit code]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 專案列表
|
## 專案列表
|
||||||
|
|||||||
+29
-48
@@ -117,31 +117,11 @@ function commitFindings({ cwd, ctx, files, result }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI code review 主流程:依固定 10 步驟執行多角色審查,回傳 process exit code。
|
* AI code review 主流程:編排多角色審查、發布審查結果,並回傳 process exit code。
|
||||||
*
|
*
|
||||||
* 流程概要(步驟 2~10 描述一般模式;建問題模式差異見末段):
|
* 一般模式會把審查情境、嚴重問題與警告/建議發布到 PR,並在成功產生本回合結果後才把舊留言標為過時。
|
||||||
* 1. 快速回報 — 最新 commit 若為 ai-review-bot 的結果 commit([success]/[failure]),直接回報 0/1 不重審;
|
* 建問題模式會把審查情境與每條 finding 發到追蹤 issue;沒有保留 finding 時不建立 issue、PR 也不留言。
|
||||||
* 2. 將 PR 既有舊留言標記為解決(跳過本回合留言;建問題模式不執行此步)——
|
* 嚴重 finding 會寫入 failure 結果 commit,警告與建議只建立追蹤資訊,不直接阻擋合併。
|
||||||
* 此步延後到「本回合審查已成功產生結果、即將發布問題留言前」才執行,避免工具偵測/diff/
|
|
||||||
* 攻防裁決任一失敗時舊結果先被清掉卻沒有新結果(一般模式);
|
|
||||||
* 3. 偵測 AI 工具(antigravity/codex/claude)並留言;
|
|
||||||
* 4. 讀 .reviewignore、整理 git diff 並留言(無可審查變更時:留言+保存空 findings,
|
|
||||||
* 一般模式 commit success、建問題模式略過 commit,回傳 0);
|
|
||||||
* 5–6. 攻擊方登場留言、每位攻擊方一個 sub agent 並行找問題;
|
|
||||||
* 7–8. 防守方登場留言、裁決誤報後排序並保存 findings JSON,
|
|
||||||
* 並以 appendExclusions 把誤判/重複問題回寫 .gitea/ai-review/exclusions.json;
|
|
||||||
* 9. 嚴重問題逐條掛在程式碼行上留言;
|
|
||||||
* 10. 警告+建議彙整為單一表格留言;
|
|
||||||
* 建問題模式(input: create-issue):不執行步驟 2、不觸碰 PR 既有留言;步驟 3~10 的所有留言
|
|
||||||
* 改發到追蹤 issue(工具/diff/角色留言先暫存,確定有保留問題後先挑好標籤、連同標籤一次建立 issue
|
|
||||||
* 並寫入暫存留言,嚴重問題與警告+建議再逐條發到該 issue,讓每條問題都能被個別回覆);
|
|
||||||
* 無保留問題或無可審查變更則不建 issue、PR 也完全不留言(靜默通過);
|
|
||||||
* 收束時在 PR 回貼 issue 連結形成雙向關聯,
|
|
||||||
* 並「僅在有嚴重問題時」讓 PR 相依於該 issue(addIssueDependency,issue 關閉前 PR 無法合併;
|
|
||||||
* 需 repo 啟用問題相依功能)——僅有警告/建議時 issue 仍建立供追蹤,但不阻擋合併;
|
|
||||||
* 收尾:組 filesToCommit —— 一般模式 commit findings 檔(+有變更的 exclusions.json)、
|
|
||||||
* 建問題模式只 commit exclusions.json、無檔案可 commit 時略過;
|
|
||||||
* commit 訊息帶結果標記(success=無嚴重問題、failure=有嚴重問題)。
|
|
||||||
*
|
*
|
||||||
* @returns {Promise<number>} process exit code:本輪「審查」一律回傳 0(不因嚴重問題直接讓檢查失敗——
|
* @returns {Promise<number>} process exit code:本輪「審查」一律回傳 0(不因嚴重問題直接讓檢查失敗——
|
||||||
* 失敗改由推出的 `[ai-review-bot][failure]` 結果 commit,於下一輪在步驟 1 讀 commit 訊息時回報);
|
* 失敗改由推出的 `[ai-review-bot][failure]` 結果 commit,於下一輪在步驟 1 讀 commit 訊息時回報);
|
||||||
@@ -182,14 +162,14 @@ async function main() {
|
|||||||
|
|
||||||
// 本回合(一般模式)發出的 PR 留言 id:resolveOldComments 標註過時時要跳過這些。
|
// 本回合(一般模式)發出的 PR 留言 id:resolveOldComments 標註過時時要跳過這些。
|
||||||
const currentRunCommentIds = new Set();
|
const currentRunCommentIds = new Set();
|
||||||
// 建問題模式:issue 於「確定有保留問題」後才建立;在那之前的情境留言(工具/diff/角色)
|
// 建問題模式:追蹤 issue 於「確定有保留問題」後才建立;在那之前的情境留言(工具/diff/角色)
|
||||||
// 先暫存於 issueBuffer,建立 issue 後一次寫入。
|
// 先暫存於 pendingIssueCommentBodies,建立 issue 後一次寫入。
|
||||||
const issueBuffer = [];
|
const pendingIssueCommentBodies = [];
|
||||||
let issue = null;
|
let trackingIssue = null;
|
||||||
/**
|
/**
|
||||||
* 發布一則審查留言。依模式決定去向:
|
* 發布一則審查留言。依模式決定去向:
|
||||||
* - 一般模式:發到 PR,並記錄留言 id 供 `resolveOldComments` 排除。
|
* - 一般模式:發到 PR,並記錄留言 id 供 `resolveOldComments` 排除。
|
||||||
* - 建問題模式:issue 已建立時發到 issue;尚未建立時先暫存到 `issueBuffer`。
|
* - 建問題模式:追蹤 issue 已建立時發到 issue;尚未建立時先暫存到 `pendingIssueCommentBodies`。
|
||||||
*
|
*
|
||||||
* @param {string} body 要發布的 Markdown 留言內容。
|
* @param {string} body 要發布的 Markdown 留言內容。
|
||||||
* @returns {Promise<Object|null>} 一般模式、或建問題模式且 issue 已建立時回傳 Gitea 留言物件;
|
* @returns {Promise<Object|null>} 一般模式、或建問題模式且 issue 已建立時回傳 Gitea 留言物件;
|
||||||
@@ -200,8 +180,8 @@ async function main() {
|
|||||||
*/
|
*/
|
||||||
const queueOrPostComment = async (body) => {
|
const queueOrPostComment = async (body) => {
|
||||||
if (ctx.createIssue) {
|
if (ctx.createIssue) {
|
||||||
if (issue) return gitea.createCommentOnIssue(ctx, issue.number, body);
|
if (trackingIssue) return gitea.createCommentOnIssue(ctx, trackingIssue.number, body);
|
||||||
issueBuffer.push(body);
|
pendingIssueCommentBodies.push(body);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const created = await gitea.createIssueComment(ctx, body);
|
const created = await gitea.createIssueComment(ctx, body);
|
||||||
@@ -210,24 +190,25 @@ async function main() {
|
|||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 建問題模式:建立追蹤 issue(標題=PR 標題、本文=PR 描述+回溯 PR 的引言,連同挑好的標籤一次建立),
|
* 建問題模式:建立追蹤 issue(標題=PR 標題、本文=PR 描述+回溯 PR 的引言,連同挑好的標籤一次建立),
|
||||||
* 並把 `issueBuffer` 內暫存的情境留言依流程順序寫入 issue;設定閉包變數 `issue` 供後續留言直接發到 issue。
|
* 並把 `pendingIssueCommentBodies` 內暫存的情境留言依流程順序寫入 issue;
|
||||||
|
* 設定閉包變數 `trackingIssue` 供後續留言直接發到 issue。
|
||||||
* 僅於「確定有保留問題」時呼叫一次。標籤於建立時一次帶入,省去「先建空標籤 issue 再補掛」的多餘 API 往返。
|
* 僅於「確定有保留問題」時呼叫一次。標籤於建立時一次帶入,省去「先建空標籤 issue 再補掛」的多餘 API 往返。
|
||||||
*
|
*
|
||||||
* @param {number[]} [labelIds] - 建立 issue 時要一併掛上的標籤 id 陣列(由 `review.selectLabels` 事先挑選);
|
* @param {number[]} [labelIds] - 建立 issue 時要一併掛上的標籤 id 陣列(由 `review.selectLabels` 事先挑選);
|
||||||
* 空陣列或省略時不掛任何標籤(`gitea.createIssue` 對空陣列不帶 labels 欄位)。
|
* 空陣列或省略時不掛任何標籤(`gitea.createIssue` 對空陣列不帶 labels 欄位)。
|
||||||
* @returns {Promise<void>} 無回傳值;結果反映在閉包變數 `issue` 與 issue 留言。
|
* @returns {Promise<void>} 無回傳值;結果反映在閉包變數 `trackingIssue` 與 issue 留言。
|
||||||
*/
|
*/
|
||||||
const createIssueAndFlushBufferedComments = async (labelIds = []) => {
|
const createIssueAndFlushBufferedComments = async (labelIds = []) => {
|
||||||
issue = await gitea.createIssue(ctx, {
|
trackingIssue = await gitea.createIssue(ctx, {
|
||||||
title: ctx.prTitle || `AI Code Review:PR #${ctx.prNumber}`,
|
title: ctx.prTitle || `AI Code Review:PR #${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 #${issue.number},寫入 ${issueBuffer.length} 則情境留言。`);
|
log('建問題', 'INF', `已建立追蹤 issue #${trackingIssue.number},寫入 ${pendingIssueCommentBodies.length} 則情境留言。`);
|
||||||
for (const body of issueBuffer) {
|
for (const body of pendingIssueCommentBodies) {
|
||||||
await gitea.createCommentOnIssue(ctx, issue.number, body);
|
await gitea.createCommentOnIssue(ctx, trackingIssue.number, body);
|
||||||
}
|
}
|
||||||
issueBuffer.length = 0;
|
pendingIssueCommentBodies.length = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── 步驟 2:延後執行 ───────────────────────────────────────────────────
|
// ── 步驟 2:延後執行 ───────────────────────────────────────────────────
|
||||||
@@ -355,7 +336,7 @@ async function main() {
|
|||||||
// ── 步驟 9:嚴重問題留言(一般模式掛在 PR 程式碼行上;建問題模式逐條發到 issue)─
|
// ── 步驟 9:嚴重問題留言(一般模式掛在 PR 程式碼行上;建問題模式逐條發到 issue)─
|
||||||
if (severe.length > 0) {
|
if (severe.length > 0) {
|
||||||
if (ctx.createIssue) {
|
if (ctx.createIssue) {
|
||||||
await review.postSevereToIssue({ ctx, gitea, issueNumber: issue.number, severe });
|
await review.postSevereToIssue({ ctx, gitea, issueNumber: trackingIssue.number, severe });
|
||||||
} else {
|
} else {
|
||||||
await review.postSevereComments({ ctx, gitea, severe, cwd });
|
await review.postSevereComments({ ctx, gitea, severe, cwd });
|
||||||
}
|
}
|
||||||
@@ -365,7 +346,7 @@ async function main() {
|
|||||||
// 建問題模式逐條發到 issue,讓每條問題都能被個別回覆。 ──
|
// 建問題模式逐條發到 issue,讓每條問題都能被個別回覆。 ──
|
||||||
if (others.length > 0) {
|
if (others.length > 0) {
|
||||||
if (ctx.createIssue) {
|
if (ctx.createIssue) {
|
||||||
await review.postOthersToIssue({ ctx, gitea, issueNumber: issue.number, others });
|
await review.postOthersToIssue({ ctx, gitea, issueNumber: trackingIssue.number, others });
|
||||||
} else {
|
} else {
|
||||||
await queueOrPostComment(templates.othersComment(others));
|
await queueOrPostComment(templates.othersComment(others));
|
||||||
log('步驟10', 'INF', `警告+建議表格留言已發布(${others.length} 條)。`);
|
log('步驟10', 'INF', `警告+建議表格留言已發布(${others.length} 條)。`);
|
||||||
@@ -374,12 +355,12 @@ async function main() {
|
|||||||
|
|
||||||
// ── 建問題模式收束:在 PR 回貼 issue 連結(雙向關聯);僅在有嚴重問題時才讓 PR 相依於該 issue ─
|
// ── 建問題模式收束:在 PR 回貼 issue 連結(雙向關聯);僅在有嚴重問題時才讓 PR 相依於該 issue ─
|
||||||
// 標籤已於建立 issue 時一次帶入(見上方 selectLabels → createIssueAndFlushBufferedComments),此處不再補掛。
|
// 標籤已於建立 issue 時一次帶入(見上方 selectLabels → createIssueAndFlushBufferedComments),此處不再補掛。
|
||||||
if (ctx.createIssue && issue) {
|
if (ctx.createIssue && trackingIssue) {
|
||||||
await gitea.createIssueComment(
|
await gitea.createIssueComment(
|
||||||
ctx,
|
ctx,
|
||||||
templates.issueLinkComment({
|
templates.prIssueLinkComment({
|
||||||
issueNumber: issue.number,
|
issueNumber: trackingIssue.number,
|
||||||
issueUrl: issue.html_url,
|
issueUrl: trackingIssue.html_url,
|
||||||
severeCount: severe.length,
|
severeCount: severe.length,
|
||||||
otherCount: others.length,
|
otherCount: others.length,
|
||||||
}),
|
}),
|
||||||
@@ -388,15 +369,15 @@ async function main() {
|
|||||||
// 僅有警告/建議時,issue 仍建立供追蹤,但不掛相依、不阻擋 PR 合併。
|
// 僅有警告/建議時,issue 仍建立供追蹤,但不掛相依、不阻擋 PR 合併。
|
||||||
if (severe.length > 0) {
|
if (severe.length > 0) {
|
||||||
try {
|
try {
|
||||||
await gitea.addIssueDependency(ctx, ctx.prNumber, issue.number);
|
await gitea.addIssueDependency(ctx, ctx.prNumber, trackingIssue.number);
|
||||||
log('建問題', 'INF', `有嚴重問題:已將 PR #${ctx.prNumber} 設為相依於 issue #${issue.number},issue 關閉前無法合併。`);
|
log('建問題', 'INF', `有嚴重問題:已將 PR #${ctx.prNumber} 設為相依於 issue #${trackingIssue.number},issue 關閉前無法合併。`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log('建問題', 'WRN', `設定 PR 相依失敗(可能未啟用「問題相依」功能):${err.message}。`);
|
log('建問題', 'WRN', `設定 PR 相依失敗(可能未啟用「問題相依」功能):${err.message}。`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log('建問題', 'INF', `無嚴重問題(僅警告/建議):issue #${issue.number} 僅供追蹤,不阻擋 PR 合併。`);
|
log('建問題', 'INF', `無嚴重問題(僅警告/建議):issue #${trackingIssue.number} 僅供追蹤,不阻擋 PR 合併。`);
|
||||||
}
|
}
|
||||||
log('建問題', 'INF', `issue #${issue.number} 已寫入審查內容,並在 PR 回貼連結。`);
|
log('建問題', 'INF', `issue #${trackingIssue.number} 已寫入審查內容,並在 PR 回貼連結。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 收尾:commit 並 push(success=無嚴重問題、failure=有嚴重問題)───────
|
// ── 收尾:commit 並 push(success=無嚴重問題、failure=有嚴重問題)───────
|
||||||
|
|||||||
@@ -37,13 +37,13 @@ function redactSecrets(text) {
|
|||||||
* 訊號與逾時狀態;只有 `ACTIONS_STEP_DEBUG=true` 時才附上經 {@link redactSecrets}
|
* 訊號與逾時狀態;只有 `ACTIONS_STEP_DEBUG=true` 時才附上經 {@link redactSecrets}
|
||||||
* 遮罩且去除控制字元的 stderr/stdout 片段。
|
* 遮罩且去除控制字元的 stderr/stdout 片段。
|
||||||
*
|
*
|
||||||
* @param {{error: (Error & {code?: number|string, signal?: string, killed?: boolean})|null, stderr?: string, output?: string}} res
|
* @param {{error: (Error & {code?: number|string, signal?: string, killed?: boolean})|null, stderr?: string, output?: string}} agentResult
|
||||||
* `runAgent` 的回傳物件。
|
* `runAgent` 的回傳物件。
|
||||||
* @returns {string} 單行診斷摘要(各段以「|」分隔);無任何資訊時回傳固定字串。
|
* @returns {string} 單行診斷摘要(各段以「|」分隔);無任何資訊時回傳固定字串。
|
||||||
*/
|
*/
|
||||||
function agentFailureDetail(res) {
|
function agentFailureDetail(agentResult) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
const err = res && res.error;
|
const err = agentResult && agentResult.error;
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.killed) parts.push('已逾時終止');
|
if (err.killed) parts.push('已逾時終止');
|
||||||
if (typeof err.code === 'number') parts.push(`exit ${err.code}`);
|
if (typeof err.code === 'number') parts.push(`exit ${err.code}`);
|
||||||
@@ -52,9 +52,9 @@ function agentFailureDetail(res) {
|
|||||||
}
|
}
|
||||||
// 失敗輸出可能含 token 或 PII,預設不寫入長期 CI log;debug 模式才輸出遮罩後片段。
|
// 失敗輸出可能含 token 或 PII,預設不寫入長期 CI log;debug 模式才輸出遮罩後片段。
|
||||||
if (process.env.ACTIONS_STEP_DEBUG === 'true') {
|
if (process.env.ACTIONS_STEP_DEBUG === 'true') {
|
||||||
const stderr = redactSecrets(String((res && res.stderr) || '').slice(0, AGENT_DIAGNOSTIC_INPUT_LIMIT));
|
const stderr = redactSecrets(String((agentResult && agentResult.stderr) || '').slice(0, AGENT_DIAGNOSTIC_INPUT_LIMIT));
|
||||||
if (stderr) parts.push(`stderr:${stderr.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
if (stderr) parts.push(`stderr:${stderr.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||||
const stdout = redactSecrets(String((res && res.output) || '').slice(0, AGENT_DIAGNOSTIC_INPUT_LIMIT));
|
const stdout = redactSecrets(String((agentResult && agentResult.output) || '').slice(0, AGENT_DIAGNOSTIC_INPUT_LIMIT));
|
||||||
if (stdout) parts.push(`stdout:${stdout.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
if (stdout) parts.push(`stdout:${stdout.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||||
}
|
}
|
||||||
if (parts.length === 0) {
|
if (parts.length === 0) {
|
||||||
|
|||||||
+7
-7
@@ -173,23 +173,23 @@ function createIssue(ctx, { title, body, labels }) {
|
|||||||
* 對應 endpoint:`POST /repos/{owner}/{repo}/issues/{issueNumber}/dependencies`
|
* 對應 endpoint:`POST /repos/{owner}/{repo}/issues/{issueNumber}/dependencies`
|
||||||
* (body 為 IssueMeta:`{index, owner, repo}`)。
|
* (body 為 IssueMeta:`{index, owner, repo}`)。
|
||||||
*
|
*
|
||||||
* 語義:URL 的 issue(`issueNumber`)相依於 body 的 issue(`dependency`)——
|
* 語義:URL 的 issue(`blockedIssueNumber`)相依於 body 的 issue(`blockingIssueNumber`)——
|
||||||
* 在 `dependency` 關閉前,`issueNumber` 無法合併/關閉。本 endpoint 需 repo 啟用
|
* 在 `blockingIssueNumber` 關閉前,`blockedIssueNumber` 無法合併/關閉。本 endpoint 需 repo 啟用
|
||||||
* 「問題相依(issue dependencies)」功能,屬版本/設定相依;未啟用或不支援時 API 會回非 2xx。
|
* 「問題相依(issue dependencies)」功能,屬版本/設定相依;未啟用或不支援時 API 會回非 2xx。
|
||||||
*
|
*
|
||||||
* @param {object} ctx - 執行環境 context。必要欄位:`apiBase`、`token`、
|
* @param {object} ctx - 執行環境 context。必要欄位:`apiBase`、`token`、
|
||||||
* `owner`(repo 擁有者)、`repo`(repo 名稱)。
|
* `owner`(repo 擁有者)、`repo`(repo 名稱)。
|
||||||
* @param {number|string} issueNumber - 要被阻擋的 issue/PR 編號(相依方)。
|
* @param {number|string} blockedIssueNumber - 要被阻擋的 issue/PR 編號(相依方)。
|
||||||
* @param {number} dependency - 作為阻擋來源的 issue 編號(同一 repo)。
|
* @param {number} blockingIssueNumber - 作為阻擋來源的 issue 編號(同一 repo)。
|
||||||
* @returns {Promise<object>} 建立成功的相依關係物件(依 Gitea API 回應而定)。
|
* @returns {Promise<object>} 建立成功的相依關係物件(依 Gitea API 回應而定)。
|
||||||
* @throws {Error} 請求失敗(非 2xx,例如未啟用問題相依功能)由底層 `api` 丟出,錯誤附 `status`、`data`。
|
* @throws {Error} 請求失敗(非 2xx,例如未啟用問題相依功能)由底層 `api` 丟出,錯誤附 `status`、`data`。
|
||||||
* @remarks 使用情境:建問題模式(input: create-issue)下,`main()` 建立追蹤 issue 後,
|
* @remarks 使用情境:建問題模式(input: create-issue)下,`main()` 建立追蹤 issue 後,
|
||||||
* 以本函式把「PR(`ctx.prNumber`)相依於追蹤 issue」,讓 issue 完成/關閉前 PR 無法合併;
|
* 以本函式把「PR(`ctx.prNumber`)相依於追蹤 issue」,讓 issue 完成/關閉前 PR 無法合併;
|
||||||
* 呼叫端以 try/catch 降級(功能未啟用時記 WRN、不阻斷流程)。
|
* 呼叫端以 try/catch 降級(功能未啟用時記 WRN、不阻斷流程)。
|
||||||
*/
|
*/
|
||||||
function addIssueDependency(ctx, issueNumber, dependency) {
|
function addIssueDependency(ctx, blockedIssueNumber, blockingIssueNumber) {
|
||||||
return api(ctx, 'POST', `/repos/${ctx.owner}/${ctx.repo}/issues/${issueNumber}/dependencies`, {
|
return api(ctx, 'POST', `/repos/${ctx.owner}/${ctx.repo}/issues/${blockedIssueNumber}/dependencies`, {
|
||||||
index: dependency,
|
index: blockingIssueNumber,
|
||||||
owner: ctx.owner,
|
owner: ctx.owner,
|
||||||
repo: ctx.repo,
|
repo: ctx.repo,
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-6
@@ -690,9 +690,9 @@ ${JSON.stringify(brief)}
|
|||||||
* 不使用 {@link templates.othersComment} 的單一表格——表格僅用於一般模式(PR)。
|
* 不使用 {@link templates.othersComment} 的單一表格——表格僅用於一般模式(PR)。
|
||||||
*/
|
*/
|
||||||
async function postSevereToIssue({ ctx, gitea, issueNumber, severe }) {
|
async function postSevereToIssue({ ctx, gitea, issueNumber, severe }) {
|
||||||
await Promise.all(
|
for (const finding of severe) {
|
||||||
severe.map((finding) => gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding))),
|
await gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding));
|
||||||
);
|
}
|
||||||
log('步驟9', 'INF', `已將 ${severe.length} 條嚴重問題留言到 issue #${issueNumber}。`);
|
log('步驟9', 'INF', `已將 ${severe.length} 條嚴重問題留言到 issue #${issueNumber}。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,9 +716,9 @@ async function postSevereToIssue({ ctx, gitea, issueNumber, severe }) {
|
|||||||
* 以本函式把警告+建議逐條留言到追蹤 issue,確保 issue 上每條問題都是可個別回覆的留言。
|
* 以本函式把警告+建議逐條留言到追蹤 issue,確保 issue 上每條問題都是可個別回覆的留言。
|
||||||
*/
|
*/
|
||||||
async function postOthersToIssue({ ctx, gitea, issueNumber, others }) {
|
async function postOthersToIssue({ ctx, gitea, issueNumber, others }) {
|
||||||
await Promise.all(
|
for (const finding of others) {
|
||||||
others.map((finding) => gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding))),
|
await gitea.createCommentOnIssue(ctx, issueNumber, templates.issueFindingComment(finding));
|
||||||
);
|
}
|
||||||
log('步驟10', 'INF', `已將 ${others.length} 條警告+建議逐條留言到 issue #${issueNumber}。`);
|
log('步驟10', 'INF', `已將 ${others.length} 條警告+建議逐條留言到 issue #${issueNumber}。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ function nothingToReviewComment(ignoredCount) {
|
|||||||
* 以本函式對 PR 留一則連結留言,達成「問題關聯回 PR」;issue 內文另以
|
* 以本函式對 PR 留一則連結留言,達成「問題關聯回 PR」;issue 內文另以
|
||||||
* {@link issueBody} 反向引用 `PR #N`,形成雙向交叉連結。
|
* {@link issueBody} 反向引用 `PR #N`,形成雙向交叉連結。
|
||||||
*/
|
*/
|
||||||
function issueLinkComment({ issueNumber, issueUrl, severeCount, otherCount }) {
|
function prIssueLinkComment({ issueNumber, issueUrl, severeCount, otherCount }) {
|
||||||
return `${MARK}
|
return `${MARK}
|
||||||
## 🔍 AI Code Review|已建立追蹤問題
|
## 🔍 AI Code Review|已建立追蹤問題
|
||||||
|
|
||||||
@@ -421,5 +421,5 @@ module.exports = {
|
|||||||
issueBody,
|
issueBody,
|
||||||
issueFindingComment,
|
issueFindingComment,
|
||||||
nothingToReviewComment,
|
nothingToReviewComment,
|
||||||
issueLinkComment,
|
prIssueLinkComment,
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ const test = require('node:test');
|
|||||||
|
|
||||||
const gitea = require('../src/lib/gitea');
|
const gitea = require('../src/lib/gitea');
|
||||||
|
|
||||||
function withFetchStub(handler, fn) {
|
function withFetchStub(handler, callback) {
|
||||||
const originalFetch = global.fetch;
|
const originalFetch = global.fetch;
|
||||||
const calls = [];
|
const calls = [];
|
||||||
global.fetch = async (url, options = {}) => {
|
global.fetch = async (url, options = {}) => {
|
||||||
@@ -13,7 +13,7 @@ function withFetchStub(handler, fn) {
|
|||||||
return handler(url, options);
|
return handler(url, options);
|
||||||
};
|
};
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => fn(calls))
|
.then(() => callback(calls))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
global.fetch = originalFetch;
|
global.fetch = originalFetch;
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ test('agentFailureDetail 在 debug 模式輸出遮罩後片段', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('postOthersToIssue 批次送出 issue 留言', async () => {
|
test('postOthersToIssue 依序送出 issue 留言以維持排序', async () => {
|
||||||
const calls = [];
|
const calls = [];
|
||||||
let active = 0;
|
let active = 0;
|
||||||
let maxActive = 0;
|
let maxActive = 0;
|
||||||
@@ -71,7 +71,7 @@ test('postOthersToIssue 批次送出 issue 留言', async () => {
|
|||||||
|
|
||||||
assert.equal(calls.length, 2);
|
assert.equal(calls.length, 2);
|
||||||
assert.equal(calls[0].issueNumber, 7);
|
assert.equal(calls[0].issueNumber, 7);
|
||||||
assert.ok(maxActive > 1);
|
assert.equal(maxActive, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resultFilesToCommit 在建問題模式有嚴重問題時仍提交 findings', () => {
|
test('resultFilesToCommit 在建問題模式有嚴重問題時仍提交 findings', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user