Files
ai-code-review/.gitea/ai-review/findings/2026-07-20-18:31:07.json
T
JefferyandClaude Opus 4.8 d7cecc8fa8
node-actions/template: CI / BUILD (pull_request) Successful in 4s
CI / TEST (Claude) (pull_request) Successful in 24s
CI / TEST (Antigravity) (pull_request) Successful in 58s
CI / TEST (Codex) (pull_request) Successful in 2m51s
chore(ai-review findings): 寫回議題 #11–#23 只來自議題的待人工 findings
以 --issue all 處理 13 個建問題模式追蹤議題,逐條對照目前程式碼與 exclusions.json 後:
 已解決 18 條(現行程式碼已修)、🚫 誤報 28 條(已列入 exclusions,不重複新增)、
⏭️ 待人工 44 條(設計/效能/慣例取捨,依主題去重寫回 42 條追蹤)。各議題已留言處理進度並關閉。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:33:01 +08:00

602 lines
40 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"generatedAt": "2026/07/20 18:31:07",
"commitSha": "0c29daa01434d6b6fa1b20ce81c513c75b1b1c6a",
"prNumber": null,
"tool": {
"name": "code-review-resolve",
"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": "F003",
"reviewer": "Leo",
"focus": "",
"badge": "🧰",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 13,
"endLine": 82,
"problem": "`agentFailureDetail()` 同時負責解析程序失敗、決定 debug 政策、讀取全域環境變數、截斷輸出及遮罩機密。尤其直接讀取 `process.env.ACTIONS_STEP_DEBUG` 形成隱藏相依,測試不同輸出政策時必須修改程序全域狀態;日後若其他呼叫端需要不同診斷層級,也只能繼續往這個函式堆條件。",
"suggestion": "把診斷政策改成明確參數,並將「錯誤中繼資料整理」與「輸出片段清理」拆成小函式;在 `main` 或 context 載入階段解析環境設定後注入。這能讓各種 exit code、signal、空輸出與 verbose 模式以純輸入輸出直接測試。",
"suggestedCode": "```\nfunction agentFailureDetail(res, { includeStdout = false, inputLimit = 2000, outputLimit = 500 } = {}) {\n const parts = failureMetadata(res && res.error);\n appendSanitizedOutput(parts, 'stderr', res && res.stderr, inputLimit, outputLimit);\n if (includeStdout) {\n appendSanitizedOutput(parts, 'stdout', res && res.output, inputLimit, outputLimit);\n }\n return parts.length ? parts.join('') : 'AI CLI 執行失敗(無診斷輸出)';\n}\n```",
"sourceIssue": 12
},
{
"id": "F004",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "警告",
"file": "src/lib/review.js",
"startLine": 42,
"endLine": 61,
"problem": "`agentFailureDetail` 的文件自相矛盾:開頭宣稱「原始輸出預設隱藏」,後文卻明言預設附上經遮罩的 `stderr` 與 `stdout` 片段;另提到 `ACTIONS_STEP_DEBUG`,函式內卻沒有相應分支。註解與實作各唱各的調,維護者無法從文件判斷實際日誌行為。",
"suggestion": "統一文件敘述為實際行為:預設輸出經遮罩且限長的診斷片段;若目前並未依 `ACTIONS_STEP_DEBUG` 改變輸出,就移除該段說明,或待真正實作開關後再補上。",
"suggestedCode": "",
"sourceIssue": 13
},
{
"id": "F005",
"reviewer": "Rogue",
"focus": "",
"badge": "⚡",
"severity": "嚴重",
"file": "src/index.js",
"startLine": 216,
"endLine": 218,
"problem": "在 `ensureIssueCreated` 函式中,使用 `for...of` 迴圈搭配 `await` 來逐條對 Gitea API 發送留言請求。由於網路請求存在延遲(每次 RTT 約 100-300ms),在迴圈內阻塞式等待會導致整體執行時間隨留言數量線性增加,浪費大量 CPU 週期與網路連線資源。",
"suggestion": "可以將 `issueBuffer` 中的所有情境留言合併為單一 Markdown 留言發送,僅需一次 API 呼叫;或者使用 `Promise.all` 將這些無相依性的留言請求並行化發送,大幅降低總延遲。",
"suggestedCode": "```\nif (issueBuffer.length > 0) {\n const combinedBody = issueBuffer.join('\\n\\n---\\n\\n');\n await gitea.createCommentOnIssue(ctx, issue.number, combinedBody);\n }\n issueBuffer.length = 0;\n```",
"sourceIssue": 14
},
{
"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": "F008",
"reviewer": "Assassin",
"focus": "",
"badge": "🗡️",
"severity": "警告",
"file": "src/lib/gitrepo.js",
"startLine": 299,
"endLine": 299,
"problem": "在 `pushWithCredential` 中,環境變數 `GIT_CONFIG_KEY_0` 被動態拼接為 `http.${remoteUrl}.extraheader`。如果 `remoteUrl` 來自外部或未經嚴格驗證的 context,且 URL 中包含特殊字元(如點號、路徑分隔符號或引號等),可能會導致 Git 配置解析錯誤,或在特定平台下引發 Git 配置參數注入風險。",
"suggestion": "由於該執行程序僅為一次性的 `git push` 操作,可直接將該憑證應用於所有 HTTP 請求,將 `GIT_CONFIG_KEY_0` 設定為靜態的 `http.extraheader`,以避免動態拼接 URL 所帶來的注入風險。",
"suggestedCode": "```\nGIT_CONFIG_KEY_0: 'http.extraheader',\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": "F011",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 33,
"endLine": 34,
"problem": "在 redactSecrets 函式中,針對 authorization 以及其他憑證關鍵字(如 token、secret、password 等)的敏感資訊遮蔽,分別使用了兩條結構極為相似的正規表示式進行替換。這造成了重複的替換邏輯與額外的處理開銷,程式碼的旋律顯得不夠俐落。",
"suggestion": "建議將這兩條正規表示式合併為單一表達式,消除重複的 replace 呼叫,使程式碼更加簡潔優雅且提升運行效率。",
"suggestedCode": "```\n.replace(/((?:authorization|api[_-]?key|token|password|secret|bearer)\\s*[:=]\\s*)\\S+/gi, '$1***')\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": "F013",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/templates.js",
"startLine": 335,
"endLine": 337,
"problem": "issueFindingComment 函式的 @remarks 文件註解中,說明其使用情境為『建問題模式下 review.postSevereToIssue 把每條嚴重 finding... 作為問題明細的追蹤紀錄』。然而實際上,非嚴重的警告與建議(others)也會透過 review.postOthersToIssue 呼叫此模板進行發布,導致文件描述不夠完整。",
"suggestion": "修正 @remarks 的使用情境說明,將 postOthersToIssue 亦併入描述中,使 JSDoc 文件能如實且精準地反映實際程式碼的呼叫情境。",
"suggestedCode": "```\n* 使用情境:建問題模式下 `review.postSevereToIssue` 與 `review.postOthersToIssue`src/lib/review.js)把保留的各級問題明細\\n * 以本函式產生留言內容、經 `gitea.createCommentOnIssue` 發布到追蹤 issue 上,\\n * 作為問題明細的追蹤紀錄。\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": "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": "F016",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 28,
"endLine": 37,
"problem": "這段註解的旋律前後打架:開頭說「原始輸出預設隱藏」,後文卻說失敗時預設附上遮罩後的 stderr/stdout 片段。讀者才剛建立心智模型,下一拍就被改調。",
"suggestion": "請讓摘要句與實際行為一致,明確說明「原始輸出不直接輸出,但會輸出遮罩與截斷後的診斷片段」。",
"suggestedCode": "```\n* 從 `runAgent` 的失敗結果組出可診斷的一行摘要:退出碼/訊號為主,並附上遮罩、去控制字元且截斷後的 stderr/stdout 片段。\n```",
"sourceIssue": 15
},
{
"id": "F017",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/templates.js",
"startLine": 334,
"endLine": 341,
"problem": "`issueFindingComment` 的註解只提到 `review.postSevereToIssue`,但主流程也以 `postOthersToIssue` 發布警告與建議。文件把共用模板寫成嚴重問題專用,讀起來像少了一個聲部。",
"suggestion": "把 remarks 改成同時涵蓋嚴重、警告與建議的 issue 留言產生器,避免維護者誤以為它只服務嚴重 finding。",
"suggestedCode": "",
"sourceIssue": 15
},
{
"id": "F018",
"reviewer": "Assassin",
"focus": "",
"badge": "🗡️",
"severity": "嚴重",
"file": "src/lib/gitrepo.js",
"startLine": 102,
"endLine": 149,
"problem": "攻擊者可以透過提交惡意 Pull Request,將 `baseRef`PR 目標分支)命名為包含路徑穿越(Path Traversal)的字串,例如 `../../hooks/pre-push`。由於 `resolveMergeBase` 直接將 `baseRef` 拼接至 `git fetch` 的 Refspec 參數中(例如 `+refs/heads/${baseRef}:refs/remotes/origin/${baseRef}`),這將導致 `git fetch` 寫入至 `.git/refs/remotes/origin/../../hooks/pre-push`(即 `.git/hooks/pre-push`)。這會覆寫或建立 Git Hook,並在後續執行 git 操作時自動觸發該惡意 Hook,從而造成遠端程式碼執行(RCE)。同樣地,`commitAndPushFindings` 函數中的 `headRef` 也存在類似的拼接風險。",
"suggestion": "在將 `baseRef` 與 `headRef` 傳入 git 指令之前,應進行嚴格的合法性檢查。建議使用正則表達式限制分支名稱僅能包含安全的字元(如英數字、斜線、底線、連字號、句點),且絕對不得含有 `..` 或以 `-` 開頭,必要時亦可使用 `git check-ref-format` 命令先行驗證該分支名稱是否安全。",
"suggestedCode": "```\nfunction resolveMergeBase(cwd, baseRef) {\n // 嚴格的分支名稱白名單檢查,防止路徑穿越與參數注入\n const safeBranchRegex = /^(?!-)(?!.*?\\.\\.)[a-zA-Z0-9/_.-]+$/;\n if (!safeBranchRegex.test(baseRef)) {\n throw new Error(`偵測到不合法的分支名稱: ${baseRef}`);\n }\n const remoteBase = `origin/${baseRef}`;\n```",
"sourceIssue": 16
},
{
"id": "F019",
"reviewer": "Leo",
"focus": "",
"badge": "🧰",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 13,
"endLine": 72,
"problem": "`redactSecrets()` 與 `agentFailureDetail()` 是低階日誌診斷/遮罩邏輯,現在放在 `review.js` 這個負責 diff 整理與審查決策的模組頂端。這會讓 `review.js` 的職責繼續膨脹:未來若其他模組也要安全輸出 CLI 錯誤,只能複製這段或反向依賴 review 模組,邊界會越來越不清楚。",
"suggestion": "把這兩個函式搬到專門的工具模組,例如 `src/lib/diagnostics.js` 或 `src/lib/log-redaction.js`,並由 `review.js` 引入。這樣遮罩規則可集中測試與重用,`review.js` 也能維持在「審查流程資料處理」的邊界內。",
"suggestedCode": "",
"sourceIssue": 17
},
{
"id": "F020",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 41,
"endLine": 45,
"problem": "這段註解的旋律前後走調:摘要先說「原始輸出預設隱藏」,下一段卻說失敗時「預設附上 stderr 與 stdout」。讀者還沒進函式本體,文件本身就已經互相拉扯。",
"suggestion": "請讓摘要與實作同拍,直接說明會輸出經遮罩與限長的診斷片段;若真的要隱藏原始輸出,也應同步改實作。",
"suggestedCode": "```\n* 從 `runAgent` 的失敗結果組出可診斷的一行摘要:退出碼/訊號為主,並附上經遮罩與限長的 stderrstdout 片段。\n```",
"sourceIssue": 17
},
{
"id": "F021",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/templates.js",
"startLine": 334,
"endLine": 338,
"problem": "`issueFindingComment` 的文件只唱「嚴重 finding」,但新版流程也讓警告與建議逐條發到 issue。函式名稱是通用的,註解卻把用途寫窄,後續讀者會誤以為它只服務嚴重問題。",
"suggestion": "把註解改成涵蓋所有 finding 等級,讓文件與函式名稱、呼叫情境保持一致。",
"suggestedCode": "```\n* 使用情境:建問題模式下,`review.postSevereToIssue` 與 `review.postOthersToIssue`\n * 會把各等級 finding 以本函式產生留言內容、經 `gitea.createCommentOnIssue`\n * 發布到追蹤 issue 上,作為問題明細的追蹤紀錄。\n```",
"sourceIssue": 17
},
{
"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": "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` 為 0action 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",
"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": "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": "F028",
"reviewer": "Leo",
"focus": "",
"badge": "🧰",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 14,
"endLine": 75,
"problem": "`review.js` 這次新增 `redactSecrets()` 與 `agentFailureDetail()`,但這兩個函式處理的是 AI CLI 執行失敗診斷與機密遮罩,責任更接近 `agents.js` 或共用 log/sanitize 工具。現在審查結果整理模組同時負責 diff、裁決、issue 發文與 CLI 診斷格式,模組邊界越來越鬆;之後其他地方若也要記錄 agent 失敗,很容易複製一份遮罩邏輯或反向依賴 `review.js`。",
"suggestion": "將這兩個函式移到 `src/lib/agents.js`(例如匯出 `formatAgentFailure()`),或新增 `src/lib/sanitize.js`/`src/lib/diagnostics.js`。`review.js` 只消費格式化後的錯誤摘要,避免讓審查編排模組承擔 CLI 診斷細節。",
"suggestedCode": "",
"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": "F030",
"reviewer": "Rogue",
"focus": "",
"badge": "⚡",
"severity": "警告",
"file": "src/index.js",
"startLine": 369,
"endLine": 370,
"problem": "建問題模式把所有警告/建議改成逐條發 issue 留言,這裡會把 `others.length` 放大成 N 次遠端 POST;正常模式同一批資料只產生 1 則彙整表格留言。只要 AI 回出數十條警告,CI 時間就會被 API round-trip 線性吃掉,還更容易撞上 Gitea rate limit 或暫時性網路延遲。",
"suggestion": "警告/建議維持批次彙整成單一留言;只有嚴重問題需要逐條追蹤時再拆開。若產品需求一定要逐條回覆,至少在 `postOthersToIssue` 內用有上限的並行池,不要一筆等一筆。",
"suggestedCode": "```\nif (others.length > 0) {\n if (ctx.createIssue) {\n await gitea.createCommentOnIssue(ctx, issue.number, templates.othersComment(others));\n log('步驟10', 'INF', `警告+建議表格留言已發布到 issue${others.length} 條)。`);\n } else {\n await postComment(templates.othersComment(others));\n log('步驟10', 'INF', `警告+建議表格留言已發布(${others.length} 條)。`);\n }\n}\n```",
"sourceIssue": 20
},
{
"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": "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": "F034",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 36,
"endLine": 43,
"problem": "`agentFailureDetail` 的 JSDoc 先說「原始輸出預設隱藏」,下一段又說「預設附上 stderr 與 stdout 片段」。同一段說明前後轉調,讀者會搞不清楚失敗診斷到底會不會輸出 CLI 內容。",
"suggestion": "請統一描述:若設計是輸出已遮罩片段,就刪掉「預設隱藏」;若設計是隱藏原始輸出,就把後段改成條件式說明。",
"suggestedCode": "",
"sourceIssue": 21
},
{
"id": "F035",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/templates.js",
"startLine": 334,
"endLine": 338,
"problem": "`issueFindingComment` 是通用 finding 留言模板,但更新後的說明只寫 `review.postSevereToIssue` 與「嚴重 finding」。然而主流程也將警告/建議逐條發到 issue,這段文件把模板唱窄了,和實際用途不一致。",
"suggestion": "把 remarks 改成涵蓋嚴重、警告與建議的通用 issue finding 留言,避免日後維護者誤以為此模板只能用於嚴重問題。",
"suggestedCode": "",
"sourceIssue": 21
},
{
"id": "F036",
"reviewer": "Rogue",
"focus": "",
"badge": "⚡",
"severity": "警告",
"file": "src/index.js",
"startLine": 305,
"endLine": 305,
"problem": "攻擊方沒有找出任何 finding 時,這裡還是照樣啟動防守方 `runDefenders`。空陣列沒有東西可裁決,卻會多跑一輪 AI CLI/sub agent、讀 exclusions/history、組 prompt;每個乾淨 PR 都被偷走 1 次防守方呼叫的 CPU、等待時間與 token。",
"suggestion": "在 `findings.length === 0` 時直接略過防守方裁決,令 `kept/excluded` 都是空陣列,直接進入保存結果與收尾。這不是微優化,是整輪 AI 呼叫直接歸零。",
"suggestedCode": "```\nconst { kept, excluded } = findings.length === 0\n ? { kept: [], excluded: [] }\n : await review.runDefenders({ tool, model: ctx.model, cwd, defenders, findings });\n```",
"sourceIssue": 22
},
{
"id": "F037",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/gitrepo.js",
"startLine": 310,
"endLine": 310,
"problem": "`pushWithCredential` 的參數名叫 `secret`,但同一檔其他區段與呼叫端都稱它為 `token`;同一個旋律忽然換調,讀者需要多花心力確認這是不是另一種憑證。",
"suggestion": "沿用既有命名,把 `secret` 改成 `token`,並同步調整 JSDoc 與 `Buffer.from` 內的引用。",
"suggestedCode": "```\nfunction pushWithCredential(cwd, remoteUrl, token, refspec, serverUrl) {\n const basic = Buffer.from(`ai-review-bot:${token}`).toString('base64');\n // ...\n}\n```",
"sourceIssue": 22
},
{
"id": "F038",
"reviewer": "Leo",
"focus": "",
"badge": "🧰",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 31,
"endLine": 46,
"problem": "`agentFailureDetail` 的註解前後語意不一致:開頭寫「原始輸出預設隱藏」,但後段又說預設會附上遮罩後的 stderr/stdout;最後還提到用 `ACTIONS_STEP_DEBUG=true` 取得原始輸出,但程式碼沒有任何 debug flag 分支。這種文件與實作脫節,會讓未來維護者誤判 CI log 會暴露多少診斷內容。",
"suggestion": "把註解改成符合目前實作:預設輸出限長且遮罩後的 stderr/stdout;若要支援 debug 模式,再補實作分支。不要在註解承諾程式沒有做的行為。",
"suggestedCode": "",
"sourceIssue": 22
},
{
"id": "F039",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/templates.js",
"startLine": 337,
"endLine": 340,
"problem": "`issueFindingComment` 是一般 finding 留言模板,但註解只寫 `review.postSevereToIssue` 的嚴重問題用途;和主流程中「警告+建議也逐條發到 issue」的描述不一致,註解像只唱了半段副歌。",
"suggestion": "把 remarks 改成涵蓋嚴重、警告與建議的共用用途,避免後續維護者誤以為此模板只服務嚴重問題。",
"suggestedCode": "",
"sourceIssue": 22
},
{
"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 AppGitea App token,並明確禁止在不可信 fork PR 傳入可寫 token。若目標只是回報檢查結果,優先用 status/check API 寫結果,不要靠 PAT push 再觸發下一輪 CI。",
"suggestedCode": "```\ndescription: 'Gitea API tokenPR/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
},
{
"id": "F042",
"reviewer": "Bard",
"focus": "",
"badge": "🎼",
"severity": "建議",
"file": "src/lib/review.js",
"startLine": 31,
"endLine": 40,
"problem": "這段註解的旋律前後失和:開頭寫「原始輸出預設隱藏」,下一句卻說預設附上 stderr 與 stdout 片段。讀者尚未進入程式碼,就已被兩個互相拉扯的描述絆住。",
"suggestion": "請讓文件只唱一個調性:若目前設計是預設輸出遮罩後的診斷片段,就刪掉「原始輸出預設隱藏」或改成「原始輸出會先遮罩與截斷」。",
"suggestedCode": "",
"sourceIssue": 23
}
],
"excluded": []
}