ai-review-resolve/20260623-163642
develop
處理 develop 上殘留的 2 條 AI review findings(皆為使用量統計相關),不更動任何 production 行為。
app/usage.test.js
formatUsageStatsLine
quota
rate
limit: NaN
NaN
> 0
fmt
0
.gitea/ai-review/exclusions.json
recordRateLimit
lowerCaseKeys
.gitea/ai-review/findings.json
[]
node --test app/*.test.js
🔍 服務:opencode 模型:gemini-2.5-flash
本次審查(opencode / gemini-2.5-flash,共 13 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -247,0 +247,4 @@
it('produces safe text (no NaN) when quota/rate carry invalid numbers', () => {
// quota.limit 為 NaN、rate.limit 為 NaN → 不應算出百分比、不得輸出 NaN
const line = formatUsageStatsLine('openai', 'm', usage,
嚴重等級:🟡 警告 審查員:Leo 問題:在測試案例中直接引用外部定義的 usage 變數,若該變數在其他測試中被意外修改,會導致測試間的隱性耦合,使得測試結果難以預測,降低測試的可維護性。 建議:建議直接在測試函式內定義該案例所需的完整 usage 物件,或是使用工廠函式(Factory function)來生成所需的測試資料,確保測試案例的獨立性與確定性。
usage
嚴重等級:🟡 警告 審查員:Maya 問題:目前的測試僅針對 NaN 的情況,但未考慮到 Infinity、null 或 undefined 等其他同樣可能導致格式錯誤或輸出異常的「無效數字」邊界條件。 建議:建議補充針對 Infinity、null 與 undefined 的測試案例,確保這些值在 formatUsageStatsLine 中皆能正確處理並輸出友善的錯誤訊息,而非損壞的文字。
嚴重等級:🔵 建議 審查員:Maya 問題:目前的測試案例將多個欄位同時設為 NaN,測試邏輯較為單一,未能明確驗證「單一欄位無效」與「組合欄位無效」時的具體行為差異。 建議:建議將測試拆分為更細緻的案例,分別驗證 quota.limit 與 rate.limit 欄位在單獨無效,以及兩者同時無效的情況,以確保各欄位的防禦性邏輯皆有被完整覆蓋。
@@ -247,0 +251,4 @@
{ available: true, used: 5, limit: NaN, currency: 'USD' },
{ hasData: true, remaining: NaN, limit: NaN, kind: 'tokens' });
assert.doesNotMatch(line, /NaN/);
assert.match(line, /剩餘可用: 無法計算/);
嚴重等級:🔴 嚴重 審查員:Mage 問題:測試情境雖處理了 NaN,但未涵蓋 limit 為 0 或負數等極端數值(例如除以零導致的錯誤或異常百分比計算)。若 formatUsageStatsLine 內部有計算 (used / limit) * 100 或類似邏輯,傳入 0 會產生 Infinity,傳入負數則會產生荒謬的邏輯結果。 建議:建議新增對 limit: 0 以及負數邊界值的測試案例,確保函式對這些非預期的數值輸入同樣能輸出「無法計算」或相應的錯誤處理訊息,而非輸出 Infinity 或負百分比。
limit
(used / limit) * 100
Infinity
limit: 0
本次審查(opencode / gemini-2.5-flash,共 12 次呼叫)
@@ -213,1 +213,4 @@
/** 是否為有限正數(排除 0、負數、NaN、Infinity),避免算出 Infinity%/NaN%/負百分比。 */
function isFinitePositive(n) {
return Number.isFinite(Number(n)) && Number(n) > 0;
嚴重等級:🟡 警告 審查員:Maya 問題:新增的 isFinitePositive 函數雖然確保了大於 0,但當 n 是類似 "100" 的數字字串時,Number(n) 可以正常運作,然而如果是 null 或 undefined,Number(n) 分別會變成 0 和 NaN。雖然在目前的使用邏輯下(limit != null)似乎安全,但這個檢查函數本身沒有對 null/undefined 做明確的防禦性處理,容易在未來被錯誤複用。 建議:建議在 isFinitePositive 內部補上對 null 或 undefined 的顯式檢查,或是增加對輸入型別的限制,確保該函數作為基礎邏輯檢查更健壯。
isFinitePositive
n
Number(n)
null
undefined
limit != null
@@ -197,2 +197,4 @@
});
it('returns null percent for non-finite or non-positive quota limits', () => {
for (const limit of [0, -5, Infinity, NaN, undefined]) {
嚴重等級:🟡 警告 審查員:Bard 問題:測試迴圈中的陣列 [0, -5, Infinity, NaN, undefined] 直接混用了不同型別與邊界值,雖然測試目的明確,但在測試碼中,將邏輯測試與邊界測試分開撰寫會更具可讀性。 建議:建議將測試案例拆分,例如分開測試「非數字類型」、「負數」與「邊界值(Infinity/NaN)」,這樣在測試失敗時能更快速釐清是哪種輸入類型導致的問題。
[0, -5, Infinity, NaN, undefined]
本次審查(opencode / gemini-2.5-flash,共 14 次呼叫)
@@ -211,23 +211,32 @@ function round1(n) {
const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };
嚴重等級:🟡 警告 審查員:Rogue 問題:在函式內部重複呼叫 Number(n),造成不必要的型別轉換開銷,且在條件式之後又呼叫一次 Number(quota.limit),造成重複轉換。 建議:建議將 Number(n) 的結果暫存起來,或在進入條件式後立即將值賦值給一個變數並重複使用,減少重複轉換的成本。
Number(quota.limit)
@@ -212,2 +212,4 @@
嚴重等級:🟡 警告 審查員:Assassin 問題:在 isFinitePositive 函數中,使用 Number(n) 進行強制轉型。若 n 為物件(例如 {} 或 []),Number() 的行為在某些邊緣情況下可能會產生非預期的數字,雖然當前邏輯有做 Number.isFinite 檢查,但對於這種隱式轉型仍需保持警惕,且函數名稱 isFinitePositive 雖然清楚,但在這個上下文中,函式內部使用了 Number(n) 強制轉型,這在 JavaScript 中可能會隱蔽掉原本資料型態的不一致。 建議:建議在 isFinitePositive 內部補上對 null 或 undefined 的顯式檢查,並直接在外部嚴格限制輸入類型或使用更明確的檢查(例如 typeof n === 'number')取代隱式轉型,避免隱式轉型帶來的不可控副作用。
{}
Number()
Number.isFinite
typeof n === 'number'
@@ -217,2 +221,2 @@
* 2. 速率配額(rate limit header)→ 當前視窗剩餘 / 上限;
* 皆無法取得時回傳 { percent: null, reason }。
* 1. 帳號額度(quota 有有限正數上限)→ 剩餘 credits / 上限;
* 2. 速率配額(rate limit header,有限正數上限)→ 當前視窗剩餘 / 上限;
嚴重等級:🟡 警告 審查員:Leo 問題:resolveRemainingPercent 函式同時處理了 quota 和 rate 的邏輯,導致檢查 limit 和 remaining 的計算與驗證邏輯在兩處重複,未來若要調整百分比算法,必須兩處同步修改,增加維護負擔。 建議:建議將百分比計算邏輯抽象為獨立的輔助函式(如 calculatePercent(remaining, limit)),讓 resolveRemainingPercent 僅負責選擇計算基準,降低重複性並提升封裝度。
@@ -219,2 +222,4 @@
* 上限或剩餘為 0/負數/NaN/Infinity 等無效值時不計算,落到 { percent: null, reason }。
*/
export function resolveRemainingPercent(quota, rate) {
嚴重等級:🔵 建議 審查員:Bard 問題:註解中提到「落到 { percent: null, reason }」,但程式碼實際邏輯是在 if 判斷式內直接 return,且 resolveRemainingPercent 函式中檢查分散在兩個 if 區塊,產生了重複的邏輯結構。 建議:建議將註解簡化為:「上限或剩餘為無效值時跳過計算」,並將計算百分比的邏輯抽離成一個獨立的輔助函式,統一處理 isFinite 的檢查與百分比運算。
resolveRemainingPercent
isFinite
@@ -224,1 +229,3 @@
return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' };
if (Number.isFinite(remaining)) {
}
嚴重等級:🔴 嚴重 審查員:Mage 問題:在計算 remaining 時,remaining = Number(rate.remaining); 之後直接檢查 Number.isFinite(remaining),但若 rate.remaining 是 undefined 或 null,Number() 會轉為 0。這意味著如果 rate.remaining 缺失,會被錯誤地視為「剩餘 0」而不是「無效值」,進而導致計算出 0% 的錯誤結果,而非預期的落到 { percent: null }。 建議:應先檢查 rate.remaining 是否為 null/undefined,或者使用更嚴格的轉換方式,確保只有在確實是有限數字時才進行後續運算。
remaining
remaining = Number(rate.remaining);
Number.isFinite(remaining)
rate.remaining
0%
{ percent: null }
剩餘值為 null/undefined 時原本經 Number() 轉成 0 而算出 0%, 改由 calculatePercent 統一檢查有效性,無效值一律落到「無法計算」。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
appendExclusions 原以 WORKSPACE 為主、repoDir 為鏡像,與 loadExclusions 相反, 會讀到空的 WORKSPACE 後把僅含本次新增的結果鏡像覆蓋掉 repoDir 既有規則, 改為以 repoDir 為主、WORKSPACE 為鏡像,順序與 loadExclusions 一致。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
本次審查(opencode / gemini-2.5-flash,共 9 次呼叫)
@@ -199,0 +214,4 @@
for (const remaining of [null, undefined]) {
const pct = resolveRemainingPercent({ available: false, reason: 'x' }, { hasData: true, remaining, limit: 200000, kind: 'tokens' });
assert.equal(pct.percent, null, `rate.remaining=${remaining} 應算不出百分比`);
嚴重等級:🔵 建議 審查員:Maya 問題:測試案例 returns null percent when rate.remaining is null/undefined 僅驗證了 remaining 為 null/undefined,但未驗證當 rate.limit 為 null/undefined 時的情況。雖然這可能由 calculatePercent 內部處理,但針對 resolveRemainingPercent 這一層級的整合測試仍不完整。 建議:建議補上一個測試案例,明確測試當 rate.limit 為 null/undefined 時,resolveRemainingPercent 的行為是否符合預期。
returns null percent when rate.remaining is null/undefined
calculatePercent
rate.limit
No dependencies set.
The note is not visible to the blocked user.
變更摘要
處理 develop 上殘留的 2 條 AI review findings(皆為使用量統計相關),不更動任何 production 行為。
影響範圍與重點檔案
app/usage.test.js:補formatUsageStatsLine在quota/rate帶入無效數字(如limit: NaN)時的測試,驗證輸出為安全文字(「剩餘可用: 無法計算」)且不含NaN、不破壞版面。對應程式碼本就以> 0守衛與fmt(NaN→0)處理,本次以測試明確固化此行為。.gitea/ai-review/exclusions.json:將recordRateLimit的「lowerCaseKeys複製整個 headers 增加開銷」一條登記為誤報——該函式每次 LLM 回應僅呼叫一次(一輪約 13 次)、headers 物件小,複製成本可忽略,改用不分大小寫存取器反增複雜度、效益不成比例。.gitea/ai-review/findings.json:清空為[](兩條皆已處理)。風險與注意事項
node --test app/*.test.js全數通過。🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 13 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -247,0 +247,4 @@it('produces safe text (no NaN) when quota/rate carry invalid numbers', () => {// quota.limit 為 NaN、rate.limit 為 NaN → 不應算出百分比、不得輸出 NaNconst line = formatUsageStatsLine('openai', 'm', usage,嚴重等級:🟡 警告
審查員:Leo
問題:在測試案例中直接引用外部定義的
usage變數,若該變數在其他測試中被意外修改,會導致測試間的隱性耦合,使得測試結果難以預測,降低測試的可維護性。建議:建議直接在測試函式內定義該案例所需的完整
usage物件,或是使用工廠函式(Factory function)來生成所需的測試資料,確保測試案例的獨立性與確定性。嚴重等級:🟡 警告
審查員:Maya
問題:目前的測試僅針對 NaN 的情況,但未考慮到 Infinity、null 或 undefined 等其他同樣可能導致格式錯誤或輸出異常的「無效數字」邊界條件。
建議:建議補充針對 Infinity、null 與 undefined 的測試案例,確保這些值在 formatUsageStatsLine 中皆能正確處理並輸出友善的錯誤訊息,而非損壞的文字。
嚴重等級:🔵 建議
審查員:Maya
問題:目前的測試案例將多個欄位同時設為 NaN,測試邏輯較為單一,未能明確驗證「單一欄位無效」與「組合欄位無效」時的具體行為差異。
建議:建議將測試拆分為更細緻的案例,分別驗證 quota.limit 與 rate.limit 欄位在單獨無效,以及兩者同時無效的情況,以確保各欄位的防禦性邏輯皆有被完整覆蓋。
@@ -247,0 +251,4 @@{ available: true, used: 5, limit: NaN, currency: 'USD' },{ hasData: true, remaining: NaN, limit: NaN, kind: 'tokens' });assert.doesNotMatch(line, /NaN/);assert.match(line, /剩餘可用: 無法計算/);嚴重等級:🔴 嚴重
審查員:Mage
問題:測試情境雖處理了 NaN,但未涵蓋
limit為 0 或負數等極端數值(例如除以零導致的錯誤或異常百分比計算)。若formatUsageStatsLine內部有計算(used / limit) * 100或類似邏輯,傳入 0 會產生Infinity,傳入負數則會產生荒謬的邏輯結果。建議:建議新增對
limit: 0以及負數邊界值的測試案例,確保函式對這些非預期的數值輸入同樣能輸出「無法計算」或相應的錯誤處理訊息,而非輸出Infinity或負百分比。🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 12 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -213,1 +213,4 @@/** 是否為有限正數(排除 0、負數、NaN、Infinity),避免算出 Infinity%/NaN%/負百分比。 */function isFinitePositive(n) {return Number.isFinite(Number(n)) && Number(n) > 0;嚴重等級:🟡 警告
審查員:Maya
問題:新增的
isFinitePositive函數雖然確保了大於 0,但當n是類似 "100" 的數字字串時,Number(n)可以正常運作,然而如果是null或undefined,Number(n)分別會變成0和NaN。雖然在目前的使用邏輯下(limit != null)似乎安全,但這個檢查函數本身沒有對null/undefined做明確的防禦性處理,容易在未來被錯誤複用。建議:建議在
isFinitePositive內部補上對null或undefined的顯式檢查,或是增加對輸入型別的限制,確保該函數作為基礎邏輯檢查更健壯。@@ -197,2 +197,4 @@});it('returns null percent for non-finite or non-positive quota limits', () => {for (const limit of [0, -5, Infinity, NaN, undefined]) {嚴重等級:🟡 警告
審查員:Bard
問題:測試迴圈中的陣列
[0, -5, Infinity, NaN, undefined]直接混用了不同型別與邊界值,雖然測試目的明確,但在測試碼中,將邏輯測試與邊界測試分開撰寫會更具可讀性。建議:建議將測試案例拆分,例如分開測試「非數字類型」、「負數」與「邊界值(Infinity/NaN)」,這樣在測試失敗時能更快速釐清是哪種輸入類型導致的問題。
🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 14 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -211,23 +211,32 @@ function round1(n) {const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };嚴重等級:🟡 警告
審查員:Rogue
問題:在函式內部重複呼叫
Number(n),造成不必要的型別轉換開銷,且在條件式之後又呼叫一次Number(quota.limit),造成重複轉換。建議:建議將
Number(n)的結果暫存起來,或在進入條件式後立即將值賦值給一個變數並重複使用,減少重複轉換的成本。@@ -212,2 +212,4 @@const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };/** 是否為有限正數(排除 0、負數、NaN、Infinity),避免算出 Infinity%/NaN%/負百分比。 */function isFinitePositive(n) {嚴重等級:🟡 警告
審查員:Assassin
問題:在
isFinitePositive函數中,使用Number(n)進行強制轉型。若n為物件(例如{}或[]),Number()的行為在某些邊緣情況下可能會產生非預期的數字,雖然當前邏輯有做Number.isFinite檢查,但對於這種隱式轉型仍需保持警惕,且函數名稱isFinitePositive雖然清楚,但在這個上下文中,函式內部使用了Number(n)強制轉型,這在 JavaScript 中可能會隱蔽掉原本資料型態的不一致。建議:建議在
isFinitePositive內部補上對null或undefined的顯式檢查,並直接在外部嚴格限制輸入類型或使用更明確的檢查(例如typeof n === 'number')取代隱式轉型,避免隱式轉型帶來的不可控副作用。@@ -217,2 +221,2 @@* 2. 速率配額(rate limit header)→ 當前視窗剩餘 / 上限;* 皆無法取得時回傳 { percent: null, reason }。* 1. 帳號額度(quota 有有限正數上限)→ 剩餘 credits / 上限;* 2. 速率配額(rate limit header,有限正數上限)→ 當前視窗剩餘 / 上限;嚴重等級:🟡 警告
審查員:Leo
問題:resolveRemainingPercent 函式同時處理了 quota 和 rate 的邏輯,導致檢查 limit 和 remaining 的計算與驗證邏輯在兩處重複,未來若要調整百分比算法,必須兩處同步修改,增加維護負擔。
建議:建議將百分比計算邏輯抽象為獨立的輔助函式(如 calculatePercent(remaining, limit)),讓 resolveRemainingPercent 僅負責選擇計算基準,降低重複性並提升封裝度。
@@ -219,2 +222,4 @@* 2. 速率配額(rate limit header,有限正數上限)→ 當前視窗剩餘 / 上限;* 上限或剩餘為 0/負數/NaN/Infinity 等無效值時不計算,落到 { percent: null, reason }。*/export function resolveRemainingPercent(quota, rate) {嚴重等級:🔵 建議
審查員:Bard
問題:註解中提到「落到 { percent: null, reason }」,但程式碼實際邏輯是在 if 判斷式內直接 return,且
resolveRemainingPercent函式中檢查分散在兩個 if 區塊,產生了重複的邏輯結構。建議:建議將註解簡化為:「上限或剩餘為無效值時跳過計算」,並將計算百分比的邏輯抽離成一個獨立的輔助函式,統一處理
isFinite的檢查與百分比運算。@@ -224,1 +229,3 @@return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' };if (Number.isFinite(remaining)) {return { percent: round1((remaining / limit) * 100), basis: '帳號額度', remaining, limit, unit: quota.currency || '' };}嚴重等級:🔴 嚴重
審查員:Mage
問題:在計算
remaining時,remaining = Number(rate.remaining);之後直接檢查Number.isFinite(remaining),但若rate.remaining是undefined或null,Number()會轉為0。這意味著如果rate.remaining缺失,會被錯誤地視為「剩餘 0」而不是「無效值」,進而導致計算出0%的錯誤結果,而非預期的落到{ percent: null }。建議:應先檢查
rate.remaining是否為 null/undefined,或者使用更嚴格的轉換方式,確保只有在確實是有限數字時才進行後續運算。🤖 AI Code Review 團隊
AI Code Review 統計
🤖 AI 助理使用量
本次審查(opencode / gemini-2.5-flash,共 9 次呼叫)
剩餘可用
剩餘可用:無法計算百分比(自架服務,無帳號額度概念)
@@ -199,0 +214,4 @@for (const remaining of [null, undefined]) {const pct = resolveRemainingPercent({ available: false, reason: 'x' }, { hasData: true, remaining, limit: 200000, kind: 'tokens' });assert.equal(pct.percent, null, `rate.remaining=${remaining} 應算不出百分比`);}嚴重等級:🔵 建議
審查員:Maya
問題:測試案例
returns null percent when rate.remaining is null/undefined僅驗證了 remaining 為 null/undefined,但未驗證當 rate.limit 為 null/undefined 時的情況。雖然這可能由calculatePercent內部處理,但針對resolveRemainingPercent這一層級的整合測試仍不完整。建議:建議補上一個測試案例,明確測試當
rate.limit為 null/undefined 時,resolveRemainingPercent的行為是否符合預期。