diff --git a/.gitea/ai-review/findings/2026-07-17-19:03:13.json b/.gitea/ai-review/findings/2026-07-17-19:03:13.json
new file mode 100644
index 0000000..c70d864
--- /dev/null
+++ b/.gitea/ai-review/findings/2026-07-17-19:03:13.json
@@ -0,0 +1,12 @@
+{
+ "generatedAt": "2026/07/17 19:03:13",
+ "commitSha": "c4c23d45314bb260d7ec350db4f968021076ee17",
+ "prNumber": 4,
+ "tool": {
+ "name": "codex",
+ "version": "codex-cli 0.144.5",
+ "model": "(工具預設)"
+ },
+ "findings": [],
+ "excluded": []
+}
diff --git a/.gitea/ai-review/findings/2026-07-20-09:46:49.json b/.gitea/ai-review/findings/2026-07-20-09:46:49.json
new file mode 100644
index 0000000..7d9d6ec
--- /dev/null
+++ b/.gitea/ai-review/findings/2026-07-20-09:46:49.json
@@ -0,0 +1,12 @@
+{
+ "generatedAt": "2026/07/20 09:46:49",
+ "commitSha": "8662e8ca801c3dbf7f74ed7e758a7a55176735b8",
+ "prNumber": 4,
+ "tool": {
+ "name": "claude",
+ "version": "2.1.215 (Claude Code)",
+ "model": "(工具預設)"
+ },
+ "findings": [],
+ "excluded": []
+}
diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index 05a4103..59705bd 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -1,45 +1,98 @@
+# ============================================================================
+# 用途:在 pull request 針對 develop 分支開啟或同步時,分別以 Antigravity、Codex、Claude 環境執行本機 AI Code Review action。
+# 更新時間:2026/07/17 18:49:58
+# ============================================================================
+# Workflow 顯示名稱。
name: CI
+# Workflow 觸發條件區塊。
on:
+ # 以 pull request 事件觸發。
pull_request:
+ # 限定 pull request 目標分支。
branches:
+ # 僅在目標分支為 develop 時執行。
- develop
+ # 限定 pull request 開啟與同步更新時執行。
types: [opened, synchronize]
+# Job 定義區塊。
jobs:
+ # Antigravity 工具環境測試 job。
test-antigravity:
+ # Job 在 workflow UI 顯示的名稱。
name: TEST (Antigravity)
+ # 指定執行 runner 標籤;需人工確認本 Gitea runner 是否使用 ubuntu 標籤。
runs-on: ubuntu
+ # Job 執行步驟。
steps:
+ # 取得存取庫內容供後續 action 使用。
- name: 取得存取庫資訊
+ # 使用呼叫端 vars 指定的 checkout action 版本。
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
+ # 安裝 Antigravity 工具環境。
- name: 安裝工具
+ # 使用 Gitea composite action 安裝 Antigravity,版本由 vars 指定。
uses: https://gitea.jsc.idv.tw/composite-actions/setup-antigravity@${{ vars.ACTION_SETUP_ANTIGRAVITY_VERSION }}
+ # 執行目前存取庫的 AI Code Review action。
- name: 程式碼審查
+ # 以目前存取庫根目錄的 action.yml 作為 action 來源。
uses: ./
+ # 傳入 action inputs。
with:
+ # Gitea / GitHub token,用於 PR 留言與 findings 寫回。
token: ${{ secrets.GITHUB_TOKEN }}
+ # 啟用建問題模式:審查內容改發到追蹤 issue,並在 PR 回貼 issue 連結。
+ create-issue: 'true'
+ # Codex 工具環境測試 job。
test-codex:
+ # Job 在 workflow UI 顯示的名稱。
name: TEST (Codex)
+ # 指定執行 runner 標籤;需人工確認本 Gitea runner 是否使用 ubuntu 標籤。
runs-on: ubuntu
+ # Job 執行步驟。
steps:
+ # 取得存取庫內容供後續 action 使用。
- name: 取得存取庫資訊
+ # 使用呼叫端 vars 指定的 checkout action 版本。
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
+ # 安裝 Codex 工具環境。
- name: 安裝工具
+ # 使用 Gitea composite action 安裝 Codex,版本由 vars 指定。
uses: https://gitea.jsc.idv.tw/composite-actions/setup-codex@${{ vars.ACTION_SETUP_CODEX_VERSION }}
+ # 執行目前存取庫的 AI Code Review action。
- name: 程式碼審查
+ # 以目前存取庫根目錄的 action.yml 作為 action 來源。
uses: ./
+ # 傳入 action inputs。
with:
+ # Gitea / GitHub token,用於 PR 留言與 findings 寫回。
token: ${{ secrets.GITHUB_TOKEN }}
+ # 啟用建問題模式:審查內容改發到追蹤 issue,並在 PR 回貼 issue 連結。
+ create-issue: 'true'
+ # Claude 工具環境測試 job。
test-claude:
+ # Job 在 workflow UI 顯示的名稱。
name: TEST (Claude)
+ # 指定執行 runner 標籤;需人工確認本 Gitea runner 是否使用 ubuntu 標籤。
runs-on: ubuntu
+ # Job 執行步驟。
steps:
+ # 取得存取庫內容供後續 action 使用。
- name: 取得存取庫資訊
+ # 使用呼叫端 vars 指定的 checkout action 版本。
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
+ # 安裝 Claude 工具環境。
- name: 安裝工具
+ # 使用 Gitea composite action 安裝 Claude,版本由 vars 指定。
uses: https://gitea.jsc.idv.tw/composite-actions/setup-claude@${{ vars.ACTION_SETUP_CLAUDE_VERSION }}
+ # 執行目前存取庫的 AI Code Review action。
- name: 程式碼審查
+ # 以目前存取庫根目錄的 action.yml 作為 action 來源。
uses: ./
+ # 傳入 action inputs。
with:
+ # Gitea / GitHub token,用於 PR 留言與 findings 寫回。
token: ${{ secrets.GITHUB_TOKEN }}
+ # 啟用建問題模式:審查內容改發到追蹤 issue,並在 PR 回貼 issue 連結。
+ create-issue: 'true'
diff --git a/.gitea/workflows/readme.md b/.gitea/workflows/readme.md
new file mode 100644
index 0000000..6b2e3c8
--- /dev/null
+++ b/.gitea/workflows/readme.md
@@ -0,0 +1,41 @@
+# Workflow 文件草稿
+
+更新時間:2026/07/17 18:49:58
+
+## Workflow 總覽
+
+| 名稱 | 檔案位置 | 用途 | 觸發條件 |
+| --- | --- | --- | --- |
+| CI | `.gitea/workflows/ci.yaml` | 在 PR 事件中分別以 Antigravity、Codex、Claude 工具環境執行本存取庫的 AI Code Review action,驗證 action 可在不同 AI 工具環境下運作。 | `pull_request` 目標分支為 `develop`,事件類型為 `opened` 或 `synchronize`。 |
+
+## CI
+
+- Workflow 名稱:`CI`
+- 檔案位置:`.gitea/workflows/ci.yaml`
+- 用途:針對送往 `develop` 的 pull request 執行三組測試 job,分別安裝 Antigravity、Codex、Claude 環境後呼叫 `uses: ./` 執行目前 action。
+- 觸發條件:`pull_request.branches` 為 `develop`;`pull_request.types` 為 `opened`、`synchronize`。
+
+## Job
+
+| Job ID | 顯示名稱 | Runner | 主要流程 |
+| --- | --- | --- | --- |
+| `test-antigravity` | `TEST (Antigravity)` | `ubuntu` | checkout 存取庫、安裝 Antigravity、執行本地 action。 |
+| `test-codex` | `TEST (Codex)` | `ubuntu` | checkout 存取庫、安裝 Codex、執行本地 action。 |
+| `test-claude` | `TEST (Claude)` | `ubuntu` | checkout 存取庫、安裝 Claude、執行本地 action。 |
+
+## 主要輸入 / 環境參數
+
+| 名稱 | 來源 | 使用位置 | 說明 |
+| --- | --- | --- | --- |
+| `vars.ACTION_CHECKOUT_VERSION` | Gitea / GitHub repository 或 organization vars | `actions/checkout@...` | 指定 checkout action 版本。 |
+| `vars.ACTION_SETUP_ANTIGRAVITY_VERSION` | Gitea / GitHub vars | `setup-antigravity@...` | 指定 Antigravity 安裝 action 版本。 |
+| `vars.ACTION_SETUP_CODEX_VERSION` | Gitea / GitHub vars | `setup-codex@...` | 指定 Codex 安裝 action 版本。 |
+| `vars.ACTION_SETUP_CLAUDE_VERSION` | Gitea / GitHub vars | `setup-claude@...` | 指定 Claude 安裝 action 版本。 |
+| `secrets.GITHUB_TOKEN` | Gitea / GitHub secrets | 本地 action input `token` | 提供 action 呼叫 Gitea API 留言與寫回 findings 所需 token。 |
+
+## 注意事項
+
+- `.gitea/workflows/readme.md` 目前不存在;本檔為新增 workflow README 的草稿。
+- `runs-on: ubuntu` 是否符合實際 Gitea runner 標籤需人工確認。
+- `vars.*` 與 `secrets.GITHUB_TOKEN` 必須由呼叫端環境提供,否則 checkout、工具安裝或程式碼審查步驟可能失敗。
+- 三個 job 均以 `uses: ./` 呼叫目前存取庫根目錄的 `action.yml`;因此 `action.yml` 的 `runs.main` 需保持可被 runner 直接執行。
diff --git a/action.yml b/action.yml
index c952c50..08eb38f 100644
--- a/action.yml
+++ b/action.yml
@@ -1,53 +1,53 @@
-# =====================================================
-# 用途 : AI 多角色 code review:攻擊方找問題、防守方裁決誤報,結果留言到 PR 並保存 findings
-# 更新時間: 2026/07/17 16:49:21
-# =====================================================
+# ============================================================================
+# 用途:定義 AI Code Review Node action 的名稱、輸入參數與 Node.js 24 進入點,供 Gitea / GitHub workflow 以 uses 引用。
+# 更新時間:2026/07/17 18:49:58
+# ============================================================================
# Gitea / GitHub node action 的 manifest(action.yml):
# 定義本 action 的名稱、說明、輸入參數(inputs)與執行方式(runs),
# 供呼叫端 workflow 以 `uses:` 引用;runner 讀取此檔後以 node24 執行 src/index.js。
-# action 顯示名稱:呼叫端 workflow log 與 marketplace 列表上看到的名稱
+# action 顯示名稱:呼叫端 workflow log 與 marketplace 列表上看到的名稱。
name: 'AI Code Review'
# action 用途說明:多角色 AI code review 流程(攻擊方找問題、防守方裁決誤報),
-# 審查結果會留言到 PR 並保存 findings(.gitea/ai-review/findings/)
+# 審查結果會留言到 PR 並保存 findings(.gitea/ai-review/findings/)。
description: 'AI 多角色 code review:攻擊方找問題、防守方裁決誤報,結果留言到 PR 並保存 findings'
-# action 作者資訊(僅供辨識,不影響執行)
+# action 作者資訊(僅供辨識,不影響執行)。
author: 'Jeffery'
# 輸入參數區塊:呼叫端 workflow 以 `with:` 傳入,
-# runner 會自動注入為 INPUT_* 環境變數(例如 INPUT_TOKEN、INPUT_MODEL、INPUT_CREATE-ISSUE)供主程式讀取
+# runner 會自動注入為 INPUT_* 環境變數(例如 INPUT_TOKEN、INPUT_MODEL、INPUT_CREATE-ISSUE)供主程式讀取。
inputs:
- # Gitea API token:用於對 PR 留言審查結果、以及 push 審查結果檔回 repo
+ # Gitea API token:用於對 PR 留言審查結果、以及 push 審查結果檔回 repo。
token:
# 參數用途說明:secrets/vars context 在 action 內不可用,
- # 故由呼叫端 workflow 以 secrets.GITHUB_TOKEN 傳入
+ # 故由呼叫端 workflow 以 secrets.GITHUB_TOKEN 傳入。
description: 'Gitea API token(PR 留言與 push findings 用;呼叫端以 secrets.GITHUB_TOKEN 傳入)'
- # 必填:缺少 token 無法呼叫 Gitea API,action 無法運作
+ # 必填:缺少 token 無法呼叫 Gitea API,action 無法運作。
required: true
- # 指定 AI 工具使用的模型名稱
+ # 指定 AI 工具使用的模型名稱。
model:
- # 參數用途說明:留空表示使用各 AI 工具自身的預設模型
+ # 參數用途說明:留空表示使用各 AI 工具自身的預設模型。
description: '指定 AI 工具使用的模型(空值=各工具預設)'
- # 選填:未指定時採用預設值
+ # 選填:未指定時採用預設值。
required: false
- # 預設為空字串,代表不覆寫各工具的預設模型
+ # 預設為空字串,代表不覆寫各工具的預設模型。
default: ''
- # 建問題模式開關:是否把審查保留的問題另建 issue 追蹤
+ # 建問題模式開關:是否把審查保留的問題另建 issue 追蹤。
create-issue:
- # 參數用途說明:字串 'true' 時建立 issue(標題=PR 標題、描述=PR 描述、AI 挑標籤)
+ # 參數用途說明:字串 'true' 時建立 issue(標題=PR 標題、描述=PR 描述、AI 挑標籤),
# 並逐條留言問題明細,findings 檔不進版控、收尾只 commit exclusions.json;
- # 預設 'false' 走原流程(findings 檔與 exclusions.json 一併 commit 回 PR 來源分支)
+ # 預設 'false' 走原流程(findings 檔與 exclusions.json 一併 commit 回 PR 來源分支)。
description: '是否將問題建到存取庫的問題追蹤(true 時建立 issue 逐條留言問題明細,最後只 commit exclusions.json;預設 false 走原流程)'
- # 選填:未指定時採用預設值
+ # 選填:未指定時採用預設值。
required: false
- # 預設為字串 'false',代表不啟用建問題模式(主程式只認字串 'true' 才啟用)
+ # 預設為字串 'false',代表不啟用建問題模式(主程式只認字串 'true' 才啟用)。
default: 'false'
-# 執行方式區塊:宣告本 action 為 node action 及其進入點
+# 執行方式區塊:宣告本 action 為 node action 及其進入點。
runs:
- # 以 Node.js 24 runtime 直接在 runner 上執行(非 Docker 容器、非 composite)
+ # 以 Node.js 24 runtime 直接在 runner 上執行(非 Docker 容器、非 composite)。
using: 'node24'
- # 主程式進入點:直接指向 src/index.js(entry point)
+ # 主程式進入點:直接指向 src/index.js(entry point)。
# 主程式為零外部相依(package.json 無 dependencies,src 僅 require Node 內建模組與本地 lib),
# runner 不會自動 npm install,零相依時依 node action 慣例 main 直接指向 src/index.js 即正確;
- # 日後若新增外部相依,需改以 @vercel/ncc 打包(package.json 已備有 build script)
- # 並將 main 改指 dist/index.js、把 dist/ commit 進 repo
+ # 日後若新增外部相依,需改以 @vercel/ncc 打包(package.json 已備有 build script),
+ # 並將 main 改指 dist/index.js、把 dist/ commit 進 repo。
main: 'src/index.js'
diff --git a/readme.md b/readme.md
index 760617f..1d9725d 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,6 @@
# AI Code Review
-> 更新時間:2026/07/17 16:49:21
+> 更新時間:2026/07/17 18:49:58
AI 多角色 code review 的 Gitea **node action**(`node24`、零外部相依):以攻擊方六角色(🔮 Mage 邏輯、🗡️ Assassin 安全、⚡ Rogue 效率、🎼 Bard 風格、🧪 Maya 測試、🧰 Leo 可維護性)並行找問題、防守方(🛡️ Paladin)裁決誤報,結果留言到 PR、保存 findings,並以 bot commit 標記審查結果(`[success]`/`[failure]`)供下次觸發快速回報。
@@ -38,13 +38,13 @@ jobs:
```mermaid
flowchart TD
S1[1 判斷 bot commit 標記] -->|命中| E0[直接回報 success/failure]
- S1 -->|未命中| S2[2 偵測 AI 工具並留言]
- S2 --> S3[3 讀 .reviewignore 整理 diff 並留言]
- S3 --> S4[4 攻擊方登場留言]
- S4 --> S5[5 攻擊方 sub agent 並行找問題]
- S5 --> S6[6 防守方登場留言]
- S6 --> S7[7 防守方裁決 → 保存 findings + 誤判回寫 exclusions.json]
- S7 --> S8[8 舊留言標記解決]
+ S1 -->|未命中| S2[2 舊留言標記解決]
+ S2 --> S3[3 偵測 AI 工具並留言]
+ S3 --> S4[4 讀 .reviewignore 整理 diff 並留言]
+ S4 --> S5[5 攻擊方登場留言]
+ S5 --> S6[6 攻擊方 sub agent 並行找問題]
+ S6 --> S7[7 防守方登場留言]
+ S7 --> S8[8 防守方裁決 → 保存 findings + 誤判回寫 exclusions.json]
S8 --> S9[9 嚴重問題逐條掛行留言]
S9 --> S10[10 警告+建議彙整表格留言]
S10 --> E1[收尾 commit/push + exit code]
@@ -56,19 +56,19 @@ flowchart TD
| 專案名稱 | 專案描述 |
| --- | --- |
-| [ai-code-review](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/) | Gitea node action:提供台北時區日誌工具、runner 上下文載入、git diff/commit 操作、Gitea REST API 客戶端(留言/review/issue/標籤)、AI CLI 工具偵測與 sub agent 執行、角色提示載入、固定留言模板,以及多角色審查編排(攻擊方找問題、防守方裁決、findings 保存、誤判回寫、建問題模式) |
+| [ai-code-review](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/) | Gitea node action:提供台北時區日誌工具、runner 上下文載入、git diff/commit 操作、Gitea REST API 客戶端(留言/review/issue/標籤)、AI CLI 工具偵測與 sub agent 執行、角色提示載入、固定留言模板,以及多角色審查編排(攻擊方找問題、防守方裁決、findings 保存、誤判回寫、建問題模式) |
### 參考專案表
| 專案名稱 | 參考專案列表 |
| --- | --- |
-| [ai-code-review](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/) | 無 |
+| [ai-code-review](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/) | 無 |
### NuGet 套件表
| 專案名稱 | NuGet 套件列表 |
| --- | --- |
-| [ai-code-review](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/) | 無 |
+| [ai-code-review](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/) | 無 |
## 功能列表
@@ -76,56 +76,56 @@ flowchart TD
| 功能名稱 | 功能描述 |
| --- | --- |
-| [log.taipeiNow](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/log.js#L20) | [取得台北時區 yyyy/MM/dd HH:mm:ss 時間字串](#logtaipeinow) |
-| [log.taipeiFileStamp](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/log.js#L41) | [取得檔名用時間戳 yyyy-MM-dd-HH:mm:ss](#logtaipeifilestamp) |
-| [log.taipeiFromIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/log.js#L60) | [將 ISO 時間字串轉為台北時區顯示字串](#logtaipeifromiso) |
-| [log.log](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/log.js#L83) | [以統一格式輸出一行日誌](#loglog) |
-| [context.loadContext](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/context.js#L64) | [彙整 runner 環境變數與事件 payload 為執行上下文](#contextloadcontext) |
-| [gitrepo.latestCommitSubject](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitrepo.js#L60) | [取得最新 commit 的訊息標題](#gitrepolatestcommitsubject) |
-| [gitrepo.resolveMergeBase](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitrepo.js#L81) | [解析 base 分支與 HEAD 的 merge-base](#gitreporesolvemergebase) |
-| [gitrepo.changedFiles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitrepo.js#L104) | [列出 base 與 HEAD 之間有變更的檔案](#gitrepochangedfiles) |
-| [gitrepo.fileDiff](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitrepo.js#L125) | [取得單一檔案的 git diff 內容](#gitrepofilediff) |
-| [gitrepo.fileLastUpdatedIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitrepo.js#L143) | [取得檔案最後一次 commit 的 ISO 時間](#gitrepofilelastupdatediso) |
-| [gitrepo.commitAndPushFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitrepo.js#L181) | [以 bot 身分 commit 結果檔並 push 回 PR 來源分支](#gitrepocommitandpushfindings) |
-| [gitea.whoAmI](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L87) | [取得 token 對應的使用者(bot 身分)](#giteawhoami) |
-| [gitea.createCommentOnIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L106) | [對指定編號 issue/PR 新增一般留言](#giteacreatecommentonissue) |
-| [gitea.createIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L124) | [對本次 PR 新增一般留言](#giteacreateissuecomment) |
-| [gitea.listLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L141) | [列出存取庫可用標籤](#gitealistlabels) |
-| [gitea.createIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L164) | [在存取庫建立 issue(可掛標籤)](#giteacreateissue) |
-| [gitea.listIssueComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L184) | [列出 PR 全部一般留言(自動分頁)](#gitealistissuecomments) |
-| [gitea.editIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L202) | [編輯既有一般留言](#giteaeditissuecomment) |
-| [gitea.createReview](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L223) | [建立 code review 並掛行內留言](#giteacreatereview) |
-| [gitea.listReviews](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L243) | [列出 PR 全部 review(自動分頁)](#gitealistreviews) |
-| [gitea.listReviewComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L264) | [列出某 review 的全部行內留言](#gitealistreviewcomments) |
-| [gitea.tryResolveReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/gitea.js#L287) | [盡力將行內留言標記為已解決](#giteatryresolvereviewcomment) |
-| [agents.detectTool](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/agents.js#L53) | [依優先序偵測可用的 AI CLI 工具](#agentsdetecttool) |
-| [agents.runAgent](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/agents.js#L98) | [非互動執行一次 sub agent 並取回回覆](#agentsrunagent) |
-| [agents.extractJson](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/agents.js#L144) | [從 agent 回覆萃取 JSON(容忍雜訊)](#agentsextractjson) |
-| [roles.loadRoles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/roles.js#L32) | [載入角色提示檔並解析 frontmatter](#rolesloadroles) |
-| [roles.attackersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/roles.js#L71) | [過濾出攻擊方角色](#rolesattackersof) |
-| [roles.defendersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/roles.js#L90) | [過濾出防守方角色](#rolesdefendersof) |
-| [templates.toolComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L88) | [產生步驟 2 審查工具留言](#templatestoolcomment) |
-| [templates.diffComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L132) | [產生步驟 3 變更摘要留言](#templatesdiffcomment) |
-| [templates.rolesComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L173) | [產生步驟 4/6 角色登場留言](#templatesrolescomment) |
-| [templates.severeCommentBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L215) | [產生步驟 9 單條嚴重問題留言](#templatesseverecommentbody) |
-| [templates.severeReviewBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L247) | [產生步驟 9 嚴重問題 review 總覽](#templatesseverereviewbody) |
-| [templates.othersComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L277) | [產生步驟 10 警告+建議彙整表格留言](#templatesotherscomment) |
-| [templates.issueBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L309) | [產生建問題模式的 issue 本文](#templatesissuebody) |
-| [templates.issueFindingComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L341) | [產生建問題模式單條問題的 issue 留言](#templatesissuefindingcomment) |
-| [templates.nothingToReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/templates.js#L379) | [產生無可審查變更留言](#templatesnothingtoreviewcomment) |
-| [review.loadReviewIgnore](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L29) | [讀取 .reviewignore 忽略前綴清單](#reviewloadreviewignore) |
-| [review.isIgnored](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L53) | [判斷檔案是否忽略不送審](#reviewisignored) |
-| [review.collectDiffRows](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L77) | [整理送審 diff 資料列(含長度上限)](#reviewcollectdiffrows) |
-| [review.fillPurposes](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L127) | [以 AI 補齊每個檔案的一行用途描述](#reviewfillpurposes) |
-| [review.runAttackers](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L276) | [攻擊方 sub agent 並行找問題並合併列表](#reviewrunattackers) |
-| [review.runDefenders](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L448) | [防守方 sub agent 裁決保留或排除](#reviewrundefenders) |
-| [review.sortFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L572) | [依嚴重度→檔案→行號排序 findings](#reviewsortfindings) |
-| [review.appendExclusions](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L521) | [誤判問題附加到 exclusions.json](#reviewappendexclusions) |
-| [review.sortFindingsForIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L596) | [依檔案→嚴重度→行號排序(建問題模式)](#reviewsortfindingsforissue) |
-| [review.selectLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L627) | [以 AI 從可用標籤挑選 issue 標籤](#reviewselectlabels) |
-| [review.createIssueWithFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L694) | [建 issue 並逐條留言問題明細](#reviewcreateissuewithfindings) |
-| [review.resolveOldComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L778) | [將 PR 舊留言標記為解決/過時](#reviewresolveoldcomments) |
-| [review.postSevereComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/master/src/lib/review.js#L855) | [嚴重問題逐條掛行留言(含降級)](#reviewpostseverecomments) |
+| [log.taipeiNow](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L20) | [取得台北時區 yyyy/MM/dd HH:mm:ss 時間字串](#logtaipeinow) |
+| [log.taipeiFileStamp](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L41) | [取得檔名用時間戳 yyyy-MM-dd-HH:mm:ss](#logtaipeifilestamp) |
+| [log.taipeiFromIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L60) | [將 ISO 時間字串轉為台北時區顯示字串](#logtaipeifromiso) |
+| [log.log](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/log.js#L83) | [以統一格式輸出一行日誌](#loglog) |
+| [context.loadContext](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/context.js#L64) | [彙整 runner 環境變數與事件 payload 為執行上下文](#contextloadcontext) |
+| [gitrepo.latestCommitSubject](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L79) | [取得最新 commit 的訊息標題](#gitrepolatestcommitsubject) |
+| [gitrepo.resolveMergeBase](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L100) | [解析 base 分支與 HEAD 的 merge-base](#gitreporesolvemergebase) |
+| [gitrepo.changedFiles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L136) | [列出 base 與 HEAD 之間有變更的檔案](#gitrepochangedfiles) |
+| [gitrepo.fileDiff](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L157) | [取得單一檔案的 git diff 內容](#gitrepofilediff) |
+| [gitrepo.fileLastUpdatedIso](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L175) | [取得檔案最後一次 commit 的 ISO 時間](#gitrepofilelastupdatediso) |
+| [gitrepo.commitAndPushFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitrepo.js#L213) | [以 bot 身分 commit 結果檔並 push 回 PR 來源分支](#gitrepocommitandpushfindings) |
+| [gitea.whoAmI](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L87) | [取得 token 對應的使用者(bot 身分)](#giteawhoami) |
+| [gitea.createCommentOnIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L106) | [對指定編號 issue/PR 新增一般留言](#giteacreatecommentonissue) |
+| [gitea.createIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L124) | [對本次 PR 新增一般留言](#giteacreateissuecomment) |
+| [gitea.listLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L141) | [列出存取庫可用標籤](#gitealistlabels) |
+| [gitea.createIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L164) | [在存取庫建立 issue(可掛標籤)](#giteacreateissue) |
+| [gitea.listIssueComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L184) | [列出 PR 全部一般留言(自動分頁)](#gitealistissuecomments) |
+| [gitea.editIssueComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L202) | [編輯既有一般留言](#giteaeditissuecomment) |
+| [gitea.createReview](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L223) | [建立 code review 並掛行內留言](#giteacreatereview) |
+| [gitea.listReviews](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L243) | [列出 PR 全部 review(自動分頁)](#gitealistreviews) |
+| [gitea.listReviewComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L264) | [列出某 review 的全部行內留言](#gitealistreviewcomments) |
+| [gitea.tryResolveReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/gitea.js#L287) | [盡力將行內留言標記為已解決](#giteatryresolvereviewcomment) |
+| [agents.detectTool](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js#L53) | [依優先序偵測可用的 AI CLI 工具](#agentsdetecttool) |
+| [agents.runAgent](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js#L98) | [非互動執行一次 sub agent 並取回回覆](#agentsrunagent) |
+| [agents.extractJson](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/agents.js#L144) | [從 agent 回覆萃取 JSON(容忍雜訊)](#agentsextractjson) |
+| [roles.loadRoles](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js#L32) | [載入角色提示檔並解析 frontmatter](#rolesloadroles) |
+| [roles.attackersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js#L71) | [過濾出攻擊方角色](#rolesattackersof) |
+| [roles.defendersOf](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/roles.js#L90) | [過濾出防守方角色](#rolesdefendersof) |
+| [templates.toolComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L88) | [產生步驟 3 審查工具留言](#templatestoolcomment) |
+| [templates.diffComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L132) | [產生步驟 4 變更摘要留言](#templatesdiffcomment) |
+| [templates.rolesComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L173) | [產生步驟 5/7 角色登場留言](#templatesrolescomment) |
+| [templates.severeCommentBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L215) | [產生步驟 9 單條嚴重問題留言](#templatesseverecommentbody) |
+| [templates.severeReviewBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L247) | [產生步驟 9 嚴重問題 review 總覽](#templatesseverereviewbody) |
+| [templates.othersComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L277) | [產生步驟 10 警告+建議彙整表格留言](#templatesotherscomment) |
+| [templates.issueBody](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L309) | [產生建問題模式的 issue 本文](#templatesissuebody) |
+| [templates.issueFindingComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L341) | [產生建問題模式單條問題的 issue 留言](#templatesissuefindingcomment) |
+| [templates.nothingToReviewComment](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/templates.js#L379) | [產生無可審查變更留言](#templatesnothingtoreviewcomment) |
+| [review.loadReviewIgnore](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L29) | [讀取 .reviewignore 忽略前綴清單](#reviewloadreviewignore) |
+| [review.isIgnored](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L53) | [判斷檔案是否忽略不送審](#reviewisignored) |
+| [review.collectDiffRows](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L77) | [整理送審 diff 資料列(含長度上限)](#reviewcollectdiffrows) |
+| [review.fillPurposes](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L127) | [以 AI 補齊每個檔案的一行用途描述](#reviewfillpurposes) |
+| [review.runAttackers](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L276) | [攻擊方 sub agent 並行找問題並合併列表](#reviewrunattackers) |
+| [review.runDefenders](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L448) | [防守方 sub agent 裁決保留或排除](#reviewrundefenders) |
+| [review.sortFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L572) | [依嚴重度→檔案→行號排序 findings](#reviewsortfindings) |
+| [review.appendExclusions](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L521) | [誤判問題附加到 exclusions.json](#reviewappendexclusions) |
+| [review.sortFindingsForIssue](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L596) | [依檔案→嚴重度→行號排序(建問題模式)](#reviewsortfindingsforissue) |
+| [review.selectLabels](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L627) | [以 AI 從可用標籤挑選 issue 標籤](#reviewselectlabels) |
+| [review.createIssueWithFindings](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L694) | [建 issue 並逐條留言問題明細](#reviewcreateissuewithfindings) |
+| [review.resolveOldComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L778) | [將 PR 舊留言標記為解決/過時](#reviewresolveoldcomments) |
+| [review.postSevereComments](https://gitea.jsc.idv.tw/node-actions/ai-code-review/src/branch/develop/src/lib/review.js#L855) | [嚴重問題逐條掛行留言(含降級)](#reviewpostseverecomments) |
## 使用範例
@@ -168,8 +168,8 @@ taipeiFromIso(''); // '—'
```js
const { log } = require('./src/lib/log');
-log('步驟3', 'INF', '變更檔案 5 個,送審 3 個。');
-// [2026/07/17 16:46:13][步驟3][INF]: 變更檔案 5 個,送審 3 個。
+log('步驟4', 'INF', '變更檔案 5 個,送審 3 個。');
+// [2026/07/17 16:46:13][步驟4][INF]: 變更檔案 5 個,送審 3 個。
```
@@ -246,7 +246,7 @@ const committed = gitrepo.commitAndPushFindings(cwd, {
### gitea.whoAmI
-取得 token 對應的使用者(`GET /user`),即 bot 身分;步驟 8 以 `login` 比對留言作者辨識本 action 發過的留言。
+取得 token 對應的使用者(`GET /user`),即 bot 身分;步驟 2 以 `login` 比對留言作者辨識本 action 發過的留言。
```js
const gitea = require('./src/lib/gitea');
@@ -269,7 +269,7 @@ await gitea.createCommentOnIssue(ctx, issue.number, '🔴 嚴重|...');
```js
const created = await gitea.createIssueComment(ctx, '## 📋 變更摘要 ...');
-// created.id 記入本回合留言集合,步驟 8 標註過時時跳過
+// created.id 記入本回合留言集合,步驟 2 標註過時時跳過
```
@@ -294,7 +294,7 @@ const issue = await gitea.createIssue(ctx, { title: 'PR 標題', body: '…', la
### gitea.listIssueComments
-列出 PR 全部一般留言(自動分頁,每頁 50 筆);步驟 8 據此找出 bot 舊留言標註〔已過時〕。
+列出 PR 全部一般留言(自動分頁,每頁 50 筆);步驟 2 據此找出 bot 舊留言標註〔已過時〕。
```js
const comments = await gitea.listIssueComments(ctx);
@@ -303,7 +303,7 @@ const comments = await gitea.listIssueComments(ctx);
### gitea.editIssueComment
-以新內容整段覆寫既有一般留言(留言 id 於 repo 層級定位);步驟 8 用來替舊留言加上〔已過時〕前綴。
+以新內容整段覆寫既有一般留言(留言 id 於 repo 層級定位);步驟 2 用來替舊留言加上〔已過時〕前綴。
```js
await gitea.editIssueComment(ctx, comment.id, `> 〔已過時〕…\n\n${comment.body}`);
@@ -323,7 +323,7 @@ await gitea.createReview(ctx, '## 🔴 嚴重問題(共 2 條)…', [
### gitea.listReviews
-列出 PR 全部 review(自動分頁);步驟 8 據此逐一取出行內留言嘗試解決。
+列出 PR 全部 review(自動分頁);步驟 2 據此逐一取出行內留言嘗試解決。
```js
const reviews = await gitea.listReviews(ctx);
@@ -411,7 +411,7 @@ const defenders = defendersOf(roles); // [Paladin]
### templates.toolComment
-產生步驟 2 的審查工具留言:工具/版本/模型/審查 commit/Run Job 連結表格+審查管線 mermaid 流程圖;開頭含隱藏標記供步驟 8 辨識。
+產生步驟 3 的審查工具留言:工具/版本/模型/審查 commit/Run Job 連結表格+審查管線 mermaid 流程圖;開頭含隱藏標記供步驟 2 辨識。
```js
const body = templates.toolComment({
@@ -424,7 +424,7 @@ const body = templates.toolComment({
### templates.diffComment
-產生步驟 3 的變更摘要留言:四欄表格(檔案/用途/git diff 長度/最後更新時間),截斷送審的檔案加註,結尾統計送審與排除數。
+產生步驟 4 的變更摘要留言:四欄表格(檔案/用途/git diff 長度/最後更新時間),截斷送審的檔案加註,結尾統計送審與排除數。
```js
const body = templates.diffComment(diffRows, ignoredCount);
@@ -433,7 +433,7 @@ const body = templates.diffComment(diffRows, ignoredCount);
### templates.rolesComment
-產生步驟 4/6 共用的角色登場留言:三欄表格(角色/面向/個性),面向以「中文(原文)」並列。
+產生步驟 5/7 共用的角色登場留言:三欄表格(角色/面向/個性),面向以「中文(原文)」並列。
```js
const body = templates.rolesComment({ title: '⚔️ 攻擊方登場', roles: attackers });
@@ -534,7 +534,7 @@ await review.fillPurposes({ tool, model: ctx.model, cwd, diffRows });
### review.runAttackers
-步驟 5:每位攻擊方角色一個 sub agent 並行分析 diff,回覆經檢核標準化後合併為單一問題列表並編派 `F001…` 流水號;單一角色失敗只記 WRN 以空結果代替。
+步驟 6:每位攻擊方角色一個 sub agent 並行分析 diff,回覆經檢核標準化後合併為單一問題列表並編派 `F001…` 流水號;單一角色失敗只記 WRN 以空結果代替。
```js
const findings = await review.runAttackers({ tool, model: ctx.model, cwd, attackers, diffRows });
@@ -543,7 +543,7 @@ const findings = await review.runAttackers({ tool, model: ctx.model, cwd, attack
### review.runDefenders
-步驟 7:每位防守方角色一個 sub agent 配合 `exclusions.json` 與歷史 findings 裁決;「全部防守方都判可排除」才移除,拿不準一律保留,每條附 `verdicts` 供追溯。
+步驟 8:每位防守方角色一個 sub agent 配合 `exclusions.json` 與歷史 findings 裁決;「全部防守方都判可排除」才移除,拿不準一律保留,每條附 `verdicts` 供追溯。
```js
const { kept, excluded } = await review.runDefenders({ tool, model: ctx.model, cwd, defenders, findings });
@@ -600,7 +600,7 @@ if (ctx.createIssue && kept.length > 0) {
### review.resolveOldComments
-步驟 8:bot 舊一般留言(非本回合)編輯加〔已過時〕前綴;review 行內留言盡力呼叫 resolve API,第一次失敗即判定版本不支援並停止。任何失敗只記 WRN 不阻斷。
+步驟 2:bot 舊一般留言(非本回合)編輯加〔已過時〕前綴;review 行內留言盡力呼叫 resolve API,第一次失敗即判定版本不支援並停止。任何失敗只記 WRN 不阻斷。
```js
await review.resolveOldComments({ ctx, gitea, currentRunCommentIds });
diff --git a/src/index.js b/src/index.js
index b8680b9..72d6237 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,7 +4,7 @@
console.log('================================================');
console.log('Action : AI Code Review');
console.log('用途 : AI 多角色 code review:攻擊方找問題、防守方裁決誤報,結果留言到 PR 並保存 findings');
-console.log('更新時間: 2026/07/17 16:49:21');
+console.log('更新時間: 2026/07/17 18:49:58');
console.log('================================================');
const fs = require('fs');
@@ -42,9 +42,9 @@ const BOT_COMMIT_PREFIX = 'chore: update ai-review findings [ai-review-bot]';
* @param {Array