feat(ai-code-review): 新增 AI 多角色 code review action(攻防審查、findings 保存、建問題模式)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-07-17 16:53:27 +08:00
co-authored by Claude Fable 5
parent 6a0573b7c0
commit d08b97bd87
18 changed files with 2884 additions and 19 deletions
+88
View File
@@ -0,0 +1,88 @@
'use strict';
// 共用時間與日誌工具:所有訊息輸出統一為 [yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息(Asia/Taipei)。
/**
* 將指定時間轉為台北時區(Asia/Taipei)的顯示字串,格式固定為 yyyy/MM/dd HH:mm:ss24 小時制)。
* 利用 sv-SE 語系的 toLocaleString 產生 yyyy-MM-dd HH:mm:ss 後再把「-」換成「/」,
* 輸出不受執行環境(CI runner/主機)系統時區影響。
*
* @param {Date} [date=new Date()] 要格式化的時間;省略時使用現在時間。
* 須為有效的 Date 物件;傳入 Invalid Date 會得到 "Invalid Date" 字串(不丟例外),
* 傳入非 Date 型別屬誤用,可能丟出 TypeError。
* @returns {string} 台北時區的時間字串,格式 yyyy/MM/dd HH:mm:ss(例如 "2026/07/17 14:30:05")。
* @remarks
* 使用情境:log() 每次輸出日誌時呼叫本函式產生時間戳前綴;
* taipeiFromIso() 也在解析 ISO 字串成功後委派給本函式做最終格式化。
* 前置條件:無(純函式、無副作用);需要固定顯示格式的時間字串時皆可直接呼叫。
* 注意:格式與 JSC 規範「更新時間一律 Asia/Taipei、yyyy/MM/dd HH:mm:ss」一致,勿自行改動分隔符號。
*/
function taipeiNow(date = new Date()) {
return date
.toLocaleString('sv-SE', { timeZone: 'Asia/Taipei', hour12: false })
.replace(/-/g, '/');
}
/**
* 產生檔名用的台北時區時間戳,格式固定為 yyyy-MM-dd-HH:mm:ss24 小時制),
* 即把 sv-SE 格式(yyyy-MM-dd HH:mm:ss)中的空白換成「-」,避免檔名含空白。
* 主要供 AI review findings 輸出檔的檔名命名使用。
*
* @param {Date} [date=new Date()] 要格式化的時間;省略時使用現在時間。
* 須為有效的 Date 物件;傳入 Invalid Date 會得到 "Invalid-Date" 字串(不丟例外),
* 傳入非 Date 型別屬誤用,可能丟出 TypeError。
* @returns {string} 檔名用時間戳字串,格式 yyyy-MM-dd-HH:mm:ss(例如 "2026-07-17-14:30:05")。
* @remarks
* 使用情境:產生 findings 檔案(如 .gitea/ai-review 下的輸出檔)時呼叫,
* 讓檔名帶有可排序的建立時間。前置條件:無(純函式、無副作用)。
* 注意:輸出仍含「:」字元,在 Linux 檔名合法,但不可移植到 Windows 檔案系統;
* 若未來需跨平台檔名,需另行替換「:」。
*/
function taipeiFileStamp(date = new Date()) {
return date
.toLocaleString('sv-SE', { timeZone: 'Asia/Taipei', hour12: false })
.replace(' ', '-');
}
/**
* 將 ISO 8601 時間字串轉為台北時區(Asia/Taipei)的顯示字串(yyyy/MM/dd HH:mm:ss);
* 輸入為空或無法解析時回傳佔位符「—」,不丟例外,適合直接嵌入報表或留言等顯示用文字。
*
* @param {string | null | undefined} iso ISO 8601 時間字串(例如 "2026-07-17T06:30:05Z")。
* 可為 nullundefined/空字串,皆視為無資料而回傳「—」。
* 實作上接受任何 Date 建構子可解析的輸入,但非 ISO 格式的解析結果依 JS 引擎而異,建議一律傳 ISO 字串。
* @returns {string} 台北時區時間字串(yyyy/MM/dd HH:mm:ss),或無法解析時的佔位符 "—"。
* @remarks
* 使用情境:顯示外部系統(如 Gitea API、AI 服務回應)帶回的 UTC/ISO 時間欄位時呼叫,
* 統一轉成台北時區給人閱讀;來源欄位可能缺值,故以「—」佔位而非丟例外。
* 前置條件:無;結果僅供顯示,不應再拿去做時間運算(需運算請直接使用原始 ISO 值)。
*/
function taipeiFromIso(iso) {
if (!iso) return '—';
const date = new Date(iso);
return Number.isNaN(date.getTime()) ? '—' : taipeiNow(date);
}
/**
* 以專案統一格式輸出一行日誌到標準輸出:[yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息,
* 時間戳固定為台北時區(Asia/Taipei)24 小時制;stage 為空時整個 [階段] 區塊省略。
*
* @param {string | null | undefined} stage 階段名稱(例如 "步驟1"、"收尾");
* 傳空字串/nullundefined 時省略 [階段] 區塊。
* @param {string} level 日誌等級,約定限 "INF"、"WRN"、"ERR"、"TRC"、"DBG" 五種;
* 程式碼未驗證,傳入其他字串會原樣輸出,遵守約定由呼叫端負責。
* @param {string} message 日誌訊息內容;非字串會被隱式轉字串(物件會變成 "[object Object]"),
* 請由呼叫端先自行序列化。
* @returns {void} 無回傳值;副作用為寫一行到 stdout。
* @remarks
* 使用情境:action 執行過程中的所有訊息輸出都應改呼叫本函式而非直接 console.log
* 讓 CIGitea Actions)log 具備一致的時間戳與等級標記、一行一則。
* 注意:所有等級(含 ERR)都輸出到 stdout 而非 stderr;此格式對應 JSC 的
* spec-time-log 輸出規範,勿自行變更括號與冒號排版。
*/
function log(stage, level, message) {
const stagePart = stage ? `[${stage}]` : '';
console.log(`[${taipeiNow()}]${stagePart}[${level}]: ${message}`);
}
module.exports = { taipeiNow, taipeiFileStamp, taipeiFromIso, log };