Files
release-cleanup/.gitea/ai-review/exclusions.json
T
JefferyandClaude Opus 4.8 9e598143bf
CI / AI Code Review (pull_request) Failing after 55s
chore(ai-review): 更新 findings.json 與 exclusions.json
移除本輪已修復(防禦異常結構)與重複提報的 finding;保留 4 條錯誤處理類設計取捨;
將 config.js replace 型別 finding 登記為不成立(已有 typeof 防護)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:59:49 +08:00

147 lines
10 KiB
JSON

[
{
"location": "app/config.js:31",
"role": "Bard",
"original_finding": "日志信息的键值对缺少空格,排版不够整齐统一。建议加上空格以提升易读性:`info('GITEA_TOKEN = [redacted]')`",
"reason": "誤報。loadConfig 中所有參數記錄皆採一致的 `KEY=value` 格式(如 GITEA_SERVER_URL=...、KEEP_COUNT=...);僅將 GITEA_TOKEN 一行改為 `KEY = value` 反而破壞整體一致性,故不採納。"
},
{
"location": "Dockerfile:13",
"role": "Bard",
"original_finding": "请在冒号后面加上一个空格,让注释读起来更舒畅:`# 基底映像: Node.js 20 的 Alpine 版本 (體積小)。`",
"reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)"
},
{
"location": "Dockerfile:16",
"role": "Bard",
"original_finding": "在括号前面增加一个空格:`# 複製 Node.js 應用程式 (不含測試)`",
"reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)"
},
{
"location": "app/logger.js:5",
"role": "Bard",
"original_finding": "分隔線字串為魔術字串,硬編碼在模組頂層,不易維護與調整。建議將分隔線管理集中化,並考慮提供動態產生方法。",
"reason": "不適用。分隔線已是模組頂層的具名常數(LINE/SUBLINE),本即集中管理且為通用良好實務;固定寬度分隔線改為動態產生方法只會徒增複雜度,無實質效益,故不採納。"
},
{
"location": "app/gitea-client.js:63",
"role": "Mage",
"original_finding": "while 迴圈中使用 AbortSignal.timeout,若 Node.js 版本低於 16 會崩潰。建議改用 AbortController 搭配 setTimeout。",
"reason": "不適用。本專案以 node:20-alpine 為基底且 package.json engines 限定 >=18,AbortSignal.timeout 自 Node 17.3 起即支援,執行環境保證可用,無向下相容需求。"
},
{
"location": "app/gitea-client.js:101",
"role": "Rogue",
"original_finding": "處理分頁資料用 for...of 逐一 all.push(item) 效率不佳,建議用 Array.prototype.push.apply(all, items)。",
"reason": "不採納且會造成回歸。先前依 Bard(gitea-client.js:59)建議刻意改用 for...of 以規避展開/apply 在大量項目時的呼叫堆疊上限風險;push.apply 等同展開,會重新引入該風險。逐頁項目數量有限,迴圈開銷可忽略。"
},
{
"location": "app/validate.js:14",
"role": "Mage",
"original_finding": "isEmptyOrNull 判斷式包含 value === 'null',導致字串 'null' 被判定為空,破壞語義一致性。建議移除對 'null' 字串的特殊判定。",
"reason": "不採納。此為刻意保留原 bash 版本 is_empty_or_null 的語義:Gitea/jq 等來源可能將未設定值輸出為字串 'null',視其為空可避免把字面 'null' 當成有效設定。移除會改變既有行為。"
},
{
"location": "app/tags.js:46",
"role": "Rogue",
"original_finding": "cleanupOrphanTags 在迴圈中重複掃描所有 release 名稱陣列,複雜度 O(tags*releases),建議改用 Set。",
"reason": "誤報。cleanupOrphanTags 已將 releaseTagNames 交給 categorizeTags,後者於進入迴圈前即建立 Set 進行 O(1) 查找;並無 O(tags*releases) 的重複掃描。"
},
{
"location": "app/config.js:33",
"role": "Leo",
"original_finding": "直接將 env 預設為 process.env,全域相依性可能導致難以追蹤的副作用,建議抽離為 Provider 或 ConfigFactory。",
"reason": "不適用。loadConfig 已將 env 設為可注入參數(預設 process.env),測試即透過傳入假 env 驗證,已具良好可測性與隔離;對此規模的 Action 引入 Provider/Factory 屬過度設計。"
},
{
"location": "app/tags.js:21",
"role": "Leo",
"original_finding": "categorizeTags 內部頻繁轉換 Set,若被頻繁呼叫可能造成效能浪費,建議改為接收已轉好的 Set。",
"reason": "不適用。categorizeTags 每次清理流程僅被呼叫一次,單次建立 Set 成本可忽略;讓純函式自行建立 Set 可保持介面單純,將轉換責任外推反而增加耦合。"
},
{
"location": "app/gitea-client.js:25",
"role": "Bard",
"original_finding": "GiteaClient 建構子的 header 初始化結構死板,建議抽離為內部 private 函式 _getHeaders() 以增加未來彈性。",
"reason": "YAGNI / 不適用。目前僅單一固定標頭,建構子直接設定清楚直觀;為尚不存在的需求預先抽象屬過度設計,待真有多標頭需求時再重構。"
},
{
"location": "app/releases.js:23",
"role": "Rogue",
"original_finding": "為排序而 map 成物件陣列再 map 回原物件,造成多次 O(n) 陣列分配與 GC 壓力,建議減少中間狀態或原地排序。",
"reason": "不採納且與前次建議衝突。此 map/sort/map(Schwartzian transform)正是前一輪 Rogue(releases.js:17)要求「排序前預先計算時間戳」的實作,用以避免比較器重複呼叫 Date.parse;release 數量有限,中間陣列開銷可忽略,可讀性更佳。"
},
{
"location": "app/config.js:35",
"role": "Assassin",
"original_finding": "requireUrl 僅驗證 URL 格式,未阻擋指向內部網路/loopback(localhost、169.254.169.254 等)的位址,容器環境恐 SSRF。建議加入內部 IP 黑名單。",
"reason": "不適用且會破壞正常部署。GITEA_SERVER_URL 來自 CI 平台提供的 gitea.server_url,即本 Action 欲操作的 Gitea 實例本身;自架 Gitea 經常部署於內網/私有位址,封鎖內部 IP 會使合法部署無法運作。此非攻擊者可控的任意外連目標。"
},
{
"location": "app/releases.js:46",
"role": "Bard",
"original_finding": "區段標題層級不一致:『取得成品資訊』內含 info 輸出,『刪除舊版本成品』直接開始邏輯,視覺節奏稍不一致。",
"reason": "主觀排版意見。現行 section() 用於標示每個主要階段,語意清楚且符合原 bash 版本結構;不構成功能或可讀性問題。"
},
{
"location": "app/releases.js:20",
"role": "Leo",
"original_finding": "selectReleasesToDelete 對無法解析的日期視為 0 是隱晦行為,維護者可能不解,建議記錄 warning 或明確處理。",
"reason": "已於程式碼註解明確記錄『格式無效或缺漏時視為最舊(0)』;Gitea API 一向回傳有效 created_at,對每筆無效日期發出 runtime warning 將是不會觸發的死碼與雜訊,不採納。"
},
{
"location": "app/logger.js:53",
"role": "Assassin",
"original_finding": "failError 直接輸出 error.stack 可能洩漏專案目錄結構、內部函式名稱等敏感路徑;建議生產環境隱藏堆疊或僅 debug 模式輸出。",
"reason": "不適用。本專案為 CI/CD 清理 Action,堆疊輸出至僅儲存庫維護者可見的 Action 日誌以利除錯,非公開環境;容器內路徑為 /app,不含敏感資訊。隱藏堆疊反而降低可維運性。"
},
{
"location": "app/gitea-client.js:10",
"role": "Leo",
"original_finding": "MAX_PAGES 寫死於程式碼,建議改為環境變數傳入以增加部署彈性。",
"reason": "不採納。MAX_PAGES 為防止無限迴圈的安全上限(backstop),非調校參數;1000 頁遠超任何真實儲存庫的 release/tag 規模,改為環境變數只會增加不必要的設定面與誤設風險。"
},
{
"location": "app/logger.js:46",
"role": "Leo",
"original_finding": "建議在 logger.js 建立通用 log(level, prefix, stream) 函式,讓 info/warn/fail 等共用以降低重複。",
"reason": "不採納。六個輸出函式皆為單行、語意直觀;為此抽象通用 dispatcher 反而增加間接層與閱讀成本,去重效益微小。"
},
{
"location": "app/gitea-client.js:63",
"role": "Assassin",
"original_finding": "baseUrl 直接拼接到 URL,若來自惡意環境變數恐 SSRF;建議確保 baseUrl 只能以預期的 GITEA_SERVER_URL 開頭。",
"reason": "不適用。fetchAllPages 的 baseUrl 並非外部輸入,而是由 config 的 releaseApiUrl/tagApiUrl 提供,二者皆以已通過 requireUrl 驗證的 GITEA_SERVER_URL 與通過 requireRepository 驗證的 repository 組成 `${serverUrl}/api/v1/repos/${repository}/...`,本即保證以 GITEA_SERVER_URL 開頭,無額外白名單必要。"
},
{
"location": "app/tags.js:62",
"role": "Assassin",
"original_finding": "tag 名稱拼接至 URL 雖用 encodeURIComponent,仍建議以白名單格式驗證(限英數字、點、破折號,禁 .. 或 /)以更安全。",
"reason": "不採納。encodeURIComponent 會將路徑分隔字元 / 編碼為 %2F,已消除 URL 路徑穿越;且 git ref 命名規則本即禁止 tag 含 '..'、控制字元與多數特殊字元。額外白名單反而可能誤拒合法 tag 名,且非 URL 安全所必需。"
},
{
"location": "app/tags.js:19",
"role": "Bard",
"original_finding": "categorizeTags 內部判斷邏輯稍複雜,建議拆分為更小的判斷函式以提高可讀性。",
"reason": "不採納。categorizeTags 僅為 skip/keep/delete 三分支的單層 map,語意已清楚且具完整測試;為三個簡單條件再抽出微函式只會增加跳轉與閱讀成本,可讀性無實質提升。"
},
{
"location": "app/tags.js:19",
"role": "Bard",
"original_finding": "建議將邏輯拆分為更小的判斷函式,提高可讀性。",
"reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)"
},
{
"location": "app/tags.js:62",
"role": "Assassin",
"original_finding": "除了 encodeURIComponent 外,應在 config.js 或 tags.js 中對 tag.name 進行嚴格的白名單格式驗證(例如限制為英數字、點、破折號,並禁止 .. 或 /),這比單純編碼更安全。",
"reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)"
},
{
"location": "app/config.js:33",
"role": "Mage",
"original_finding": "loadConfig 對 GITEA_SERVER_URL 的 replace 假設輸入為 string,非字串恐拋 TypeError。",
"reason": "不成立。該行已以 `typeof rawServerUrl === 'string' ? rawServerUrl.replace(...) : rawServerUrl` 做型別防護,非字串時不呼叫 replace;finding 內文亦自承「已經做了檢查」。env 變數本即為字串或 undefined,不會出現陣列/物件。"
}
]