Compare commits
9
Commits
v0.0.4-beta.37
...
v0.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34aecf6e43 | ||
|
|
2d3128eb45 | ||
|
|
885b79f8b3 | ||
|
|
db83b54c2d | ||
|
|
3c1cd55444 | ||
|
|
cce35b3bef | ||
|
|
25f76e6e6e | ||
|
|
44c33b6c1e | ||
|
|
449b17480b |
@@ -2099,5 +2099,258 @@
|
||||
"endLine": 322,
|
||||
"problem": "commitAndPushFindings 改成一律透過 pushWithCredential 用 PAT extraheader 推送,且新增 headRef 安全檢查與固定錯誤遮蔽;但目前 gitrepo 測試沒有覆蓋 commitAndPushFindings/pushWithCredential 的推送行為。這讓「token 不進 argv」、「清掉 checkout 既有 extraheader」、「遠端 URL 不符時拒絕」、「push 失敗不洩漏 URL/token」這些失敗與安全邊界都沒有被驗證。",
|
||||
"reason": "Paladin:可排除(重複)。歷史 findings 已涵蓋 commitAndPushFindings 與 pushWithCredential 的推送策略、認證遮蔽、失敗路徑與 token 不外洩等測試缺口。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Assassin",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/diagnostics.js",
|
||||
"startLine": 68,
|
||||
"endLine": 71,
|
||||
"problem": "攻擊者只要讓 AI CLI 失敗,並碰上 runner 開了 `ACTIONS_STEP_DEBUG=true`,就能把 CLI 的 stderr/stdout 片段推進長期保存的 CI log。這裡的遮罩是黑名單式,會漏掉不少常見秘密格式或個資,例如短 token、AWS access key、JWT 片段、email、電話、內部路徑與提示中夾帶的 diff 內容。安全邊界不能寄望「debug log 只有自己看」;repo 協作者或 CI log 讀者都可能取得這些輸出。",
|
||||
"reason": "Paladin:可排除(重複)。既有紀錄已多次涵蓋 debug 模式輸出 AI CLI stderr/stdout、黑名單遮罩可繞過,以及機密或個資外洩風險;本條只是改到 diagnostics.js 後的同型指控。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Bard",
|
||||
"severity": "建議",
|
||||
"file": "readme.md",
|
||||
"startLine": 41,
|
||||
"endLine": 51,
|
||||
"problem": "Mermaid 流程圖的節點 ID 從 `N3`、`N4` 一路走到 `N8`,中途又接回 `N2`。顯示文字雖然表達「步驟 2 延後」,但原始碼層面的節點命名逆行,讓文件維護者讀圖時節奏斷裂。",
|
||||
"reason": "Paladin:可排除(重複)。歷史 finding 已指出流程步驟編號被當成跨模組識別值、README 流程圖出現 1、3~8、2、9~10 的逆序維護問題;本條屬同一文件編號耦合問題。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Leo",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 162,
|
||||
"endLine": 392,
|
||||
"problem": "`main()` 這次把建問題模式的狀態機、留言緩衝、issue 建立、fallback、舊留言清理、結果 commit 判定都塞進同一個流程函式與多個閉包裡。半年後要改其中一條路徑時,很難確認 `issueModeActive`、`trackingIssue`、`pendingIssueCommentBodies`、`currentRunCommentIds` 在各分支是否仍一致;尤其建 issue 失敗後降級回 PR 留言,後面又要決定清舊留言、發嚴重問題、提交 findings,維護者需要整段流程一起讀才敢動。",
|
||||
"reason": "Paladin:可排除(命中已知排除事項且重複)。已知排除與歷史 findings 已涵蓋 main() 內建問題模式、留言路由、issue 狀態、緩衝佇列與發布職責耦合的問題。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Leo",
|
||||
"severity": "建議",
|
||||
"file": "src/lib/gitea.js",
|
||||
"startLine": 156,
|
||||
"endLine": 160,
|
||||
"problem": "文件註解提到 `main()` 的 `createIssueAndFlushBufferedComments`,但這次新增的實際閉包名稱是 `openTrackingIssue`。這類失準的內部函式名引用會讓未來維護者循線找不到程式碼,久了文件會變成負債。",
|
||||
"reason": "Paladin:可排除(列表內重複)。與 F002 指涉 src/lib/gitea.js 同一段 JSDoc 引用不存在的 createIssueAndFlushBufferedComments 問題。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Leo",
|
||||
"severity": "建議",
|
||||
"file": "src/lib/templates.js",
|
||||
"startLine": 302,
|
||||
"endLine": 305,
|
||||
"problem": "`issueBody()` 的 JSDoc 同樣引用不存在的 `createIssueAndFlushBufferedComments`。模板模組本來應該只說明模板用途;引用外層流程的私有閉包名稱,會讓文件跟主流程重構強耦合。",
|
||||
"reason": "Paladin:可排除(列表內重複)。與 F003 指涉 src/lib/templates.js 的 issueBody JSDoc 引用不存在內部閉包名稱,屬同一問題。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 162,
|
||||
"endLine": 239,
|
||||
"problem": "建問題模式新增了 `queueOrPostComment`、`openTrackingIssue`、`fallbackToPrComments` 這組分流行為,但目前測試只覆蓋了部分 `review` helper,沒有驗證主流程在 issue 尚未建立時會暫存留言、建立成功後會依序 flush、建立或寫入失敗時會回退到 PR 留言。這些都是本次新增的失敗路徑,沒測到時很容易出現審查內容被靜默丟掉或留言落錯地方。",
|
||||
"reason": "Paladin:可排除(重複)。歷史 findings 已涵蓋建問題模式暫存留言、建立後依序 flush、建立或 API 失敗時降級,以及無 finding 靜默通過等核心流程缺少測試。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 103,
|
||||
"endLine": 158,
|
||||
"problem": "`resolveMergeBase` 新增了多段 fetch 補抓策略、淺層 repo 判斷、每次 fetch 後重試 merge-base,以及全部失敗時附診斷的錯誤;目前測試只驗證不安全 `baseRef` 會在 fetch 前被拒絕,沒有測到這些新增分支。這些邊界正是 shallow checkout 最容易壞的地方。",
|
||||
"reason": "Paladin:可排除(重複)。歷史 findings 已針對 resolveMergeBase 的多階段 fetch、淺層與非淺層分支、停止條件、降級與最終診斷缺少測試提出相同問題。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/21 17:25:38",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 287,
|
||||
"endLine": 326,
|
||||
"problem": "`pushWithCredential` 是新增的認證推送核心路徑,包含清掉 checkout 既有 extraheader、用 PAT extraheader 推送、遠端 URL origin 檢查,以及失敗時隱藏 URL/token;但目前沒有任何測試驗證這些行為。這裡的失敗路徑沒測到,容易在 runner 上才發現結果 commit 推不上去或錯誤訊息洩漏敏感資訊。",
|
||||
"reason": "Paladin:可排除(重複)。歷史 findings 已涵蓋 pushToken/pushWithCredential、origin 與一般 token 推送策略、認證遮蔽及失敗路徑缺少測試。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 306,
|
||||
"endLine": 359,
|
||||
"problem": "建問題模式的核心流程已大幅改變,但 diff 中沒有對應測試驗證各分支:有保留問題時才建立 issue、暫存留言依序送出、無問題時靜默通過、嚴重與非嚴重問題送往正確位置,以及標籤失敗後仍須回貼 PR 連結。這些分支牽涉多次外部 API 呼叫與狀態切換,未測試時很容易出現漏留言、留言送錯 PR/issue,或在 `issue` 尚未建立時解參考的回歸。",
|
||||
"reason": "建問題模式現行已有追蹤 issue 建立失敗降級、嚴重問題才設 dependency、無 finding 靜默通過與結果檔 fail-closed 測試;其餘屬設計重構建議,非本次 findings 修復的可安全最小改動。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 184,
|
||||
"endLine": 245,
|
||||
"problem": "建問題模式新增「issue 建立前暫存留言、建立後依序沖刷」的狀態流程,但沒有對應測試。尚未驗證 createIssue 或沖刷途中拋錯、空 buffer、重複呼叫、以及一般/建問題模式留言目的地是否正確;失敗路徑可能造成 issue 已建立但內容不完整或留言誤發到 PR。",
|
||||
"reason": "此 finding 屬主流程重構建議,牽涉建問題模式設計切分;現行行為已有降級與結果檔 fail-closed 防護,本次不做高風險重構。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 309,
|
||||
"endLine": 395,
|
||||
"problem": "建問題模式核心分支被大幅改寫,但沒有測試驗證各種 findings 組合與 API 失敗行為。kept.length===0、只有嚴重、只有警告/建議、兩者皆有,以及標籤挑選/issue 建立/PR 回貼連結/相依 API 失敗等路徑尚未試煉,無法確認「無問題完全靜默」「有問題才建 issue」及相依失敗僅降級等契約成立。",
|
||||
"reason": "此 finding 屬主流程重構建議,牽涉建問題模式設計切分;現行行為已有降級與結果檔 fail-closed 防護,本次不做高風險重構。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 249,
|
||||
"endLine": 289,
|
||||
"problem": "推送流程新增 pushToken 分支,但沒有測試驗證兩套認證策略:有 PAT 時略過 origin、PAT 推送失敗不退回其他 token、無 PAT 時 origin 成功不重試、origin 失敗才用一般 token;也沒有案例保護含憑證資訊不出現在錯誤或測試輸出中。(本次已將認證改經 env 傳入並遮蔽 push 錯誤,測試仍待補。)",
|
||||
"reason": "現行推送路徑已統一走 `pushWithCredential`,token 只經環境變數 extraheader 注入且錯誤不帶 URL/憑證;finding 中的 pushToken/origin 雙軌描述已非現行程式。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Leo",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 121,
|
||||
"endLine": 145,
|
||||
"problem": "流程步驟編號硬編碼在主流程註解、日誌字串、README 與多個函式 JSDoc 中;插入一個步驟就要同步修改大量檔案,文件與實作高耦合,日後調整流程易漏改而互相矛盾。",
|
||||
"reason": "此 finding 屬主流程重構建議,牽涉建問題模式設計切分;現行行為已有降級與結果檔 fail-closed 防護,本次不做高風險重構。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Leo",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 47,
|
||||
"endLine": 65,
|
||||
"problem": "tryGit 將所有 git 失敗壓成布林值,resolveMergeBase 的診斷只知策略成敗、無法區分認證/refspec/網路/版本問題;CI 出錯時維護者只能重跑或自行重現,診斷成本高。",
|
||||
"reason": "現行推送路徑已統一走 `pushWithCredential`,token 只經環境變數 extraheader 注入且錯誤不帶 URL/憑證;finding 中的 pushToken/origin 雙軌描述已非現行程式。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 270,
|
||||
"endLine": 291,
|
||||
"problem": "findings 推送的認證路徑沒有測試。註:原「PAT 直接推送 vs origin 失敗重試」雙路徑已於先前 commit 合併為「一律以 token 明確認證推送」,測試仍待補:空 token 邊界、推送目標正確、錯誤與輸出不含 token 原文。",
|
||||
"reason": "現行推送路徑已統一走 `pushWithCredential`,token 只經環境變數 extraheader 注入且錯誤不帶 URL/憑證;finding 中的 pushToken/origin 雙軌描述已非現行程式。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Bard",
|
||||
"severity": "建議",
|
||||
"file": "src/index.js",
|
||||
"startLine": 179,
|
||||
"endLine": 231,
|
||||
"problem": "main() 內新增兩個帶完整 JSDoc 的閉包函式與一大段「步驟 2:延後執行」說明,使主流程在進入步驟 3 前被近六十行細節打斷,留言路由、issue 建立與流程說明混在同一層,閱讀節奏沉重。",
|
||||
"reason": "此 finding 屬主流程重構建議,牽涉建問題模式設計切分;現行行為已有降級與結果檔 fail-closed 防護,本次不做高風險重構。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": null,
|
||||
"reviewer": "🧰 Leo",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 257,
|
||||
"endLine": 307,
|
||||
"problem": "流程步驟編號同時硬編碼在 log 字串、區段註解、JSDoc、README 與多個函式庫中。插入或調整一個步驟就必須跨大量檔案全面改號,容易讓文件與實際紀錄不一致。",
|
||||
"reason": "此 finding 屬流程註解與文件呈現的維護性建議;現行延後清理步驟已有明確註解,不影響執行行為,後續可由文件化流程統一整理。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": null,
|
||||
"reviewer": "🧪 Maya",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 252,
|
||||
"endLine": 289,
|
||||
"problem": "commitAndPushFindings 的推送行為(有無變更、認證方式、空 commit 防護、是否觸發 CI)缺少測試證明。此為本次變更的核心行為,卻沒有任何測試覆蓋。",
|
||||
"reason": "現行推送路徑已統一走 `pushWithCredential`,token 只經環境變數 extraheader 注入且錯誤不帶 URL/憑證;finding 中的 pushToken/origin 雙軌描述已非現行程式。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": null,
|
||||
"reviewer": "Leo",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 119,
|
||||
"endLine": 139,
|
||||
"problem": "流程步驟編號被當成跨模組識別值,散落在 `index.js`、各 library 的日誌與 JSDoc、README 流程圖及功能表。這次僅因插入並延後一步,就必須同步修改大量 `步驟2`~`步驟8` 字串,而且實際執行順序已變成 1、3~8、2、9~10;未來再調整流程時非常容易讓文件、日誌與程式碼脫節。",
|
||||
"reason": "此 finding 屬流程註解與文件呈現的維護性建議;現行延後清理步驟已有明確註解,不影響執行行為,後續可由文件化流程統一整理。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": null,
|
||||
"reviewer": "Leo",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 293,
|
||||
"endLine": 305,
|
||||
"problem": "為了防止 Git 推送失敗時在例外訊息中回顯包含 Token 與遠端 URL 的命令列參數,pushWithCredential 的 catch 區塊直接拋出一個固定的 Error('推送審查結果 commit 失敗...')。但這樣一來,它完全吞掉了原始的錯誤(例如 non-fast-forward 非快轉、分支保護規則阻擋、或連線逾時),六個月後的維護者在 CI log 中看到此錯誤時,完全無從判斷失敗的原因。",
|
||||
"reason": "現行推送路徑已統一走 `pushWithCredential`,token 只經環境變數 extraheader 注入且錯誤不帶 URL/憑證;finding 中的 pushToken/origin 雙軌描述已非現行程式。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": null,
|
||||
"reviewer": "Bard",
|
||||
"severity": "建議",
|
||||
"file": "src/index.js",
|
||||
"startLine": 308,
|
||||
"endLine": 368,
|
||||
"problem": "新增或修改的步驟分隔註解(如步驟 8 分組、步驟 2 延後、步驟 9、步驟 10、及建問題模式收束)其尾隨的水平分隔線(─)長度不一或僅存單一字元,破壞了專案既有程式碼中整齊劃一的長分隔線視覺排版,視覺上顯得雜亂、走調。",
|
||||
"reason": "此 finding 屬流程註解與文件呈現的維護性建議;現行延後清理步驟已有明確註解,不影響執行行為,後續可由文件化流程統一整理。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": null,
|
||||
"reviewer": "Rogue",
|
||||
"severity": "建議",
|
||||
"file": "src/index.js",
|
||||
"startLine": 366,
|
||||
"endLine": 382,
|
||||
"problem": "在建問題模式收束時,先 `await gitea.createIssueComment` 再 `await gitea.addIssueDependency`,這兩個 Gitea API 呼叫是獨立且無資料相依性的,卻以序列(Sequential)方式執行,白白浪費了一次網路往返(RTT)的等待時間。",
|
||||
"reason": "建問題模式現行已有追蹤 issue 建立失敗降級、嚴重問題才設 dependency、無 finding 靜默通過與結果檔 fail-closed 測試;其餘屬設計重構建議,非本次 findings 修復的可安全最小改動。"
|
||||
},
|
||||
{
|
||||
"addedAt": "2026/07/28 13:57:24",
|
||||
"prNumber": 6,
|
||||
"reviewer": "Bard",
|
||||
"severity": "建議",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 139,
|
||||
"endLine": 160,
|
||||
"problem": "`runFetch`、`tryMergeBase`、`firstError`、`strategies` 夾在 `resolveMergeBase` 主旋律中,使這個函式同時負責驗證、fetch 策略編排、診斷文字組裝與錯誤包裝。即使邏輯可行,閱讀節奏已偏密,維護者很難一眼分辨「主要流程」與「補救策略」。",
|
||||
"reason": "現行 `resolveMergeBase` 已保留策略診斷與首次錯誤 cause,並先拒絕不安全 baseRef;較大規模的可測性重構屬後續設計工作,本次僅套用可安全的淺層判斷效能修正。"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7,33 +7,6 @@
|
||||
"version": "0.0.8",
|
||||
"model": "(工具預設)"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "F002",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 306,
|
||||
"endLine": 359,
|
||||
"problem": "建問題模式的核心流程已大幅改變,但 diff 中沒有對應測試驗證各分支:有保留問題時才建立 issue、暫存留言依序送出、無問題時靜默通過、嚴重與非嚴重問題送往正確位置,以及標籤失敗後仍須回貼 PR 連結。這些分支牽涉多次外部 API 呼叫與狀態切換,未測試時很容易出現漏留言、留言送錯 PR/issue,或在 `issue` 尚未建立時解參考的回歸。",
|
||||
"suggestion": "新增主流程測試並 mock Gitea、agent 與 git 操作,至少涵蓋:`kept=[]` 時不建立 issue 且不留言;僅嚴重問題;僅警告/建議;混合問題;建立 issue 或寫入暫存留言失敗;標籤查詢、AI 選標籤及補掛標籤失敗時仍回貼 issue 連結。除了呼叫次數,也應斷言 API 呼叫順序、目標 issue 編號及留言內容。",
|
||||
"suggestedCode": ""
|
||||
},
|
||||
{
|
||||
"id": "F004",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 100,
|
||||
"endLine": 122,
|
||||
"problem": "`resolveMergeBase` 新增多階段 fetch 與淺層 checkout 修復邏輯,但沒有看到測試驗證成功、降級與最終失敗路徑。尤其初次 merge-base 失敗後,淺層與非淺層 repository 會走不同路徑,且多個 `tryGit` 失敗會被刻意吞掉;若參數、refspec 或重試順序有誤,只會在實際 CI checkout 深度不足時才暴露。",
|
||||
"suggestion": "以 stub 的 git 執行器或暫存 repository 補齊案例:首次 merge-base 成功;淺層 repository 經 `--unshallow` 後成功;`--unshallow` 失敗但 `--deepen=1000` 後成功;非淺層首次失敗後重試成功;所有策略失敗時拋出含 `baseRef` 且保留原始 `cause` 的錯誤。並斷言 base/head fetch 的 refspec 與執行順序。",
|
||||
"suggestedCode": ""
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": []
|
||||
}
|
||||
|
||||
@@ -7,59 +7,6 @@
|
||||
"version": "0.0.8",
|
||||
"model": "(工具預設)"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "F003",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 184,
|
||||
"endLine": 245,
|
||||
"problem": "建問題模式新增「issue 建立前暫存留言、建立後依序沖刷」的狀態流程,但沒有對應測試。尚未驗證 createIssue 或沖刷途中拋錯、空 buffer、重複呼叫、以及一般/建問題模式留言目的地是否正確;失敗路徑可能造成 issue 已建立但內容不完整或留言誤發到 PR。",
|
||||
"suggestion": "補主流程整合測試並 mock Gitea API,驗證:一般模式直接發 PR 且記錄留言 id;issue 未建立時只暫存;建立後依序沖刷並清空 buffer;createIssue 與第 N 則沖刷留言失敗時以失敗結束且不再發布後續內容。屬測試架構決策(專案目前無測試框架)。",
|
||||
"suggestedCode": ""
|
||||
},
|
||||
{
|
||||
"id": "F004",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 309,
|
||||
"endLine": 395,
|
||||
"problem": "建問題模式核心分支被大幅改寫,但沒有測試驗證各種 findings 組合與 API 失敗行為。kept.length===0、只有嚴重、只有警告/建議、兩者皆有,以及標籤挑選/issue 建立/PR 回貼連結/相依 API 失敗等路徑尚未試煉,無法確認「無問題完全靜默」「有問題才建 issue」及相依失敗僅降級等契約成立。",
|
||||
"suggestion": "以參數化測試覆蓋 findings 四種組合,斷言 API 呼叫順序/次數/issue number/統計;並分別讓 listLabels/selectLabels/createIssue/問題留言/PR 連結留言/addIssueDependency 拋錯,驗證哪些中止、哪些僅記警告續行;零 findings 時斷言所有寫入 API 皆不呼叫。屬測試架構決策。",
|
||||
"suggestedCode": ""
|
||||
},
|
||||
{
|
||||
"id": "F006",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 103,
|
||||
"endLine": 157,
|
||||
"problem": "resolveMergeBase 新增多階段 fetch/重試策略,卻沒有測試鎖定淺層與失敗路徑:首次成功提早返回、非 shallow 不 unshallow、某次 fetch 失敗後仍嘗試下一策略、補抓成功立即停止、全部失敗時診斷與 cause 是否完整;易因呼叫順序或 off-by-one 在 runner 上才暴露。",
|
||||
"suggestion": "將 git 執行器注入或 stub,建立表格化案例覆蓋首次成功/unshallow 後成功/deepen base 後成功/deepen HEAD 後成功/各 fetch 個別失敗/全部失敗;逐案斷言 git 參數與順序、成功後不再額外 fetch,並檢查最終錯誤含各策略成敗摘要且保留首次錯誤為 cause。屬可測性重構+測試架構決策。",
|
||||
"suggestedCode": ""
|
||||
},
|
||||
{
|
||||
"id": "F007",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 249,
|
||||
"endLine": 289,
|
||||
"problem": "推送流程新增 pushToken 分支,但沒有測試驗證兩套認證策略:有 PAT 時略過 origin、PAT 推送失敗不退回其他 token、無 PAT 時 origin 成功不重試、origin 失敗才用一般 token;也沒有案例保護含憑證資訊不出現在錯誤或測試輸出中。(本次已將認證改經 env 傳入並遮蔽 push 錯誤,測試仍待補。)",
|
||||
"suggestion": "mock git 執行器與 URL/env 組裝,補測 pushToken 有值/空、origin 成功/失敗、PAT 推送失敗及含特殊字元等案例;斷言 push 目標與呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。屬測試架構決策。",
|
||||
"suggestedCode": ""
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": []
|
||||
}
|
||||
|
||||
@@ -7,98 +7,6 @@
|
||||
"version": "0.0.8",
|
||||
"model": "(工具預設)"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "F001",
|
||||
"reviewer": "Leo",
|
||||
"focus": "maintainability",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 121,
|
||||
"endLine": 145,
|
||||
"problem": "流程步驟編號硬編碼在主流程註解、日誌字串、README 與多個函式 JSDoc 中;插入一個步驟就要同步修改大量檔案,文件與實作高耦合,日後調整流程易漏改而互相矛盾。",
|
||||
"suggestion": "共用函式 JSDoc 改以語意階段名稱描述、不引用易變動的數字;日誌集中定義階段名稱或由單一流程描述產生編號;README 流程圖也以語意名稱為主。屬跨檔重構+設計取捨。",
|
||||
"suggestedCode": "const STAGE = Object.freeze({\n TOOL_DETECTION: '偵測工具',\n DIFF_COLLECTION: '整理差異',\n ATTACK_REVIEW: '攻擊方審查',\n DEFENSE_REVIEW: '防守方裁決',\n});\nlog(STAGE.DIFF_COLLECTION, 'INF', message);"
|
||||
},
|
||||
{
|
||||
"id": "F003",
|
||||
"reviewer": "Leo",
|
||||
"focus": "maintainability",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 47,
|
||||
"endLine": 65,
|
||||
"problem": "tryGit 將所有 git 失敗壓成布林值,resolveMergeBase 的診斷只知策略成敗、無法區分認證/refspec/網路/版本問題;CI 出錯時維護者只能重跑或自行重現,診斷成本高。",
|
||||
"suggestion": "讓嘗試結果保留結構化且已清理的錯誤分類(exit code、git 子命令、安全化後短訊息),仍避免記錄遠端 URL 或憑證;最終錯誤彙整足以行動的原因,並可對各失敗類型做單元測試。屬診斷重構+需測試。",
|
||||
"suggestedCode": "function tryGit(cwd, ...args) {\n try {\n git(cwd, ...args);\n return { ok: true };\n } catch (error) {\n return { ok: false, code: error.status ?? error.code ?? null, reason: sanitizeGitError(error) };\n }\n}"
|
||||
},
|
||||
{
|
||||
"id": "F004",
|
||||
"reviewer": "Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 270,
|
||||
"endLine": 291,
|
||||
"problem": "findings 推送的認證路徑沒有測試。註:原「PAT 直接推送 vs origin 失敗重試」雙路徑已於先前 commit 合併為「一律以 token 明確認證推送」,測試仍待補:空 token 邊界、推送目標正確、錯誤與輸出不含 token 原文。",
|
||||
"suggestion": "補單元測試攔截 git 參數/env:斷言以 token 認證推送、推送目標 refspec 正確、呼叫次數,並確保任何拋出的錯誤、log 或快照都不含原始 token。屬測試架構決策。",
|
||||
"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",
|
||||
"reviewer": "Bard",
|
||||
"focus": "style",
|
||||
"badge": "🎼",
|
||||
"severity": "建議",
|
||||
"file": "src/index.js",
|
||||
"startLine": 179,
|
||||
"endLine": 231,
|
||||
"problem": "main() 內新增兩個帶完整 JSDoc 的閉包函式與一大段「步驟 2:延後執行」說明,使主流程在進入步驟 3 前被近六十行細節打斷,留言路由、issue 建立與流程說明混在同一層,閱讀節奏沉重。",
|
||||
"suggestion": "將留言路由與 issue 建立封裝成具語義名稱的輔助物件/模組(例如 createCommentPublisher),讓 main() 只保留流程級呼叫;延後清理理由縮成貼近呼叫點的簡短註解。與 F001(語意階段名稱)同屬主流程重構,宜一併處理。",
|
||||
"suggestedCode": "const comments = createCommentPublisher({ ctx, gitea });\nawait comments.post(templates.toolComment({ /* ... */ }));"
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": []
|
||||
}
|
||||
|
||||
@@ -7,33 +7,6 @@
|
||||
"version": "0.0.8",
|
||||
"model": "(工具預設)"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "F001",
|
||||
"reviewer": "🧰 Leo",
|
||||
"focus": "maintainability",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 257,
|
||||
"endLine": 307,
|
||||
"problem": "流程步驟編號同時硬編碼在 log 字串、區段註解、JSDoc、README 與多個函式庫中。插入或調整一個步驟就必須跨大量檔案全面改號,容易讓文件與實際紀錄不一致。",
|
||||
"suggestion": "程式內改用穩定的語意階段名稱(如 diff、attack、defend、publish),由單一流程定義集中決定顯示順序;JSDoc 以階段名稱互相引用,README 流程圖由同一份階段資料產生或僅在文件層維護展示編號。",
|
||||
"suggestedCode": "const PHASE = Object.freeze({\n RESOLVE_OLD: '清理舊留言',\n DETECT_TOOL: '偵測工具',\n COLLECT_DIFF: '整理差異',\n ATTACK: '攻擊方審查',\n DEFEND: '防守方裁決',\n});\n\nlog(PHASE.COLLECT_DIFF, 'INF', `變更檔案 ${allFiles.length} 個…`);"
|
||||
},
|
||||
{
|
||||
"id": "F002",
|
||||
"reviewer": "🧪 Maya",
|
||||
"focus": "testing",
|
||||
"badge": "🧪",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 252,
|
||||
"endLine": 289,
|
||||
"problem": "commitAndPushFindings 的推送行為(有無變更、認證方式、空 commit 防護、是否觸發 CI)缺少測試證明。此為本次變更的核心行為,卻沒有任何測試覆蓋。",
|
||||
"suggestion": "mock git 命令,斷言:無 staged diff 時回傳 false 且不執行 commit/push;有變更時以認證方式推送到正確 refspec 與分支。注意:原 finding 描述的 pushToken 對比 origin 雙軌邏輯已於重構後移除(現行一律以 token 經 pushWithCredential 認證推送),撰寫測試前需依現行程式碼重新界定情境。",
|
||||
"suggestedCode": ""
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": []
|
||||
}
|
||||
|
||||
@@ -7,203 +7,6 @@
|
||||
"version": "0.0.9",
|
||||
"model": "(工具預設)"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "F001",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "src/index.js",
|
||||
"startLine": 119,
|
||||
"endLine": 139,
|
||||
"problem": "流程步驟編號被當成跨模組識別值,散落在 `index.js`、各 library 的日誌與 JSDoc、README 流程圖及功能表。這次僅因插入並延後一步,就必須同步修改大量 `步驟2`~`步驟8` 字串,而且實際執行順序已變成 1、3~8、2、9~10;未來再調整流程時非常容易讓文件、日誌與程式碼脫節。",
|
||||
"suggestion": "以穩定的語意階段名稱取代硬編碼序號,例如 `TOOL_DETECTION`、`COLLECT_DIFF`、`RESOLVE_OLD_COMMENTS`,由單一常數表集中決定顯示名稱;README 的流程順序則從同一份定義產生,或至少不要在各函式文件重複紀錄易變的數字。",
|
||||
"suggestedCode": "```\nconst PHASE = Object.freeze({\n FAST_RESULT: '快速回報',\n DETECT_TOOL: '偵測 AI 工具',\n COLLECT_DIFF: '整理變更',\n RESOLVE_OLD: '處理舊留言',\n PUBLISH_FINDINGS: '發布審查結果',\n});\n\nlog(PHASE.DETECT_TOOL, 'INF', `選用工具:${tool.name}。`);\n```",
|
||||
"sourceIssue": 11
|
||||
},
|
||||
{
|
||||
"id": "F002",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "readme.md",
|
||||
"startLine": 59,
|
||||
"endLine": 130,
|
||||
"problem": "功能索引把分支名稱與原始碼行號硬編碼在數十個連結中;本次僅因程式碼增行,就必須人工把大量 `#L...` 全面更新,已直接顯示這份文件存在高同步成本。之後任一檔案前段增刪程式碼,都會讓這些連結再次漂移,而且指向會持續變動的 `develop` 分支,使舊版 README 與實際連結內容無法穩定對應。",
|
||||
"suggestion": "不要手動維護原始碼行號。若只需導覽,連到檔案並由右欄既有章節錨點提供函式級定位;若必須精確指向定義,應由 AST/文件產生工具在 CI 自動建立索引,並連到固定 commit SHA 或版本 tag。至少增加連結檢查,避免半年後整張功能表悄悄失準。",
|
||||
"suggestedCode": "```\n| 功能名稱 | 功能描述 |\n| --- | --- |\n| [gitrepo.resolveMergeBase](src/lib/gitrepo.js) | [解析 base 分支與 HEAD 的 merge-base](#gitreporesolvemergebase) |\n```",
|
||||
"sourceIssue": 12
|
||||
},
|
||||
{
|
||||
"id": "F006",
|
||||
"reviewer": "Rogue",
|
||||
"focus": "",
|
||||
"badge": "⚡",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 134,
|
||||
"endLine": 134,
|
||||
"problem": "在檢查是否為淺層 repository (shallow repository) 時,呼叫了外部子行程執行 `git rev-parse --is-shallow-repository`。建立與啟動 OS 子行程是非常昂貴的操作,會白白浪費數十毫秒的 CPU 週期與系統資源。",
|
||||
"suggestion": "Git 在淺層 clone 時會在 `.git` 目錄下建立一個 `shallow` 檔案。我們可以使用 Node.js 內建的 `fs.existsSync` 進行本地檔案檢查,不需啟動額外的 Git 子行程,執行速度可快上百倍。",
|
||||
"suggestedCode": "```\nconst fs = require('fs');\n// ...\nif (fs.existsSync(path.join(cwd, '.git', 'shallow'))) {\n strategies.push(['unshallow', 'fetch', '--no-tags', '--unshallow', 'origin']);\n}\n```",
|
||||
"sourceIssue": 14
|
||||
},
|
||||
{
|
||||
"id": "F007",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 293,
|
||||
"endLine": 305,
|
||||
"problem": "為了防止 Git 推送失敗時在例外訊息中回顯包含 Token 與遠端 URL 的命令列參數,pushWithCredential 的 catch 區塊直接拋出一個固定的 Error('推送審查結果 commit 失敗...')。但這樣一來,它完全吞掉了原始的錯誤(例如 non-fast-forward 非快轉、分支保護規則阻擋、或連線逾時),六個月後的維護者在 CI log 中看到此錯誤時,完全無從判斷失敗的原因。",
|
||||
"suggestion": "建議在保留安全遮罩的前提下,保留原始 exception 的排錯線索。例如可以檢查並安全地過濾 err.message 或 err.stderr 中所有的敏感字串(如 Token/URL),然後將其作為新錯誤的 cause 屬性或附加訊息傳遞下去。",
|
||||
"suggestedCode": "```\n} catch (err) {\n // 過濾敏感資訊後保留錯誤細節\n const safeMessage = err.message ? redactSecrets(err.message) : '未知錯誤';\n const error = new Error(`推送審查結果 commit 失敗(${safeMessage})。`);\n error.cause = err;\n throw error;\n }\n```",
|
||||
"sourceIssue": 14
|
||||
},
|
||||
{
|
||||
"id": "F009",
|
||||
"reviewer": "Bard",
|
||||
"focus": "",
|
||||
"badge": "🎼",
|
||||
"severity": "建議",
|
||||
"file": "src/index.js",
|
||||
"startLine": 308,
|
||||
"endLine": 368,
|
||||
"problem": "新增或修改的步驟分隔註解(如步驟 8 分組、步驟 2 延後、步驟 9、步驟 10、及建問題模式收束)其尾隨的水平分隔線(─)長度不一或僅存單一字元,破壞了專案既有程式碼中整齊劃一的長分隔線視覺排版,視覺上顯得雜亂、走調。",
|
||||
"suggestion": "補足尾隨的水平線 ─,使其與鄰近步驟分隔註解的長度(約 70~80 字元寬度)與視覺風格保持一致,維持排版的美觀。",
|
||||
"suggestedCode": "```\n// ── 步驟 8(分組):依嚴重等級分組(嚴重/警告+建議),組內已依檔案與行數排序 ────────────────\n```",
|
||||
"sourceIssue": 14
|
||||
},
|
||||
{
|
||||
"id": "F010",
|
||||
"reviewer": "Rogue",
|
||||
"focus": "",
|
||||
"badge": "⚡",
|
||||
"severity": "建議",
|
||||
"file": "src/index.js",
|
||||
"startLine": 366,
|
||||
"endLine": 382,
|
||||
"problem": "在建問題模式收束時,先 `await gitea.createIssueComment` 再 `await gitea.addIssueDependency`,這兩個 Gitea API 呼叫是獨立且無資料相依性的,卻以序列(Sequential)方式執行,白白浪費了一次網路往返(RTT)的等待時間。",
|
||||
"suggestion": "使用 `Promise.all` 同時發起這兩個請求,並行處理以減少整體 execution 的等待時間。",
|
||||
"suggestedCode": "```\nconst commentPromise = gitea.createIssueComment(\n ctx,\n templates.issueLinkComment({\n issueNumber: issue.number,\n issueUrl: issue.html_url,\n severeCount: severe.length,\n otherCount: others.length,\n })\n );\n const dependencyPromise = gitea.addIssueDependency(ctx, ctx.prNumber, issue.number)\n .then(() => log('建問題', 'INF', `已將 PR #${ctx.prNumber} 設為相依於 issue #${issue.number}。`))\n .catch((err) => log('建問題', 'WRN', `設定 PR 相依失敗:${err.message}。`));\n \n await Promise.all([commentPromise, dependencyPromise]);\n```",
|
||||
"sourceIssue": 14
|
||||
},
|
||||
{
|
||||
"id": "F012",
|
||||
"reviewer": "Rogue",
|
||||
"focus": "",
|
||||
"badge": "⚡",
|
||||
"severity": "建議",
|
||||
"file": "src/lib/review.js",
|
||||
"startLine": 48,
|
||||
"endLine": 53,
|
||||
"problem": "在 `agentFailureDetail` 之中,進行 stderr 與 stdout 的遮罩處理時,是先截斷至 2,000 字元,然後執行多次複雜的 `redactSecrets` 正規表示式替換,最後再截斷至 500 字元輸出。這會造成 1,500 字元的複雜 regex 運算結果在下一步被直接丟棄,白白浪費了 CPU 進行字串比對與取代的週期。",
|
||||
"suggestion": "應在呼叫 `redactSecrets` 之前,就先將字串截斷至目標長度(500 字元),再進行遮罩,可大幅減少 regex 運算負擔。",
|
||||
"suggestedCode": "```\nconst stderr = redactSecrets(String((res && res.stderr) || '').slice(0, 500));\n if (stderr) parts.push(`stderr:${stderr}`);\n const stdout = redactSecrets(String((res && res.output) || '').slice(0, 500));\n if (stdout) parts.push(`stdout:${stdout}`);\n```",
|
||||
"sourceIssue": 14
|
||||
},
|
||||
{
|
||||
"id": "F014",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "建議",
|
||||
"file": "readme.md",
|
||||
"startLine": 56,
|
||||
"endLine": 132,
|
||||
"problem": "README 的功能表大量手動維護 `src/branch/develop/...#Lxx` 深連結,這次光是分支與行號就改了整排。這類文件會隨任何插入註解、重排函式、換預設分支而失準,未來維護者必須在改程式時同步更新一大段文件,維護成本偏高。",
|
||||
"suggestion": "改成不依賴行號的相對連結,或用文件產生腳本從原始碼 JSDoc 自動產出這張表。若仍要指向 Gitea,建議至少移除 `#Lxx`,或集中定義分支名稱,避免每次改分支都要全表搜尋替換。",
|
||||
"suggestedCode": "",
|
||||
"sourceIssue": 15
|
||||
},
|
||||
{
|
||||
"id": "F022",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "警告",
|
||||
"file": "readme.md",
|
||||
"startLine": 76,
|
||||
"endLine": 132,
|
||||
"problem": "README 內大量函式清單同時硬編分支名稱與行號錨點,這次 diff 已經整批從 `master` 改成 `develop` 並同步調整行號。這類文件和原始碼結構高度重複,後續只要插入幾行程式,文件連結就會失準,維護者必須靠人工記得同步整張表。",
|
||||
"suggestion": "改成不含行號的穩定檔案連結,或把這份 API/功能表改由 JSDoc/腳本產生。若一定要保留行號,建議把產生流程寫入 npm script,避免每次程式碼位移都人工批次修改 README。",
|
||||
"suggestedCode": "",
|
||||
"sourceIssue": 18
|
||||
},
|
||||
{
|
||||
"id": "F025",
|
||||
"reviewer": "Assassin",
|
||||
"focus": "",
|
||||
"badge": "🗡️",
|
||||
"severity": "警告",
|
||||
"file": "action.yml",
|
||||
"startLine": 21,
|
||||
"endLine": 24,
|
||||
"problem": "這裡建議呼叫端傳入「能觸發 CI 的 PAT」作為 action token。攻擊者最愛這種長效、可推送、可觸發 workflow 的憑證:只要此 action 在不受信任 PR 上執行,或 PR 能影響 action/workflow 執行內容,惡意變更就可能讀取 `INPUT_TOKEN`、推送結果 commit、再藉由可觸發 CI 的身分製造後續執行鏈。自動 token 原本不觸發 CI 是一道防線,這個建議等於要求使用者把防線拆掉。",
|
||||
"suggestion": "不要泛稱建議使用可觸發 CI 的 PAT。文件與介面應明確要求最小權限、repo 限定、短效或可輪替 token,並禁止在 fork/不受信任 PR context 暴露 PAT。更穩的設計是分離 API 留言 token 與 push token,且只有在明確受信任事件或受保護分支才允許 push token 存在;否則拒絕 commit/push,只做留言或 artifact。",
|
||||
"suggestedCode": "",
|
||||
"sourceIssue": 19
|
||||
},
|
||||
{
|
||||
"id": "F026",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "建議",
|
||||
"file": "readme.md",
|
||||
"startLine": 59,
|
||||
"endLine": 132,
|
||||
"problem": "README 的功能列表手動維護了大量 `src/branch/develop/...#Lxx` 深連結與行號。這次 PR 已經一次改動數十個 branch/line anchor,代表文件和原始碼行號高度耦合;下一次只要插入幾行程式,文件就會悄悄過期,維護者很難知道哪些連結還準。",
|
||||
"suggestion": "避免在手寫 README 綁定行號,改連到函式所在檔案或穩定章節錨點;若必須保留行號,請把這段改成產生式文件,讓 CI 或腳本從原始碼/JSDoc 重新生成,減少人工同步成本。",
|
||||
"suggestedCode": "",
|
||||
"sourceIssue": 19
|
||||
},
|
||||
{
|
||||
"id": "F031",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "建議",
|
||||
"file": "readme.md",
|
||||
"startLine": 56,
|
||||
"endLine": 132,
|
||||
"problem": "README 的功能表大量硬編遠端分支名稱與行號,這次只是從 `master` 改成 `develop` 並同步行號,但這種文件很容易在下一次函式移動、預設分支更名或重排時再次整批失準。未來維護者會被迫反覆做低價值的連結校正,文件也可能在沒人注意時指到錯誤位置。",
|
||||
"suggestion": "若 README 是 repo 內文件,優先改成相對路徑連結,並避免固定行號;若必須保留行號,建議用產生腳本統一輸出這張表,讓分支名與行號只從單一來源計算。",
|
||||
"suggestedCode": "",
|
||||
"sourceIssue": 21
|
||||
},
|
||||
{
|
||||
"id": "F040",
|
||||
"reviewer": "Assassin",
|
||||
"focus": "",
|
||||
"badge": "🗡️",
|
||||
"severity": "警告",
|
||||
"file": "action.yml",
|
||||
"startLine": 19,
|
||||
"endLine": 22,
|
||||
"problem": "這段新增說明鼓勵呼叫端傳入「能觸發 CI 的 PAT」。攻擊者最喜歡這種長效、高權限、可觸發 workflow 的憑證:若 action 跑在不可信 PR、AI CLI 被 prompt injection 誘導讀環境變數,或同 repo PR 可改動本 action 程式碼,就可能把 PAT 外送或濫用成寫入 repo/觸發 CI 的跳板。",
|
||||
"suggestion": "不要把長效 PAT 當建議預設。改用最小權限、短效的 GitHub App/Gitea App token,並明確禁止在不可信 fork PR 傳入可寫 token。若目標只是回報檢查結果,優先用 status/check API 寫結果,不要靠 PAT push 再觸發下一輪 CI。",
|
||||
"suggestedCode": "```\ndescription: 'Gitea API token(PR/issue 留言與 push findings 用;請使用最小權限、短效 token,勿在不可信 PR 傳入長效 PAT)'\n```",
|
||||
"sourceIssue": 23
|
||||
},
|
||||
{
|
||||
"id": "F041",
|
||||
"reviewer": "Leo",
|
||||
"focus": "",
|
||||
"badge": "🧰",
|
||||
"severity": "建議",
|
||||
"file": "readme.md",
|
||||
"startLine": 58,
|
||||
"endLine": 131,
|
||||
"problem": "README 的功能表把分支名稱與行號大量硬編在外部 URL 裡,這次已經需要整批 `master` 改 `develop` 並同步多個 `#Lxx`。這類文件會隨任何程式碼插行、函式移動或預設分支變更而失準,維護成本會線性累積,最後讀者點到的文件比沒有文件更誤導。",
|
||||
"suggestion": "改用 repo 相對連結、不固定行號,或把這段功能表改由腳本從 JSDoc 自動產生。若需要連到特定實作,優先連到檔案或錨點,避免每次重排程式碼都要同步更新幾十個行號。",
|
||||
"suggestedCode": "```\n| log.taipeiNow | [src/lib/log.js](src/lib/log.js) | 取得台北時區 yyyy/MM/dd HH:mm:ss 時間字串 |\n| review.runAttackers | [src/lib/review.js](src/lib/review.js) | 攻擊方 sub agent 並行找問題並合併列表 |\n```",
|
||||
"sourceIssue": 23
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": []
|
||||
}
|
||||
|
||||
@@ -7,27 +7,7 @@
|
||||
"version": "codex-cli 0.144.6",
|
||||
"model": "gpt-5.5"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"reviewer": "Bard",
|
||||
"focus": "style",
|
||||
"badge": "🎼",
|
||||
"severity": "建議",
|
||||
"file": "src/lib/gitrepo.js",
|
||||
"startLine": 139,
|
||||
"endLine": 160,
|
||||
"problem": "`runFetch`、`tryMergeBase`、`firstError`、`strategies` 夾在 `resolveMergeBase` 主旋律中,使這個函式同時負責驗證、fetch 策略編排、診斷文字組裝與錯誤包裝。即使邏輯可行,閱讀節奏已偏密,維護者很難一眼分辨「主要流程」與「補救策略」。",
|
||||
"suggestion": "建議將 fetch 策略與診斷收集抽成小函式,例如 `fetchAndRecord`、`resolveMergeBaseWithStrategies`,讓 `resolveMergeBase` 保留高階流程:驗證 baseRef → fetch base → 嘗試 merge-base → 補抓歷史。",
|
||||
"suggestedCode": "",
|
||||
"id": "F003",
|
||||
"verdicts": {
|
||||
"Paladin": {
|
||||
"exclude": false,
|
||||
"reason": "保留。歷史 findings 主要涵蓋 resolveMergeBase 缺測試與診斷不足,本條指向函式內策略編排、診斷組裝與錯誤包裝混雜的可維護性問題,未被既有排除事項完整涵蓋。"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": [
|
||||
{
|
||||
"reviewer": "Bard",
|
||||
|
||||
@@ -7,103 +7,7 @@
|
||||
"version": "codex-cli 0.144.6",
|
||||
"model": "gpt-5.5"
|
||||
},
|
||||
"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 明確涵蓋;證據不足以排除。"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
"excluded": [
|
||||
{
|
||||
"reviewer": "Bard",
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# ============================================================================
|
||||
# 用途:定義 AI Code Review Node action 的名稱、輸入參數與 Node.js 24 進入點,供 Gitea / GitHub workflow 以 uses 引用。
|
||||
# 更新時間:2026/07/17 18:49:58
|
||||
# 更新時間:2026/07/28 13:57:24
|
||||
# ============================================================================
|
||||
# Gitea / GitHub node action 的 manifest(action.yml):
|
||||
# 定義本 action 的名稱、說明、輸入參數(inputs)與執行方式(runs),
|
||||
@@ -19,8 +19,8 @@ inputs:
|
||||
# Gitea API token:用於對 PR/issue 留言審查結果,以及 push 審查結果檔(findings/exclusions)回 repo。
|
||||
token:
|
||||
# 參數用途說明:secrets/vars context 在 action 內不可用,故由呼叫端 workflow 以 secrets 傳入。
|
||||
# 建議傳入能觸發 CI 的 PAT;自動 token 推送結果 commit 時可能不會再觸發 workflow。
|
||||
description: 'Gitea API token(PR/issue 留言與 push findings 用;建議以能觸發 CI 的 PAT 由 secrets 傳入)'
|
||||
# 建議使用 repo 限定、最小權限且可輪替的 token;不得在不受信任 fork PR 暴露可寫入 token。
|
||||
description: 'Gitea API token(PR/issue 留言與 push findings 用;請以 repo 限定、最小權限且可輪替的 secrets 傳入)'
|
||||
# 必填:缺少 token 無法呼叫 Gitea API,action 無法運作。
|
||||
required: true
|
||||
# 指定 AI 工具使用的模型名稱。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AI Code Review
|
||||
|
||||
> 更新時間:2026/07/17 18:49:58
|
||||
> 更新時間:2026/07/28 13:57:24
|
||||
|
||||
AI 多角色 code review 的 Gitea **node action**(`node24`、零外部相依):以攻擊方六角色(🔮 Mage 邏輯、🗡️ Assassin 安全、⚡ Rogue 效率、🎼 Bard 風格、🧪 Maya 測試、🧰 Leo 可維護性)並行找問題、防守方(🛡️ Paladin)裁決誤報,結果留言到 PR、保存 findings,並以 bot commit 標記審查結果(`[success]`/`[failure]`)供下次觸發快速回報。
|
||||
|
||||
@@ -76,56 +76,56 @@ flowchart TD
|
||||
|
||||
| 功能名稱 | 功能描述 |
|
||||
| --- | --- |
|
||||
| [log.taipeiNow](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L20) | [取得台北時區 yyyy/MM/dd HH:mm:ss 時間字串](#logtaipeinow) |
|
||||
| [log.taipeiFileStamp](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L41) | [取得檔名用時間戳 yyyy-MM-dd-HH:mm:ss](#logtaipeifilestamp) |
|
||||
| [log.taipeiFromIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L60) | [將 ISO 時間字串轉為台北時區顯示字串](#logtaipeifromiso) |
|
||||
| [log.log](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L83) | [以統一格式輸出一行日誌](#loglog) |
|
||||
| [context.loadContext](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/context.js#L64) | [彙整 runner 環境變數與事件 payload 為執行上下文](#contextloadcontext) |
|
||||
| [gitrepo.latestCommitSubject](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L79) | [取得最新 commit 的訊息標題](#gitrepolatestcommitsubject) |
|
||||
| [gitrepo.resolveMergeBase](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L100) | [解析 base 分支與 HEAD 的 merge-base](#gitreporesolvemergebase) |
|
||||
| [gitrepo.changedFiles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L136) | [列出 base 與 HEAD 之間有變更的檔案](#gitrepochangedfiles) |
|
||||
| [gitrepo.fileDiff](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L157) | [取得單一檔案的 git diff 內容](#gitrepofilediff) |
|
||||
| [gitrepo.fileLastUpdatedIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L175) | [取得檔案最後一次 commit 的 ISO 時間](#gitrepofilelastupdatediso) |
|
||||
| [gitrepo.commitAndPushFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L213) | [以 bot 身分 commit 結果檔並 push 回 PR 來源分支](#gitrepocommitandpushfindings) |
|
||||
| [gitea.whoAmI](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L87) | [取得 token 對應的使用者(bot 身分)](#giteawhoami) |
|
||||
| [gitea.createCommentOnIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L106) | [對指定編號 issue/PR 新增一般留言](#giteacreatecommentonissue) |
|
||||
| [gitea.createIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L124) | [對本次 PR 新增一般留言](#giteacreateissuecomment) |
|
||||
| [gitea.listLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L141) | [列出存取庫可用標籤](#gitealistlabels) |
|
||||
| [gitea.createIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L164) | [在存取庫建立 issue(可掛標籤)](#giteacreateissue) |
|
||||
| [gitea.listIssueComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L184) | [列出 PR 全部一般留言(自動分頁)](#gitealistissuecomments) |
|
||||
| [gitea.editIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L202) | [編輯既有一般留言](#giteaeditissuecomment) |
|
||||
| [gitea.createReview](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L223) | [建立 code review 並掛行內留言](#giteacreatereview) |
|
||||
| [gitea.listReviews](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L243) | [列出 PR 全部 review(自動分頁)](#gitealistreviews) |
|
||||
| [gitea.listReviewComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L264) | [列出某 review 的全部行內留言](#gitealistreviewcomments) |
|
||||
| [gitea.tryResolveReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L287) | [盡力將行內留言標記為已解決](#giteatryresolvereviewcomment) |
|
||||
| [agents.detectTool](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js#L53) | [依優先序偵測可用的 AI CLI 工具](#agentsdetecttool) |
|
||||
| [agents.runAgent](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js#L98) | [非互動執行一次 sub agent 並取回回覆](#agentsrunagent) |
|
||||
| [agents.extractJson](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js#L144) | [從 agent 回覆萃取 JSON(容忍雜訊)](#agentsextractjson) |
|
||||
| [roles.loadRoles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js#L32) | [載入角色提示檔並解析 frontmatter](#rolesloadroles) |
|
||||
| [roles.attackersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js#L71) | [過濾出攻擊方角色](#rolesattackersof) |
|
||||
| [roles.defendersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js#L90) | [過濾出防守方角色](#rolesdefendersof) |
|
||||
| [templates.toolComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L88) | [產生步驟 3 審查工具留言](#templatestoolcomment) |
|
||||
| [templates.diffComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L132) | [產生步驟 4 變更摘要留言](#templatesdiffcomment) |
|
||||
| [templates.rolesComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L173) | [產生步驟 5/7 角色登場留言](#templatesrolescomment) |
|
||||
| [templates.severeCommentBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L215) | [產生步驟 9 單條嚴重問題留言](#templatesseverecommentbody) |
|
||||
| [templates.severeReviewBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L247) | [產生步驟 9 嚴重問題 review 總覽](#templatesseverereviewbody) |
|
||||
| [templates.othersComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L277) | [產生步驟 10 警告+建議彙整表格留言](#templatesotherscomment) |
|
||||
| [templates.issueBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L309) | [產生建問題模式的 issue 本文](#templatesissuebody) |
|
||||
| [templates.issueFindingComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L341) | [產生建問題模式單條問題的 issue 留言](#templatesissuefindingcomment) |
|
||||
| [templates.nothingToReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L379) | [產生無可審查變更留言](#templatesnothingtoreviewcomment) |
|
||||
| [review.loadReviewIgnore](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L29) | [讀取 .reviewignore 忽略前綴清單](#reviewloadreviewignore) |
|
||||
| [review.isIgnored](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L53) | [判斷檔案是否忽略不送審](#reviewisignored) |
|
||||
| [review.collectDiffRows](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L77) | [整理送審 diff 資料列(含長度上限)](#reviewcollectdiffrows) |
|
||||
| [review.fillPurposes](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L127) | [以 AI 補齊每個檔案的一行用途描述](#reviewfillpurposes) |
|
||||
| [review.runAttackers](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L276) | [攻擊方 sub agent 並行找問題並合併列表](#reviewrunattackers) |
|
||||
| [review.runDefenders](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L448) | [防守方 sub agent 裁決保留或排除](#reviewrundefenders) |
|
||||
| [review.sortFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L572) | [依嚴重度→檔案→行號排序 findings](#reviewsortfindings) |
|
||||
| [review.appendExclusions](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L521) | [誤判問題附加到 exclusions.json](#reviewappendexclusions) |
|
||||
| [review.sortFindingsForIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L596) | [依檔案→嚴重度→行號排序(建問題模式)](#reviewsortfindingsforissue) |
|
||||
| [review.selectLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L627) | [以 AI 從可用標籤挑選 issue 標籤](#reviewselectlabels) |
|
||||
| [review.createIssueWithFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L694) | [建 issue 並逐條留言問題明細](#reviewcreateissuewithfindings) |
|
||||
| [review.resolveOldComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L778) | [將 PR 舊留言標記為解決/過時](#reviewresolveoldcomments) |
|
||||
| [review.postSevereComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L855) | [嚴重問題逐條掛行留言(含降級)](#reviewpostseverecomments) |
|
||||
| [log.taipeiNow](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js) | [取得台北時區 yyyy/MM/dd HH:mm:ss 時間字串](#logtaipeinow) |
|
||||
| [log.taipeiFileStamp](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js) | [取得檔名用時間戳 yyyy-MM-dd-HH:mm:ss](#logtaipeifilestamp) |
|
||||
| [log.taipeiFromIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js) | [將 ISO 時間字串轉為台北時區顯示字串](#logtaipeifromiso) |
|
||||
| [log.log](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js) | [以統一格式輸出一行日誌](#loglog) |
|
||||
| [context.loadContext](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/context.js) | [彙整 runner 環境變數與事件 payload 為執行上下文](#contextloadcontext) |
|
||||
| [gitrepo.latestCommitSubject](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js) | [取得最新 commit 的訊息標題](#gitrepolatestcommitsubject) |
|
||||
| [gitrepo.resolveMergeBase](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js) | [解析 base 分支與 HEAD 的 merge-base](#gitreporesolvemergebase) |
|
||||
| [gitrepo.changedFiles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js) | [列出 base 與 HEAD 之間有變更的檔案](#gitrepochangedfiles) |
|
||||
| [gitrepo.fileDiff](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js) | [取得單一檔案的 git diff 內容](#gitrepofilediff) |
|
||||
| [gitrepo.fileLastUpdatedIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js) | [取得檔案最後一次 commit 的 ISO 時間](#gitrepofilelastupdatediso) |
|
||||
| [gitrepo.commitAndPushFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js) | [以 bot 身分 commit 結果檔並 push 回 PR 來源分支](#gitrepocommitandpushfindings) |
|
||||
| [gitea.whoAmI](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [取得 token 對應的使用者(bot 身分)](#giteawhoami) |
|
||||
| [gitea.createCommentOnIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [對指定編號 issue/PR 新增一般留言](#giteacreatecommentonissue) |
|
||||
| [gitea.createIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [對本次 PR 新增一般留言](#giteacreateissuecomment) |
|
||||
| [gitea.listLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [列出存取庫可用標籤](#gitealistlabels) |
|
||||
| [gitea.createIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [在存取庫建立 issue(可掛標籤)](#giteacreateissue) |
|
||||
| [gitea.listIssueComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [列出 PR 全部一般留言(自動分頁)](#gitealistissuecomments) |
|
||||
| [gitea.editIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [編輯既有一般留言](#giteaeditissuecomment) |
|
||||
| [gitea.createReview](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [建立 code review 並掛行內留言](#giteacreatereview) |
|
||||
| [gitea.listReviews](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [列出 PR 全部 review(自動分頁)](#gitealistreviews) |
|
||||
| [gitea.listReviewComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [列出某 review 的全部行內留言](#gitealistreviewcomments) |
|
||||
| [gitea.tryResolveReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js) | [盡力將行內留言標記為已解決](#giteatryresolvereviewcomment) |
|
||||
| [agents.detectTool](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js) | [依優先序偵測可用的 AI CLI 工具](#agentsdetecttool) |
|
||||
| [agents.runAgent](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js) | [非互動執行一次 sub agent 並取回回覆](#agentsrunagent) |
|
||||
| [agents.extractJson](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js) | [從 agent 回覆萃取 JSON(容忍雜訊)](#agentsextractjson) |
|
||||
| [roles.loadRoles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js) | [載入角色提示檔並解析 frontmatter](#rolesloadroles) |
|
||||
| [roles.attackersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js) | [過濾出攻擊方角色](#rolesattackersof) |
|
||||
| [roles.defendersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js) | [過濾出防守方角色](#rolesdefendersof) |
|
||||
| [templates.toolComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生步驟 3 審查工具留言](#templatestoolcomment) |
|
||||
| [templates.diffComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生步驟 4 變更摘要留言](#templatesdiffcomment) |
|
||||
| [templates.rolesComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生步驟 5/7 角色登場留言](#templatesrolescomment) |
|
||||
| [templates.severeCommentBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生步驟 9 單條嚴重問題留言](#templatesseverecommentbody) |
|
||||
| [templates.severeReviewBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生步驟 9 嚴重問題 review 總覽](#templatesseverereviewbody) |
|
||||
| [templates.othersComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生步驟 10 警告+建議彙整表格留言](#templatesotherscomment) |
|
||||
| [templates.issueBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生建問題模式的 issue 本文](#templatesissuebody) |
|
||||
| [templates.issueFindingComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生建問題模式單條問題的 issue 留言](#templatesissuefindingcomment) |
|
||||
| [templates.nothingToReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js) | [產生無可審查變更留言](#templatesnothingtoreviewcomment) |
|
||||
| [review.loadReviewIgnore](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [讀取 .reviewignore 忽略前綴清單](#reviewloadreviewignore) |
|
||||
| [review.isIgnored](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [判斷檔案是否忽略不送審](#reviewisignored) |
|
||||
| [review.collectDiffRows](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [整理送審 diff 資料列(含長度上限)](#reviewcollectdiffrows) |
|
||||
| [review.fillPurposes](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [以 AI 補齊每個檔案的一行用途描述](#reviewfillpurposes) |
|
||||
| [review.runAttackers](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [攻擊方 sub agent 並行找問題並合併列表](#reviewrunattackers) |
|
||||
| [review.runDefenders](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [防守方 sub agent 裁決保留或排除](#reviewrundefenders) |
|
||||
| [review.sortFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [依嚴重度→檔案→行號排序 findings](#reviewsortfindings) |
|
||||
| [review.appendExclusions](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [誤判問題附加到 exclusions.json](#reviewappendexclusions) |
|
||||
| [review.sortFindingsForIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [依檔案→嚴重度→行號排序(建問題模式)](#reviewsortfindingsforissue) |
|
||||
| [review.selectLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [以 AI 從可用標籤挑選 issue 標籤](#reviewselectlabels) |
|
||||
| [review.createIssueWithFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [建 issue 並逐條留言問題明細](#reviewcreateissuewithfindings) |
|
||||
| [review.resolveOldComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [將 PR 舊留言標記為解決/過時](#reviewresolveoldcomments) |
|
||||
| [review.postSevereComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js) | [嚴重問題逐條掛行留言(含降級)](#reviewpostseverecomments) |
|
||||
|
||||
## 使用範例
|
||||
|
||||
|
||||
+18
-19
@@ -4,7 +4,7 @@
|
||||
console.log('================================================');
|
||||
console.log('Action : AI Code Review');
|
||||
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('================================================');
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -126,15 +126,14 @@ function commitFindings({ cwd, ctx, files, result }) {
|
||||
* 建問題模式會把審查情境與每條 finding 發到追蹤 issue;沒有保留 finding 時不建立 issue、PR 也不留言。
|
||||
* 嚴重 finding 會寫入 failure 結果 commit,警告與建議只建立追蹤資訊,不直接阻擋合併。
|
||||
*
|
||||
* @returns {Promise<number>} process exit code:本輪「審查」一律回傳 0(不因嚴重問題直接讓檢查失敗——
|
||||
* 失敗改由推出的 `[ai-review-bot][failure]` 結果 commit,於下一輪在步驟 1 讀 commit 訊息時回報);
|
||||
* 回傳 1 僅發生於:步驟 1 偵測到 `[ai-review-bot][failure]` 結果 commit,或前置條件不足
|
||||
* (缺 PR 編號/token、找不到 AI 工具)等無法進行審查的情況。
|
||||
* @returns {Promise<number>} process exit code:沒有嚴重問題且結果 commit 已成功推送時回傳 0;
|
||||
* 若步驟 1 偵測到 `[ai-review-bot][failure]`、前置條件不足,或需要推送結果檔卻未成功推送,
|
||||
* 則回傳 1,避免 token 權限不足或遠端競態讓 workflow 靜默通過。
|
||||
* @remarks
|
||||
* 使用情境:由本檔尾端的頂層呼叫端執行 —— `main().then((code) => process.exit(code))`;
|
||||
* 非預期例外由頂層 `catch` 記 ERR log 後以 exit code 1 收場,且刻意不 commit 結果標記,
|
||||
* 讓下一次 workflow 觸發時重新完整審查。警告+建議等級不影響結果標記,只有「嚴重」會使結果 commit
|
||||
* 標記為 failure;而「失敗檢查(exit 1)」只由步驟 1 讀到該 failure 結果 commit 時產生,審查本輪不直接 exit 1。
|
||||
* 標記為 failure;若結果檔需要 push 卻失敗,本輪直接 exit 1,避免缺權限時無聲放行。
|
||||
* 建問題模式只改變問題明細的落地方式(issue 留言取代 findings 進版控),不改變上述結果標記判定。
|
||||
*/
|
||||
async function main() {
|
||||
@@ -193,7 +192,7 @@ async function main() {
|
||||
return created;
|
||||
};
|
||||
/**
|
||||
* 建問題模式:建立追蹤 issue(標題=PR 標題、本文=PR 描述+回溯 PR 的引言,連同挑好的標籤一次建立),
|
||||
* 建問題模式:開啟追蹤 issue(標題=PR 標題、本文=PR 描述+回溯 PR 的引言,連同挑好的標籤一次建立),
|
||||
* 並把 `pendingIssueCommentBodies` 內暫存的情境留言依流程順序寫入 issue;
|
||||
* 設定閉包變數 `trackingIssue` 供後續留言直接發到 issue。
|
||||
* 僅於「確定有保留問題」時呼叫一次。標籤於建立時一次帶入,省去「先建空標籤 issue 再補掛」的多餘 API 往返。
|
||||
@@ -202,17 +201,18 @@ async function main() {
|
||||
* 空陣列或省略時不掛任何標籤(`gitea.createIssue` 對空陣列不帶 labels 欄位)。
|
||||
* @returns {Promise<void>} 無回傳值;結果反映在閉包變數 `trackingIssue` 與 issue 留言。
|
||||
*/
|
||||
const createIssueAndFlushBufferedComments = async (labelIds = []) => {
|
||||
const openTrackingIssue = async (labelIds = []) => {
|
||||
trackingIssue = await gitea.createIssue(ctx, {
|
||||
title: ctx.prTitle || `AI Code Review:PR #${ctx.prNumber}`,
|
||||
body: templates.issueBody({ prNumber: ctx.prNumber, prBody: ctx.prBody }),
|
||||
labels: labelIds,
|
||||
});
|
||||
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);
|
||||
pendingIssueCommentBodies.shift();
|
||||
}
|
||||
pendingIssueCommentBodies.length = 0;
|
||||
};
|
||||
/**
|
||||
* 建問題模式降級:追蹤 issue 無法建立或寫入時,改把已暫存的情境留言發回 PR,後續沿用一般模式。
|
||||
@@ -337,7 +337,7 @@ async function main() {
|
||||
log('建問題', 'WRN', `標籤挑選失敗(${err.message}),issue 不掛標籤。`);
|
||||
}
|
||||
try {
|
||||
await createIssueAndFlushBufferedComments(labelIds);
|
||||
await openTrackingIssue(labelIds);
|
||||
} catch (err) {
|
||||
log('建問題', 'WRN', `建立或寫入追蹤 issue 失敗(${err.message}),改用 PR 留言與 findings 檔流程。`);
|
||||
await fallbackToPrComments();
|
||||
@@ -377,7 +377,7 @@ async function main() {
|
||||
}
|
||||
|
||||
// ── 建問題模式收束:在 PR 回貼 issue 連結(雙向關聯);僅在有嚴重問題時才讓 PR 相依於該 issue ─
|
||||
// 標籤已於建立 issue 時一次帶入(見上方 selectLabels → createIssueAndFlushBufferedComments),此處不再補掛。
|
||||
// 標籤已於建立 issue 時一次帶入(見上方 selectLabels → openTrackingIssue),此處不再補掛。
|
||||
if (issueModeActive && trackingIssue) {
|
||||
await review.resolveOldIssueLinkComments({ ctx, gitea });
|
||||
await gitea.createIssueComment(
|
||||
@@ -420,14 +420,13 @@ async function main() {
|
||||
} else {
|
||||
log('收尾', 'INF', '建問題模式且 exclusions.json 無變更,略過 commit/push。');
|
||||
}
|
||||
// 本輪「審查」一律以成功收場、不直接讓檢查失敗;有嚴重問題時已推出 [failure] 結果 commit,
|
||||
// 由它再觸發的下一輪在步驟 1 讀 commit 訊息時才回報失敗(exit 1)。如此失敗檢查落在帶有結果
|
||||
// 標記的最新 head 上,與合併判定一致。(result 僅用於上方 commit 訊息的結果標記。)
|
||||
// 需要推送結果檔卻沒成功時直接失敗;這通常代表 token 權限不足、非快轉或分支保護阻擋。
|
||||
if (review.shouldFailMissingResultCommit({ filesToCommit, resultCommitted })) {
|
||||
log('收尾', 'ERR', '審查結果檔需要 push 但未成功產生結果 commit;直接回報失敗避免缺權限時靜默通過。');
|
||||
return 1;
|
||||
}
|
||||
// 嚴重問題已推出 [failure] 結果 commit 時,由它再觸發的下一輪在步驟 1 讀 commit 訊息回報失敗。
|
||||
if (result === 'failure') {
|
||||
if (!resultCommitted) {
|
||||
log('收尾', 'ERR', '本輪有嚴重問題,但未成功產生 [failure] 結果 commit;直接回報失敗避免 fail-open。');
|
||||
return 1;
|
||||
}
|
||||
log('收尾', 'INF', '本輪有嚴重問題:已標記結果 commit 為 [failure],失敗檢查由下一輪步驟 1 讀 commit 訊息回報。');
|
||||
}
|
||||
return 0;
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ const path = require('path');
|
||||
* - owner / repo:自 repository 拆出的擁有者與專案名,缺值時為空字串。
|
||||
* - apiBase:Gitea REST API 基底網址(`<serverUrl>/api/v1`)。
|
||||
* - token:action input `token`(INPUT_TOKEN),用於 Gitea API 認證,以及 push findings/exclusions
|
||||
* commit 回 repo;建議為「能觸發 CI 的 PAT」(自動 token 推送不會再觸發 CI)。缺值時為空字串。
|
||||
* commit 回 repo;建議為 repo 限定、最小權限且可輪替的 token。缺值時為空字串。
|
||||
* - model:action input `model`(INPUT_MODEL,已 trim),指定 AI 模型,缺值時為空字串。
|
||||
* - createIssue:action input `create-issue`(INPUT_CREATE-ISSUE),是否將問題建到
|
||||
* 存取庫的問題追蹤(建問題模式);trim + 小寫後與字串 'true' 嚴格比對,預設 false。
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
// AI CLI 失敗診斷與機密遮罩工具:供 review 流程記錄安全、限長的一行錯誤摘要。
|
||||
|
||||
// 遮罩前先截去的輸入上限,避免對數 MB 失敗輸出跑整份 O(k*n) 正規掃描。
|
||||
const AGENT_DIAGNOSTIC_INPUT_LIMIT = 2_000;
|
||||
// 每段診斷片段(stderr/stdout)寫入日誌的字元上限。
|
||||
const AGENT_DIAGNOSTIC_OUTPUT_LIMIT = 500;
|
||||
|
||||
@@ -24,6 +22,9 @@ function redactSecrets(text) {
|
||||
.replace(/(authorization\s*[:=]\s*)(?:bearer\s+)?\S+/gi, '$1***')
|
||||
.replace(/((?:api[_-]?key|token|password|secret|bearer)\s*[:=]\s*)\S+/gi, '$1***')
|
||||
.replace(/(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi, '$1***:***@')
|
||||
.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, '***')
|
||||
.replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '***')
|
||||
.replace(/-----BEGIN [^-]+ PRIVATE KEY-----[\s\S]*?-----END [^-]+ PRIVATE KEY-----/g, '***')
|
||||
.replace(/\bgh[pousr]_[A-Za-z0-9]{16,}\b/g, '***')
|
||||
.replace(/\b[A-Za-z0-9_-]{40,}\b/g, '***')
|
||||
.trim();
|
||||
@@ -38,8 +39,8 @@ function redactSecrets(text) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function appendRedactedOutput(parts, label, value) {
|
||||
const redacted = redactSecrets(String(value || '').slice(0, AGENT_DIAGNOSTIC_INPUT_LIMIT));
|
||||
if (redacted) parts.push(`${label}:${redacted.slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT)}`);
|
||||
const redacted = redactSecrets(String(value || '').slice(0, AGENT_DIAGNOSTIC_OUTPUT_LIMIT));
|
||||
if (redacted) parts.push(`${label}:${redacted}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +76,6 @@ function agentFailureDetail(agentResult) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AGENT_DIAGNOSTIC_INPUT_LIMIT,
|
||||
AGENT_DIAGNOSTIC_OUTPUT_LIMIT,
|
||||
redactSecrets,
|
||||
agentFailureDetail,
|
||||
|
||||
+12
-2
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const { assertSafeBranchRef } = require('./gitref');
|
||||
|
||||
// git 操作工具:一律以 execFileSync 呼叫 git(不經 shell,避免注入),輸出以 UTF-8 回傳。
|
||||
@@ -64,6 +65,15 @@ function tryGit(cwd, ...args) {
|
||||
}
|
||||
}
|
||||
|
||||
function isShallowRepository(cwd) {
|
||||
try {
|
||||
const shallowPath = gitTrim(cwd, 'rev-parse', '--git-path', 'shallow');
|
||||
return fs.existsSync(shallowPath);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得目前 HEAD 最新一筆 commit 的訊息標題(commit message 第一行)。
|
||||
*
|
||||
@@ -142,7 +152,7 @@ function resolveMergeBase(cwd, baseRef) {
|
||||
['deepen base', 'fetch', '--no-tags', '--deepen=1000', 'origin', `+refs/heads/${baseRef}:refs/remotes/${remoteBase}`],
|
||||
['deepen PR HEAD', 'fetch', '--no-tags', '--deepen=1000', 'origin', headSha],
|
||||
];
|
||||
if (gitTrim(cwd, 'rev-parse', '--is-shallow-repository') === 'true') {
|
||||
if (isShallowRepository(cwd)) {
|
||||
strategies.push(['unshallow', 'fetch', '--no-tags', '--unshallow', 'origin']);
|
||||
}
|
||||
|
||||
@@ -237,7 +247,7 @@ function fileLastUpdatedIso(cwd, file) {
|
||||
* @param {string} [options.headSha] - PR head 的 commit SHA;有提供且與目前 HEAD 不同時會先 detach 到此 commit。可省略(falsy 時不 detach,直接於目前 HEAD 上 commit)。
|
||||
* @param {string} options.message - commit 訊息。
|
||||
* @param {string[]} options.files - 要加入 commit 的檔案路徑清單(相對 repo 根目錄);全數無實際變更時不 commit、回傳 false。
|
||||
* @param {string} options.token - 具該 repo push 權限的 Gitea access token(建議為能觸發 CI 的 PAT);用於 findings commit 的認證推送。
|
||||
* @param {string} options.token - 具該 repo push 權限的 Gitea access token(建議 repo 限定、最小權限且可輪替);用於 findings commit 的認證推送。
|
||||
* @param {string} options.serverUrl - Gitea 伺服器根網址(例如 https://gitea.example.com),須為合法 URL。
|
||||
* @param {string} options.repository - repo 完整名稱(owner/repo 格式),與 serverUrl 組成 clone URL。
|
||||
* @returns {boolean} true=有變更且已 commit 並 push 到來源分支;false=暫存區與 HEAD 無差異,略過 commit/push。
|
||||
|
||||
@@ -579,6 +579,18 @@ function resultFilesToCommit({ createIssue, severeCount, relativePath, exclusion
|
||||
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:依 嚴重→警告→建議、再依檔案路徑、再依起始行遞增。
|
||||
*
|
||||
@@ -914,6 +926,7 @@ module.exports = {
|
||||
sortFindings,
|
||||
appendExclusions,
|
||||
resultFilesToCommit,
|
||||
shouldFailMissingResultCommit,
|
||||
selectLabels,
|
||||
postSevereToIssue,
|
||||
postOthersToIssue,
|
||||
|
||||
@@ -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', () => {
|
||||
assert.throws(
|
||||
() => gitrepo.resolveMergeBase(process.cwd(), '../../hooks/pre-push'),
|
||||
|
||||
@@ -44,6 +44,25 @@ test('agentFailureDetail 在 debug 模式輸出遮罩後片段', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('agentFailureDetail 先限長再遮罩並處理常見 PII', () => {
|
||||
const oldDebug = process.env.ACTIONS_STEP_DEBUG;
|
||||
process.env.ACTIONS_STEP_DEBUG = 'true';
|
||||
try {
|
||||
const detail = diagnostics.agentFailureDetail({
|
||||
ok: false,
|
||||
error: Object.assign(new Error('boom'), { code: 3 }),
|
||||
stderr: `${'x'.repeat(520)} token=should-not-be-scanned`,
|
||||
output: 'user@example.test eyJaaaaaaaaaa.bbbbbbbbbb.cccccccccc',
|
||||
});
|
||||
assert.match(detail, /exit 3/);
|
||||
assert.doesNotMatch(detail, /should-not-be-scanned|user@example.test|eyJaaaaaaaaaa/);
|
||||
assert.match(detail, /stdout:\*\*\* \*\*\*/);
|
||||
} finally {
|
||||
if (oldDebug === undefined) delete process.env.ACTIONS_STEP_DEBUG;
|
||||
else process.env.ACTIONS_STEP_DEBUG = oldDebug;
|
||||
}
|
||||
});
|
||||
|
||||
test('postOthersToIssue 依序送出 issue 留言以維持排序', async () => {
|
||||
const calls = [];
|
||||
let active = 0;
|
||||
@@ -98,6 +117,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 () => {
|
||||
const edited = [];
|
||||
const fakeGitea = {
|
||||
|
||||
Reference in New Issue
Block a user