為可測性 export 三個內部函式(isSafeRepoPath、extractBalancedJSON/extractJSONText、normalizeText)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
變更摘要
本 PR 為
/jsc:doc-funcs文件化與專案結構優化的成果,不含任何 runtime 行為變更(單元測試 233 筆全數通過)。*.test.js從app/移至app/test/,並把npm test指令更新為node --test test/*.test.js。Dockerfile、entrypoint.sh、.gitea/workflows/ci.yaml、.gitea/workflows/cd.yaml補上「用途/更新日期」標頭與逐行註解,指令邏輯與原檔完全一致。README.md,含專案列表、85 個公開函式的功能列表與使用範例(錨點 1:1 驗證通過)。app/.gitignore忽略node_modules。影響範圍
develop完全相同。重點檔案/模組
app/*.js:13 個模組補上 JSDoc。app/test/:單元測試新位置。app/package.json:測試指令調整。README.md:重建專案文件。風險/注意事項
.js僅有註解變動、4 個指令檔指令邏輯與develop相同。本分支 commits
test(test 目錄): 將 12 個單元測試移至 app/test 並更新 npm test 指令docs(ai-code-review): 補齊各模組 JSDoc、指令檔逐行註解並重建 READMEchore(app): 新增 .gitignore 忽略 node_modules🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 19 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -1,1 +9,4 @@# 指定基底映像為 Alpine Linux 最新版;Alpine 體積小,可縮小最終映像大小並加快拉取速度。# 需人工確認:使用 latest tag 會在不同時間建置出不同基底版本,可能影響可重現性,# 建議釘選明確版本(例如 alpine:3.20)以確保建置一致。FROM alpine:latest嚴重等級:🟡 警告
審查員:Assassin
問題:使用了
alpine:latest作為基底映像。latest標籤極不穩定,當 Alpine Linux 發布新版本時,建置出的映像環境可能會產生難以預期的變更,甚至引入不相容的套件版本,導致建置再現性(Reproducibility)崩潰。建議:建議釘選明確的 Alpine 版本號(例如
alpine:3.20),以確保每次建置的執行環境完全一致且可預測。@@ -1,2 +10,4 @@# 需人工確認:使用 latest tag 會在不同時間建置出不同基底版本,可能影響可重現性,# 建議釘選明確版本(例如 alpine:3.20)以確保建置一致。FROM alpine:latest嚴重等級:🟡 警告
審查員:Leo
問題:註解中提到建議使用明確版本(例如 alpine:3.20),但實作上卻持續使用 latest。這會在未來的建置環境中產生不可重現的隱憂,把債留給未來。
建議:直接在 Dockerfile 中指定明確的 Alpine 版本(如 alpine:3.20),並將該註解移除。
@@ -3,1 +14,4 @@# 安裝必要的工具# 安裝執行 code review 所需的工具:bash(執行 entrypoint 腳本)、git(前置遠端驗證/取得 diff)、# nodejs 與 npm(執行 app 內的 Node.js 程式)。# --no-cache:不保留 apk 套件索引快取,避免殘留在映像層中以減少映像大小。嚴重等級:🔴 嚴重
審查員:Assassin
問題:在
apk add中使用了--no-check-certificate參數。這會強制忽略套件來源的 SSL/TLS 憑證檢查,攻擊者可以透過中間人攻擊(MITM)在容器建置期間植入惡意套件,導致整個建置環境被入侵。建議:正式環境嚴禁使用
--no-check-certificate。若遇到憑證問題,應排查環境配置(如是否缺少根憑證),或是確實解決憑證驗證問題,確保套件來源可信。@@ -4,2 +18,4 @@# 需人工確認:--no-check-certificate 會略過套件來源的憑證驗證,存在中間人攻擊風險,# 僅在內網或憑證受限環境下使用;正式環境建議移除以維持安全性。RUN apk add --no-cache --no-check-certificate bash git nodejs npm嚴重等級:🟡 警告
審查員:Leo
問題:Dockerfile 中留下了明確的安全性警告註解(關於 --no-check-certificate)。這種將安全性風險留在程式碼中並以註解提醒的做法,容易被未來的維護者忽略,導致潛在的安全性漏洞。
建議:若此 flag 僅為暫時性,應盡快優化環境以移除它;若不可避免,應將此設定集中管理,而非在 Dockerfile 中寫死此類不安全的設定。
@@ -11,0 +13,4 @@** @param {{ level?: string, role?: string, location?: string, suggestion?: string }} f* 單筆審查問題物件。`level` 若不在 critical/warning/info 之內,emoji 留空、標籤回退為原始 level 值;* `role`、`location`、`suggestion` 直接內嵌字串(未定義時會輸出 undefined 字樣)。傳入 null/undefined 會拋 TypeError(需人工確認是否需防呆)。嚴重等級:🔵 建議
審查員:Maya
問題:新增了許多輔助性的 formatting 函式(如 findingRow, buildTable 等),雖為內部使用,但這類字串處理邏輯若沒有測試覆蓋,極易因修改格式而導致 Markdown 輸出損壞。
建議:雖然是輔助函式,但建議在 test/comments.test.js 中補齊這些 formatting 函式的斷言測試,確保輸出格式穩定。
@@ -56,17 +103,48 @@ function reviewCommentBody(f) {].join('\n');嚴重等級:🔵 建議
審查員:Bard
問題:這裡同時出現了普通註解與 JSDoc,內容描述重複,顯得較為冗餘。
建議:移除該行普通註解(// 等級無法...),僅保留下方的 JSDoc 即可。
@@ -15,0 +17,4 @@* 供連接使用自簽或無效憑證的 OpenCode 服務時使用。** @remarks 每次呼叫都會回傳全新的 Agent 實例(不快取),建議呼叫端重用以共用連線池。* 停用憑證驗證有中間人攻擊風險,僅限受信任的內部環境使用。嚴重等級:🟡 警告
審查員:Assassin
問題:
getOpenCodeHttpsAgent函式明確回傳了rejectUnauthorized: false的 HTTPS Agent。在與 OpenCode 服務通訊時,這會完全停用憑證驗證,若傳輸過程被攔截,攻擊者可輕易偽裝成合法伺服器竊取機敏數據。建議:在生產環境下,請務必將此 Agent 設定為進行完整的憑證驗證。若因服務使用自簽憑證,應將該憑證加入容器的受信任憑證庫(CA Store)中,而非全域停用驗證。
@@ -54,0 +70,4 @@** @param {string} fullPath - 目標檔案路徑;上層目錄須事先存在(本函式不建立目錄)。* @param {Array<object>} exclusions - 欲寫入的排除條目陣列。* @returns {void}嚴重等級:🟡 警告
審查員:Maya
問題:normalizeText 執行了複雜的字元正規化(NFKC、標點符號壓縮),這是 findings 去重比對的核心邏輯,但目前缺乏測試來驗證其對各種 Unicode 變體、全形/半形符號的處理是否符合預期。
建議:請補上測試用例,輸入包含全形/半形標點符號、各種 Unicode 組合字元的測試資料,確保正規化結果的一致性。
@@ -18,5 +18,5 @@/*** 透過 LLM 修正 JSON 陣列內容。* @param {string} fullPath 檔案路徑,供提示詞與除錯使用。* @param {string} label 檔案標籤。嚴重等級:🟡 警告
審查員:Maya
問題:repairJSONArrayWithAI 是修正 JSON 陣列的重要邏輯,但新增後未見針對其「對 LLM 輸出內容進行修復」的情境進行測試,若模型輸出異常或格式混亂時,此函式是否能穩定產出合法 JSON 陣列未被驗證。
建議:請建立測試用例,模擬各類 malformed JSON 字串(如缺少括號、註解、 markdown 殘留),驗證 repairer 是否能確實將其修復為可解析的 JSON 陣列。
@@ -49,0 +96,4 @@** @param {string} systemPrompt - 系統提示詞。* @param {string} userContent - 使用者輸入內容。* @returns {Promise<string>} 模型回應的純文字內容。嚴重等級:🔴 嚴重
審查員:Maya
問題:extractBalancedJSON 處理括號平衡解析的邏輯相當複雜,且直接操作字串索引,非常容易在處理特殊字元或不完整 JSON 片段時產生錯誤,目前完全沒有針對邊界條件的單元測試。
建議:建議編寫針對性的單元測試,包含深層巢狀結構、跳脫字元字串、不完整 JSON、與非法起始字元的邊界測試案例,以確保解析器的魯棒性。
@@ -59,5 +59,5 @@step('Step5', '角色分析產生 findings');const { provider, apiKeys, baseURL, model } = getLLMConfig();if (!provider) {result(false, '未設定任何 LLM API Key,請檢查 action inputs');process.exit(1);嚴重等級:🟡 警告
審查員:Leo
問題:main 函式過於龐大,擔任了過多的總指揮與細節實作職責(Step 1 到 Step 11)。隨著審查流程的邏輯增加,這段程式碼將變得極難閱讀與維護。
建議:將每個 Step 拆解為獨立的函式(例如 runPreflight, checkBotCommit 等),讓 main 函式只保留高階的流程協調邏輯。
@@ -125,2 +139,4 @@}/*** 將一段仍成立(open)對話對應的 bot finding 加入結轉清單,標記 is_new=false 表示為延續的舊問題。嚴重等級:🔴 嚴重
審查員:Maya
問題:isSafeRepoPath 函式對於路徑穿越(Directory Traversal)的驗證邏輯至關重要,但目前新增處完全沒有相對應的安全性測試案例,無法確保無法被惡意路徑繞過。
建議:請補齊測試案例,驗證包含 '../', '..', 或以絕對路徑開頭的惡意輸入,確保該函式能正確阻擋所有非預期的路徑穿越攻擊。
🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 29 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -11,0 +12,4 @@* 將單一 finding 格式化為 Markdown 表格的一列(等級|審查員|位置|建議)。** @param {{ level?: string, role?: string, location?: string, suggestion?: string }} f* 單筆審查問題物件。`level` 若不在 critical/warning/info 之內,emoji 留空、標籤回退為原始 level 值;嚴重等級:🔴 嚴重
審查員:Mage
問題:
findingRow直接存取f.role且無空值檢查,f為空值時會造成程式崩潰。建議:加入空值判斷,避免程式崩潰
@@ -21,0 +48,4 @@* @param {{ level?: string, location?: string }} b 比較項 B。* @returns {number} 負值代表 a 排在 b 之前,正值代表之後,0 代表相等(供 Array.prototype.sort 使用)。* @remarks 不在 LEVEL_ORDER 內的等級一律視為最低優先(排在最後);location 未定義時以空字串參與比較,因此排序穩定不會丟例外。*/嚴重等級:🟡 警告
審查員:Rogue
問題:每一項比較都在做 LEVEL_ORDER.includes 與 indexOf 搜尋,若 findings 超過千筆,這些無謂的搜尋將浪費大量 CPU 週期。
建議:在 bySeverity 外部將 LEVEL_ORDER 預先處理成 Map<string, number>,排序時直接 Map 取值,時間複雜度從 O(N log N * M) 降至 O(N log N)。
@@ -15,0 +18,4 @@** @remarks 每次呼叫都會回傳全新的 Agent 實例(不快取),建議呼叫端重用以共用連線池。* 停用憑證驗證有中間人攻擊風險,僅限受信任的內部環境使用。* @returns {import('https').Agent} 已關閉憑證驗證的 HTTPS Agent 實例。嚴重等級:🟡 警告
審查員:Rogue
問題:每次呼叫都 new https.Agent(),這會建立全新的 TCP 連線池,不僅增加記憶體碎片,還浪費三次握手建立連線的時間,根本沒發揮連線池的優勢。
建議:將 Agent 實例化為模組層級的靜態變數(Singleton),只在第一次呼叫時建立,後續重複使用。
@@ -58,0 +81,4 @@/*** 將檔案 mtime(毫秒時間戳)格式化為 ISO 字串,無效值回傳 'unknown'。** @param {number} mtimeMs - 毫秒時間戳(通常為 fs.Stats.mtimeMs)。嚴重等級:🔴 嚴重
審查員:Rogue
問題:這函式在 inner loop 被瘋狂呼叫,每次都跑四次重量級的字串正則替換 (normalize, replace, trim),若這與排除規則做笛卡爾積比對,效能直接雪崩。
建議:針對 findings 與 exclusions 的內容,採用「首次計算後快取 (Memoization)」機制,不要重複對同一段文字計算正規化。
@@ -69,0 +143,4 @@* @param {string} repoDir git 工作目錄路徑。* @param {typeof import('child_process').spawnSync} [_spawnSync=spawnSync]* 測試用依賴注入。* @returns {boolean} HEAD 訊息含機器人標記時為 true;讀取失敗時安全地回傳 false。嚴重等級:🟡 警告
審查員:Maya
問題:
commitAndPush函式在git push失敗時僅記錄了 warning 且不拋出例外。這意味著如果 push 失敗(例如網路問題、衝突),系統會誤以為 review 已完成並發布,測試案例中沒有驗證 push 失敗後對於整個 CI pipeline 狀態的影響。建議:即使不希望中斷流程,也應該在測試中明確驗證此時是否會觸發後續的告警機制或讓狀態檢查器偵測到失敗,確保沒有被誤當成執行成功。
@@ -111,0 +191,4 @@* 將 AI 審查產出的 review 檔(findings / exclusions)結轉到 repo,並 commit、* push 回 PR head branch。流程:設定機器人 git 身分 → fetch + hard reset 對齊* 遠端 → 從 workspace 複製存在的 review 檔到 repo 並 add → 若無變更則跳過 →* 以含 `BOT_COMMIT_MARKER` 與結果標籤的訊息 commit → push。嚴重等級:🔵 建議
審查員:Leo
問題:
withAskpassis tightly coupled with Gitea's bot-trigger behavior.建議:Encapsulate bot commit logic within
commitAndPush, auto-selecting the correctGITEA_COMMENT_TOKEN.@@ -65,5 +65,5 @@const original = readJSONText(fullPath, label);const repaired = await repairer(fullPath, label, original);fs.writeFileSync(fullPath, repaired.endsWith('\n') ? repaired : `${repaired}\n`, 'utf8');JSON.parse(readJSONText(fullPath, label));ok(`${label} 已由 AI 修正並通過再次驗證`);嚴重等級:🔴 嚴重
審查員:Maya
問題:validateJSONArrayFile 函式在 AI 修復後會直接覆寫檔案,但這部分邏輯完全沒有對應的單元測試,如果 AI 修復失敗或產生了無效 JSON,系統可能會產生毀損的 findings 檔案。
建議:補上測試案例,模擬 repairJSONArrayWithAI 失敗、模擬修復後產生 invalid JSON,確保程式會拋出例外且不會覆寫原檔案。
@@ -11,0 +12,4 @@* 將單一 finding 格式化為 Markdown 表格的一列(等級|審查員|位置|建議)。** @param {{ level?: string, role?: string, location?: string, suggestion?: string }} f* 單筆審查問題物件。`level` 若不在 critical/warning/info 之內,emoji 留空、標籤回退為原始 level 值;嚴重等級:🔴 嚴重
審查員:Mage
問題:
findingRow直接存取f.role且無空值檢查,f為空值時會造成程式崩潰。建議:加入空值判斷,避免程式崩潰
@@ -58,0 +81,4 @@/*** 將檔案 mtime(毫秒時間戳)格式化為 ISO 字串,無效值回傳 'unknown'。** @param {number} mtimeMs - 毫秒時間戳(通常為 fs.Stats.mtimeMs)。嚴重等級:🔴 嚴重
審查員:Rogue
問題:這函式在 inner loop 被瘋狂呼叫,每次都跑四次重量級的字串正則替換 (normalize, replace, trim),若這與排除規則做笛卡爾積比對,效能直接雪崩。
建議:針對 findings 與 exclusions 的內容,採用「首次計算後快取 (Memoization)」機制,不要重複對同一段文字計算正規化。
@@ -65,5 +65,5 @@const original = readJSONText(fullPath, label);const repaired = await repairer(fullPath, label, original);fs.writeFileSync(fullPath, repaired.endsWith('\n') ? repaired : `${repaired}\n`, 'utf8');JSON.parse(readJSONText(fullPath, label));ok(`${label} 已由 AI 修正並通過再次驗證`);嚴重等級:🔴 嚴重
審查員:Maya
問題:validateJSONArrayFile 函式在 AI 修復後會直接覆寫檔案,但這部分邏輯完全沒有對應的單元測試,如果 AI 修復失敗或產生了無效 JSON,系統可能會產生毀損的 findings 檔案。
建議:補上測試案例,模擬 repairJSONArrayWithAI 失敗、模擬修復後產生 invalid JSON,確保程式會拋出例外且不會覆寫原檔案。
@@ -21,0 +48,4 @@* @param {{ level?: string, location?: string }} b 比較項 B。* @returns {number} 負值代表 a 排在 b 之前,正值代表之後,0 代表相等(供 Array.prototype.sort 使用)。* @remarks 不在 LEVEL_ORDER 內的等級一律視為最低優先(排在最後);location 未定義時以空字串參與比較,因此排序穩定不會丟例外。*/嚴重等級:🟡 警告
審查員:Rogue
問題:每一項比較都在做 LEVEL_ORDER.includes 與 indexOf 搜尋,若 findings 超過千筆,這些無謂的搜尋將浪費大量 CPU 週期。
建議:在 bySeverity 外部將 LEVEL_ORDER 預先處理成 Map<string, number>,排序時直接 Map 取值,時間複雜度從 O(N log N * M) 降至 O(N log N)。
@@ -15,0 +18,4 @@** @remarks 每次呼叫都會回傳全新的 Agent 實例(不快取),建議呼叫端重用以共用連線池。* 停用憑證驗證有中間人攻擊風險,僅限受信任的內部環境使用。* @returns {import('https').Agent} 已關閉憑證驗證的 HTTPS Agent 實例。嚴重等級:🟡 警告
審查員:Rogue
問題:每次呼叫都 new https.Agent(),這會建立全新的 TCP 連線池,不僅增加記憶體碎片,還浪費三次握手建立連線的時間,根本沒發揮連線池的優勢。
建議:將 Agent 實例化為模組層級的靜態變數(Singleton),只在第一次呼叫時建立,後續重複使用。
@@ -69,0 +143,4 @@* @param {string} repoDir git 工作目錄路徑。* @param {typeof import('child_process').spawnSync} [_spawnSync=spawnSync]* 測試用依賴注入。* @returns {boolean} HEAD 訊息含機器人標記時為 true;讀取失敗時安全地回傳 false。嚴重等級:🟡 警告
審查員:Maya
問題:
commitAndPush函式在git push失敗時僅記錄了 warning 且不拋出例外。這意味著如果 push 失敗(例如網路問題、衝突),系統會誤以為 review 已完成並發布,測試案例中沒有驗證 push 失敗後對於整個 CI pipeline 狀態的影響。建議:即使不希望中斷流程,也應該在測試中明確驗證此時是否會觸發後續的告警機制或讓狀態檢查器偵測到失敗,確保沒有被誤當成執行成功。
@@ -111,0 +191,4 @@* 將 AI 審查產出的 review 檔(findings / exclusions)結轉到 repo,並 commit、* push 回 PR head branch。流程:設定機器人 git 身分 → fetch + hard reset 對齊* 遠端 → 從 workspace 複製存在的 review 檔到 repo 並 add → 若無變更則跳過 →* 以含 `BOT_COMMIT_MARKER` 與結果標籤的訊息 commit → push。嚴重等級:🔵 建議
審查員:Leo
問題:
withAskpassis tightly coupled with Gitea's bot-trigger behavior.建議:Encapsulate bot commit logic within
commitAndPush, auto-selecting the correctGITEA_COMMENT_TOKEN.@@ -0,0 +30,4 @@capturedUserContent = userContent;return '```json\n[{"fixed":true}]\n```';});嚴重等級:🔵 建議
審查員:Maya
問題:雖然有
repairJSONArrayWithAI的測試,但沒有測試當chatFn拋出錯誤時,validateJSONArrayFile是否正確地向上拋出該例外,缺乏失敗路徑的健壯性驗證。建議:在
json.test.js增加一個測試案例,mockrepairer為拋出 Error,驗證validateJSONArrayFile的錯誤傳遞路徑。🤖 AI Code Review 團隊
🤖 AI Code Review 團隊
🤖 AI Code Review 團隊