diff --git a/.gitea/ai-review/exclusions.json b/.gitea/ai-review/exclusions.json
new file mode 100644
index 0000000..a97c27a
--- /dev/null
+++ b/.gitea/ai-review/exclusions.json
@@ -0,0 +1,69 @@
+[
+ {
+ "addedAt": "2026/08/07 16:47:53",
+ "prNumber": 4,
+ "reviewer": "Bard",
+ "severity": "警告",
+ "file": "readme.md",
+ "startLine": 3,
+ "endLine": 3,
+ "problem": "這份 README 已經長成機械化的 API 編目,還把時間戳與大量硬編碼連結一起寫進來,讓主文件變得又厚又脆,讀者很難快速抓到重點。",
+ "reason": "此專案的 README 本身就是生成式 API 參考文件,維持完整索引與連結有助於內部使用,屬於文件取捨而非功能性缺陷。"
+ },
+ {
+ "location": "src/main.js:59",
+ "role": "Bard",
+ "original_finding": "刪掉這種會隨流程變動而失真的數量型描述;若真要提醒收尾差異,改成更穩定的概念性說明即可。",
+ "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)"
+ },
+ {
+ "location": "entrypoint.sh:2",
+ "role": "Bard",
+ "original_finding": "移除這種會過期的時間戳註解,只保留真正需要提醒讀者的簡短說明即可。",
+ "reason": "AI 對話收斂判定為誤報(問題在最新程式碼中不成立或不適用)"
+ },
+ {
+ "addedAt": "2026/08/07 16:39:03",
+ "prNumber": null,
+ "reviewer": "Assassin",
+ "severity": "警告",
+ "file": "action.yml",
+ "startLine": 14,
+ "endLine": 14,
+ "problem": "action.yml 中新增的 inputs.model 沒有在 GitHub Actions 層面進行輸入驗證。雖然描述寫著「僅允許英數字、點、底線與連字號」,但使用者可以提供任意字符,這些值會先進入環境變數,再由程式端驗證。",
+ "reason": "GitHub / Gitea 的 action schema 不提供字串輸入的正則驗證;本專案已在程式端做完整驗證,action.yml 無法再向前移到平台層。"
+ },
+ {
+ "addedAt": "2026/08/07 16:39:03",
+ "prNumber": null,
+ "reviewer": "Bard",
+ "severity": "警告",
+ "file": "readme.md",
+ "startLine": 1,
+ "endLine": 1,
+ "problem": "新文件採用小寫 readme.md,和倉庫中常見的 README.md 命名慣例不合。",
+ "reason": "這個檔名是現有專案慣例的一部分,直接改名會牽動大量內部連結與生成內容,屬於文件命名取捨。"
+ },
+ {
+ "addedAt": "2026/08/07 16:39:03",
+ "prNumber": null,
+ "reviewer": "Mage",
+ "severity": "警告",
+ "file": "src/findings.js",
+ "startLine": 658,
+ "endLine": 658,
+ "problem": "applyExclusions 的比對邏輯在 (locationMatches && roleMatches && (textMatches || ...)) 中,若排除規則只指定 filePath 不指定 role,會產生「該檔案內所有角色的問題都被排除」的非預期行為;若只指定 role 不指定 filePath,則「該角色所有檔案的問題都被排除」。此為對稱性缺陷",
+ "reason": "此處的排除規則刻意把 filePath / role 當成可獨立放寬的過濾條件,讓已知誤報可以用較粗粒度收斂;行為與設計一致。"
+ },
+ {
+ "addedAt": "2026/08/07 16:39:03",
+ "prNumber": null,
+ "reviewer": "Mage",
+ "severity": "建議",
+ "file": "src/resolve.js",
+ "startLine": 84,
+ "endLine": 84,
+ "problem": "groupConversations 在設置 botFinding 時用 botFindings[0],若該對話的 botFindings 陣列為空,botFinding 會為 undefined。",
+ "reason": "程式已將 botFinding 以 null 初始化,且下游邏輯以 botFindings 陣列為主要資料來源;此為相容舊邏輯的保守設計。"
+ }
+]
diff --git a/.gitea/ai-review/findings.json b/.gitea/ai-review/findings.json
new file mode 100644
index 0000000..9f77cf2
--- /dev/null
+++ b/.gitea/ai-review/findings.json
@@ -0,0 +1,53 @@
+{
+ "generatedAt": "2026/08/08 00:46:03",
+ "commitSha": "ab384fe1080e5265724747a8faa960e4e960bf68",
+ "prNumber": 4,
+ "tool": {
+ "name": "ai-code-review",
+ "version": "1.0.0",
+ "model": "auto"
+ },
+ "findings": [
+ {
+ "level": "warning",
+ "role": "Mage",
+ "problem": "summarizeApiError 函式內存取 e.stderr 與 e.stdout 時未使用可選鏈,若 e 為 null 或 undefined,會拋出 TypeError 而非優雅容錯",
+ "suggestion": "改用可選鏈:`const stderr = e?.stderr || ''` 與 `const stdout = e?.stdout || ''`,或在函式開頭加入 `if (!e) return String(e);` 早期退出",
+ "location": "src/llm.js:161",
+ "is_new": false
+ },
+ {
+ "level": "warning",
+ "role": "Assassin",
+ "location": "src/config.js:44",
+ "problem": "這裡只限制字元種類,卻還放行 `.` 與 `/`,因此像 `../foo`、`foo/../../bar` 這類路徑式字串仍可通過。攻擊者只要能控制 `inputs.model` 或 `CLI_PROXY_API_MODEL`,就能把惡意 model 值送進 CLIProxyAPI;若後端拿 model 名稱去拼路徑、呼叫指令或做檔名查找,這個輸入就可能被拿來做路徑穿越或指令注入。",
+ "suggestion": "不要只做字元白名單,應改成明確白名單比對可用模型 slug,並額外拒絕 `..`、前導/結尾 `/`、連續 `/`、反斜線與控制字元;如果可行,直接用 `/v1/models` 回傳清單做嚴格選擇,而不是接受任意形狀的字串。",
+ "is_new": true
+ },
+ {
+ "level": "info",
+ "role": "Mage",
+ "problem": "mergeFindings 用 suggestion 前 50 字作為 key 的一部分進行去重。若兩個 findings 的 role 與 location 相同但 suggestion 在第 50 字之後才出現差異,會被誤判為重複而遭移除",
+ "suggestion": "考慮是否改用完整 suggestion 或增加其他識別字段(如 problem)來組成 key,確保去重不會誤刪本質不同的問題",
+ "location": "src/findings.js:349",
+ "is_new": false
+ },
+ {
+ "level": "info",
+ "role": "Assassin",
+ "problem": "formatTimestamp 函數依賴 Intl.DateTimeFormat.formatToParts 的實現細節。若回應結構不符預期,`map.year`、`map.month` 等會是 `undefined`,導致日誌中顯示 `undefined` 字樣。雖然不影響安全性,但可能造成日誌混亂及除錯困難。",
+ "suggestion": "加強容錯處理。在存取 `map.year` 等屬性前先驗證其存在性;或改用更穩定的日期格式化方式(如 `new Date().toISOString()`)。同時增加單元測試,確保在異常情況下(例如不同的語言環境或舊版本瀏覽器)仍能產生正確的日誌格式。",
+ "location": "src/log.js:19",
+ "is_new": false
+ },
+ {
+ "level": "info",
+ "role": "Assassin",
+ "location": "readme.md:9",
+ "problem": "這份新增文件把內部 Gitea 網域與完整倉庫路徑直接寫進專案內容。只要文件被外部看見,攻擊者就能先掌握內部服務命名、URL 模式與專案結構,降低枚舉、釣魚與後續橫向移動的成本。",
+ "suggestion": "如果這份文件有外部可見的可能,請把內網主機名與完整路徑改成相對路徑或 placeholder,並把只限內部使用的操作細節移到不對外公開的位置。",
+ "is_new": true
+ }
+ ],
+ "excluded": []
+}
diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index 0125fd1..28e79fe 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -1,5 +1,7 @@
-# 用途:CI workflow 的 command-file 草稿,保留原始流程並補上逐行說明。
-# 更新時間:2026/07/11 19:00:45
+# ============================================================================
+# 用途:這是 pull request 時計算版本、視情況發佈 release、並在目標分支為 develop 的 beta 情境下執行 AI Code Review 的 CI workflow。
+# 更新時間:2026/08/07 13:51:53
+# ============================================================================
# workflow 名稱,對應 Gitea UI 中的顯示標題。
name: CI
# 定義此 workflow 的觸發事件。
diff --git a/.gitea/workflows/master.yaml b/.gitea/workflows/master.yaml
index 95fa62c..b61089d 100644
--- a/.gitea/workflows/master.yaml
+++ b/.gitea/workflows/master.yaml
@@ -1,5 +1,7 @@
-# 用途:master workflow 的 command-file 草稿,保留原始流程並補上逐行說明。
-# 更新時間:2026/07/11 19:00:45
+# ============================================================================
+# 用途:推送到 master 分支後,輸出 Gitea context、查詢 commit 對應 tag,作為部署前資訊檢查的 CD workflow
+# 更新時間:2026/08/07 13:51:53
+# ============================================================================
# workflow 名稱,對應 Gitea UI 中的顯示標題。
name: CD
# 定義此 workflow 的觸發事件。
diff --git a/.gitea/workflows/readme.md b/.gitea/workflows/readme.md
index d8c10be..c13f723 100644
--- a/.gitea/workflows/readme.md
+++ b/.gitea/workflows/readme.md
@@ -1,49 +1,58 @@
-# GITEA NODE ACTION 工作流說明草稿
+# Gitea Workflow 說明文件
-更新時間:2026/07/11 18:54:51
+更新時間:2026/08/07 13:51:53
## 總覽
-此專案目前包含兩個 workflow:
+此專案(`ai-code-review`)目前包含兩個 workflow:
-- `CI`:處理 pull request 期間的版本計算、釋出與 AI 程式碼審查。
-- `CD`:處理推送到 `master` 分支後的部署相關檢查與資訊輸出。
+| Workflow 名稱 | 檔案位置 | 觸發條件 | 大致用途 |
+| --- | --- | --- | --- |
+| `CI` | `.gitea/workflows/ci.yaml` | `pull_request`(`opened`、`synchronize`) | 計算版本號、必要時建立 release,並在目標分支為 `develop`(beta 情境)時執行 AI 程式碼審查(呼叫本專案自身發佈的 `ai-code-review` action) |
+| `CD` | `.gitea/workflows/master.yaml` | `push` 到 `master` 分支 | 輸出完整 Gitea event context,並查詢指定 commit 對應的 tag,作為後續部署流程的基礎資訊 |
+
+以下依 workflow 檔案逐一整理細節。
## Workflow 明細
-### CI
+### CI(`.gitea/workflows/ci.yaml`)
-- 檔案位置:`.gitea/workflows/ci.yaml`
-- 用途:在 pull request 事件中計算版本,必要時建立 release,並在 beta 情境下執行 AI 程式碼審查。
-- 觸發條件:`pull_request`,事件類型為 `opened` 與 `synchronize`。
-- 主要輸入 / 環境參數:
- - `gitea.base_ref`:用來判斷是否為 `develop`,進而決定 `IS_BETA`。
- - `vars.ACTION_CALCULATE_VERSION`:提供 `calculate-version` action 的版本。
- - `vars.ACTION_GITEA_RELEASE_VERSION`:提供 release action 的版本。
- - `secrets.LLM_OAUTH`:設定 LLM CLI 的 OAuth。
- - `secrets.TOKEN`:提供 AI 程式碼審查 action 存取 Gitea API。
- - `vars.LLM_NAME`:指定審查使用的模型名稱。
-- 重要注意事項:
- - `test` job 只會在 `IS_BETA == true` 時執行,也就是 pull request 目標分支為 `develop` 時。
- - `Publishing Release` 會使用 `VERSION` 與 `gitea.sha` 建立 release 與 tag。
- - 若變數或 secret 未設定,對應步驟會失敗,需人工確認部署前置條件。
+- **workflow 名稱**:`CI`(Gitea UI 顯示標題)
+- **檔案位置**:`.gitea/workflows/ci.yaml`
+- **用途說明**:
+ - `build` job:計算版本號(呼叫 `calculate-version` action),並用計算出的版本建立 Gitea release/tag。
+ - `test` job:僅在 `build` job 判定為 beta 情境時執行,呼叫本專案自身發佈的 `ai-code-review` action 對 PR 進行 AI 程式碼審查。
+ - `result` job:等待前兩個 job 完成後,將版本號輸出到 log,作為流程結尾的確認步驟。
+- **觸發條件**:`pull_request` 事件,且事件類型限定為 `opened` 與 `synchronize`(PR 建立與後續推送同步時觸發)。
+- **主要輸入 / 環境參數**:
+ - `gitea.base_ref`:用來判斷 PR 目標分支是否為 `develop`,據以設定 `IS_BETA` 環境變數。
+ - `vars.ACTION_CALCULATE_VERSION`:`calculate-version` action 的版本(`build` job 使用)。
+ - `vars.ACTION_GITEA_RELEASE_VERSION`:`akkuman/gitea-release-action` 的版本(`build` job 使用)。
+ - `gitea.event.repository.name`、`gitea.sha`:組成 release 名稱與 `target_commitish`。
+ - `needs.build.outputs.version`、`needs.build.outputs.is_beta`:`test`、`result` job 透過 job 輸出取得版本號與 beta 判定結果。
+- **重要注意事項**:
+ - `test` job 的執行條件為 `needs.build.outputs.is_beta == 'true'`,即 PR 目標分支(`gitea.base_ref`)為 `develop` 時才會執行 AI 程式碼審查。
+ - `Publishing Release` 步驟會用 `VERSION` 與 `gitea.sha` 建立對應的 release 與 tag(`v${{ env.VERSION }}`),beta 情境下標記為 prerelease。
+ - `test` job 的「Run AI Code Review」步驟在 `ci.yaml` 中本身**未**透過 `with` 或 `env` 傳入任何額外參數;該步驟呼叫的是本專案自身發佈的 `ai-code-review@v{VERSION}` action,實際會用到哪些 `secrets.*`/`vars.*`(例如 action 內部的 token、CLI Proxy 相關設定)屬於該 action 自身的定義範圍,不在 `ci.yaml` 這個 workflow 檔案內顯式宣告,**需人工確認**該 action 版本實際所需的機密與變數是否已在目標環境設定妥當。
+ - 若 `vars.ACTION_CALCULATE_VERSION`、`vars.ACTION_GITEA_RELEASE_VERSION` 等變數未設定,對應步驟會失敗,需人工確認部署前置條件是否齊備。
-### CD
+### CD(`.gitea/workflows/master.yaml`)
-- 檔案位置:`.gitea/workflows/master.yaml`
-- 用途:在 `master` 分支推送後輸出 Gitea context、檢查提交標籤,作為後續部署流程的基礎。
-- 觸發條件:`push` 到 `master` 分支。
-- 主要輸入 / 環境參數:
- - `gitea` 事件內容:轉成 `GITEA_CONTEXT` 後交給 `jq` 顯示。
- - `gitea.event.commits[1].id`:作為 `COMMIT_SHA`,用來查詢 commit tag。
- - `vars.ACTION_CHECKOUT_VERSION`:提供 `actions/checkout` 的版本。
- - `GITEA_OUTPUT`:寫入 `git describe --contains` 的結果。
-- 重要注意事項:
- - `COMMIT_SHA` 取用 commits 陣列的第 2 筆資料,若 push 事件實際只有 1 筆 commit,需人工確認是否會發生索引風險。
- - `Get Commit Tag` 依賴完整的 git 歷史與 tags,因此 checkout 已設定 `fetch-depth: 0` 與 `fetch-tags: true`。
- - `Show Gitea Context` 會輸出完整事件內容,若包含敏感資訊,需注意執行環境的日誌保存策略。
+- **workflow 名稱**:`CD`(Gitea UI 顯示標題)
+- **檔案位置**:`.gitea/workflows/master.yaml`
+- **用途說明**:單一 `deploy` job,在推送到 `master` 分支後,輸出完整的 Gitea event context,並取回完整原始碼與 tag 歷史後,查詢指定 commit 對應的 tag,將結果印出。整個 workflow 目前僅做資訊輸出與查詢,未包含實際部署動作。
+- **觸發條件**:`push` 事件,且限定分支為 `master`。
+- **主要輸入 / 環境參數**:
+ - `gitea`(整個 context,透過 `toJSON(gitea)` 轉字串):以 `GITEA_CONTEXT` 環境變數輸出並用 `jq` 顯示。
+ - `gitea.event.commits[1].id`:作為 `COMMIT_SHA`,用來查詢該 commit 對應的 tag。
+ - `vars.ACTION_CHECKOUT_VERSION`:`actions/checkout` action 的版本。
+ - `GITEA_OUTPUT`:`Get Commit Tag` 步驟將 `git describe --contains` 的查詢結果寫入此檔案,供 `steps.commit.outputs.tag` 讀取。
+- **重要注意事項**:
+ - **需人工確認**:`COMMIT_SHA` 目前固定取用 `gitea.event.commits` 陣列的第 2 筆(索引 1,即 `commits[1]`)。若一次 `push` 事件只包含 1 筆 commit,該索引將不存在,`COMMIT_SHA` 可能為空值,導致後續 `git describe --contains` 查詢失敗或行為不符預期;是否需改為取最後一筆(例如 `commits[-1]` 或依陣列長度動態取值)需人工確認並評估是否調整(本文件僅整理現況,未變更任何 workflow 實際邏輯)。
+ - `Get Commit Tag` 步驟依賴完整的 git 歷史與 tag 資訊,因此 `Source Code Checkout` 已設定 `fetch-depth: 0` 與 `fetch-tags: true`,若移除這兩個設定會導致 `git describe --contains` 查不到結果。
+ - `Show Gitea Context` 會將完整事件內容輸出到 log,若事件內容包含敏感資訊,需注意執行環境的日誌保存與存取權限策略。
## 備註
-- 本檔為草稿版本,僅整理 workflow 行為與設定重點,不修改任何 workflow 實際邏輯。
-- 若後續要覆蓋正式檔,請先確認 `ci.yaml` 與 `master.yaml` 的變數與 secret 已在目標環境中正確配置。
+- 本文件僅整理 `ci.yaml`、`master.yaml` 兩個 workflow 檔案目前的行為與參數重點,內容依實際檔案內容彙整,未新增或臆測未在檔案中出現的流程與參數;標註「需人工確認」之處為既有設計中需要人工再次確認的風險點,非文件本身待補內容。
+- 若後續要以本文件覆蓋既有說明文件,請先確認 `ci.yaml` 與 `master.yaml` 所引用的 `vars.*`、`secrets.*` 已在目標環境(Gitea repo/organization 設定)中正確配置。
diff --git a/Dockerfile b/Dockerfile
index bb4b53d..53e3cb5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,23 @@
+# ============================================================================
+# 用途:Docker 容器 action 的建置檔,以 Node 24 Alpine 為基底安裝 git 與
+# ca-certificates,並將 action 原始碼與進入點腳本複製進容器、設定執行進入點。
+# 更新時間:2026/08/07 13:51:53
+# ============================================================================
+
+# 使用 Node 24 的 Alpine 精簡映像檔作為基底,提供 node 執行環境並縮小最終映像檔體積
FROM node:24-alpine
+# 安裝 git 與 ca-certificates:action 執行期需要 clone/操作 git 倉庫,且透過 HTTPS 呼叫外部 API 時需要憑證驗證;--no-cache 可避免留下 apk 索引快取、進一步縮小映像檔
RUN apk add --no-cache git ca-certificates
+# 複製 action 主程式原始碼到容器內的 /action/src/,供 entrypoint 執行時呼叫
COPY src/ /action/src/
+
+# 複製容器啟動時要執行的進入點腳本到 /action/entrypoint.sh
COPY entrypoint.sh /action/entrypoint.sh
+# 賦予 entrypoint.sh 執行權限,確保容器啟動時能直接執行該腳本
RUN chmod +x /action/entrypoint.sh
+# 設定容器的進入點為 entrypoint.sh,容器啟動時會執行此腳本作為 action 的入口
ENTRYPOINT ["/action/entrypoint.sh"]
diff --git a/README.md b/README.md
deleted file mode 100644
index 7ea73e4..0000000
--- a/README.md
+++ /dev/null
@@ -1,1451 +0,0 @@
-# AI Code Review
-
-更新時間:2026/07/11 19:18:23
-
-## 專案列表
-
-| 專案名稱 | 專案描述 |
-| --- | --- |
-| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/) | 此專案提供 Gitea 工作流程中的 AI 程式碼審查、findings / exclusions 管理、CLIProxyAPI 橋接與 git / Gitea 前置驗證工具。 |
-
-| 專案名稱 | 參考專案列表 |
-| --- | --- |
-| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/) | 無 |
-
-| 專案名稱 | NuGet 套件列表 |
-| --- | --- |
-| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/) | 無 |
-
-## 功能列表
-
-### AI Code Review
-
-| 功能名稱 | 功能描述 |
-| --- | --- |
-| [parseLocation](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L66) | [解析 finding location 為檔案與行號。](#parselocation) |
-| [formatFindingsStats](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L150) | [產生 findings 統計表格。](#formatfindingsstats) |
-| [formatFindingsStatsLine](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L171) | [產生 findings 單行統計摘要。](#formatfindingsstatsline) |
-| [postFindingsReview](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L220) | [發布 findings 的 Gitea review。](#postfindingsreview) |
-| [saveFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L259) | [將 findings 寫入工作區與鏡像目錄。](#savefindings) |
-| [postOldFindingsComment](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L274) | [發布舊問題的 comment。](#postoldfindingscomment) |
-| [postNewNonCriticalComment](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L288) | [發布新問題中的非嚴重 comment。](#postnewnoncriticalcomment) |
-| [postNewCriticalComments](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/comments.js#L304) | [發布新嚴重問題的 comment。](#postnewcriticalcomments) |
-| [getInsecureHttpsAgent](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/config.js#L60) | [取得一個關閉 TLS 憑證驗證的 HTTPS Agent 單例,供內部服務連線使用。](#getinsecurehttpsagent) |
-| [getLLMConfig](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/config.js#L130) | [依環境變數解析目前可用的 CLIProxyAPI 設定。](#getllmconfig) |
-| [analyzeWithRole](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L14) | [用指定角色分析 diff 並產生 findings。](#analyzewithrole) |
-| [normalizeText](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L120) | [將文字正規化成比對用形式。](#normalizetext) |
-| [loadOldFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L302) | [讀取舊 findings 並標記為舊問題。](#loadoldfindings) |
-| [mergeFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L319) | [合併新舊 findings 並去重。](#mergefindings) |
-| [sortByLevel](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L335) | [依嚴重度排序 findings。](#sortbylevel) |
-| [resolveMissingLineNumbers](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L376) | [為缺少行號的 findings 補上行號。](#resolvemissinglinenumbers) |
-| [deduplicateWithAI](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L423) | [用 AI 進行 findings 語意去重。](#deduplicatewithai) |
-| [loadExclusions](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L450) | [讀取並正規化 exclusions。](#loadexclusions) |
-| [appendExclusions](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L500) | [將新的排除條目追加到 exclusions 檔。](#appendexclusions) |
-| [applyExclusions](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L544) | [依 exclusions 過濾 findings。](#applyexclusions) |
-| [filterFalsePositivesWithAI](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/findings.js#L577) | [用 AI 過濾誤報 findings。](#filterfalsepositiveswithai) |
-| [getRepoState](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/git.js#L120) | [讀取指定 git repo 的基本狀態快照,包含 HEAD、分支與 commit 時間。](#getrepostate) |
-| [getHeadCommitMessage](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/git.js#L138) | [讀取指定 repo 的 HEAD commit 完整 commit message,失敗時保守回傳空字串。](#getheadcommitmessage) |
-| [isBotAutoCommit](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/git.js#L153) | [判斷 HEAD commit 是否由 AI Review bot 自動產生,避免重複觸發後續流程。](#isbotautocommit) |
-| [verifyRemoteAccess](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/git.js#L163) | [先用 `git ls-remote` 驗證 remote 認證與連線是否可用,失敗時回傳結構化錯誤。](#verifyremoteaccess) |
-| [cloneRepo](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/git.js#L178) | [以可重入方式抓取 PR head branch 到工作目錄內的 `repo` 資料夾。](#clonerepo) |
-| [commitAndPush](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/git.js#L220) | [將 AI 審查產出的檔案結轉、提交並推回 PR head branch,失敗時保守記錄 log 而不中斷流程。](#commitandpush) |
-| [getBotReviewOutcome](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L39) | [解析 AI Review bot 的結果標記,回傳 success、failure 或 unknown。](#getbotreviewoutcome) |
-| [parseReviewIgnore](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L62) | [把 `.reviewignore` 文字解析成可用的排除前綴陣列。](#parsereviewignore) |
-| [getReviewIgnore](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L74) | [讀取並解析 PR 的 `.reviewignore`,沒有自訂規則時回退預設排除清單。](#getreviewignore) |
-| [getPRDiff](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L89) | [取得目前 PR 的 unified diff,並套用 `.reviewignore` 與內建過濾規則。](#getprdiff) |
-| [getCommitMessageBySha](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L101) | [依 commit SHA 讀取 Gitea 上的 commit message,失敗時保守回空字串。](#getcommitmessagebysha) |
-| [getBranchHeadCommitMessage](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L122) | [讀取指定分支 head commit 的訊息,失敗時保守回空字串。](#getbranchheadcommitmessage) |
-| [shouldSkipBotCommit](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L147) | [判斷目前 PR head 是否為 bot 自動提交,若是就跳過後續審查流程。](#shouldskipbotcommit) |
-| [filterDiff](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L164) | [過濾 unified diff 中不需要審查的路徑區塊,保留其餘內容。](#filterdiff) |
-| [postComment](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L184) | [在 PR 底下發布一則 Markdown 留言,適合非行內評論用途。](#postcomment) |
-| [postPullReviewComment](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L203) | [對 PR 指定檔案與行號發送單筆行內 review comment。](#postpullreviewcomment) |
-| [postPullReview](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L226) | [建立一則包含摘要與多筆行內 comment 的 PR review。](#postpullreview) |
-| [listPullReviews](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L246) | [列出目前 PR 的所有 review,回應格式不正確時保守回空陣列。](#listpullreviews) |
-| [getPullReviewComments](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L260) | [依 review ID 取得該 review 底下的所有行內 comment。](#getpullreviewcomments) |
-| [listAllReviewComments](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L274) | [彙整目前 PR 的所有 review comments,單筆失敗時略過並持續處理。](#listallreviewcomments) |
-| [resolvePullReviewComment](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L296) | [解決指定 review comment 對話,對應 Gitea 的 resolve API。](#resolvepullreviewcomment) |
-| [getFileContentAtRef](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/gitea.js#L313) | [讀取指定 ref 下的檔案文字內容,支援 base64 解碼並在失敗時保守回空字串。](#getfilecontentatref) |
-| [stripCodeFence](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/json.js#L17) | [移除文字外層的 markdown code fence,並清理前後空白。](#stripcodefence) |
-| [repairJSONArrayWithAI](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/json.js#L43) | [透過 LLM 將原始內容修復成可直接 JSON.parse 的 JSON 陣列字串。](#repairjsonarraywithai) |
-| [validateJSONArrayFile](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/json.js#L93) | [驗證 JSON 檔案是否合法,必要時嘗試透過 AI 修復一次。](#validatejsonarrayfile) |
-| [ensureJSONArrayFileExists](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/json.js#L134) | [確保指定路徑存在一個 JSON 檔案,不存在時建立空陣列檔。](#ensurejsonarrayfileexists) |
-| [mapWithConcurrency](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/llm.js#L25) | [以可控制併發數的方式並行處理陣列項目。](#mapwithconcurrency) |
-| [extractMeaningfulError](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/llm.js#L96) | [從 CLI 原始輸出中擷取最有用的錯誤訊息。](#extractmeaningfulerror) |
-| [chat](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/llm.js#L190) | [呼叫 CLIProxyAPI,並回傳文字回應。](#chat) |
-| [chatJSON](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/llm.js#L218) | [呼叫 AI 助理並把回應解析成 JSON。](#chatjson) |
-| [extractBalancedJSON](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/llm.js#L255) | [從指定索引開始擷取完整平衡的 JSON 片段。](#extractbalancedjson) |
-| [extractJSONText](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/llm.js#L298) | [從雜訊文字中抽出最可能的 JSON 內容。](#extractjsontext) |
-| [section](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L9) | [輸出最上層區塊標題,用來切分整體執行流程。](#section) |
-| [step](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L22) | [輸出流程中的步驟標題,標示某一小段工作內容。](#step) |
-| [line](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L34) | [輸出一行中性的明細資訊。](#line) |
-| [input](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L45) | [輸出某一步驟的輸入描述,方便追蹤資料來源。](#input) |
-| [output](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L56) | [輸出某一步驟的產出描述,方便追蹤結果。](#output) |
-| [result](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L69) | [依布林值輸出成功或失敗的檢查結果。](#result) |
-| [ok](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L81) | [輸出一筆成功或完成訊息。](#ok) |
-| [warn](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L93) | [輸出一筆警告訊息到 stderr。](#warn) |
-| [error](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/log.js#L105) | [輸出一筆錯誤訊息到 stderr。](#error) |
-| [main](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/main.js#L55) | [執行 AI Code Review Pipeline 的完整流程。](#main) |
-| [checkRequiredEnv](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/preflight.js#L60) | [檢查前置驗證所需的必要環境值是否齊全。](#checkrequiredenv) |
-| [verifyGiteaToken](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/preflight.js#L77) | [驗證 Gitea token 是否可讀取指定 repository。](#verifygiteatoken) |
-| [verifyCommentToken](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/preflight.js#L94) | [驗證 comment token 是否可用;未提供時回傳 skipped。](#verifycommenttoken) |
-| [fetchLLMModels](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/preflight.js#L118) | [呼叫 CLIProxyAPI 的模型清單端點並取得目前可用的模型 id 清單。](#fetchllmmodels) |
-| [verifyLLM](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/preflight.js#L171) | [驗證目前環境是否有可用的 CLIProxyAPI 設定與對應模型。](#verifyllm) |
-| [runPreflight](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/preflight.js#L203) | [執行所有前置驗證流程,任一失敗即回傳 false。](#runpreflight) |
-| [parseBotReviewComment](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L51) | [解析 bot 產生的 review comment,還原成 finding 欄位物件。](#parsebotreviewcomment) |
-| [groupConversations](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L73) | [依檔案路徑與行號把 review comments 收斂成對話群組。](#groupconversations) |
-| [codeWindow](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L100) | [擷取目標行附近的程式碼片段,供 AI 判讀。](#codewindow) |
-| [judgeConversations](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L126) | [處理 judgeConversations 相關邏輯。](#judgeconversations) |
-| [isSafeRepoPath](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L174) | [檢查路徑是否安全,避免讀取 repo 外檔案。](#issaferepopath) |
-| [reconcileConversations](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L190) | [收斂 PR review 對話,並依 AI 裁定回填 findings 的去向。](#reconcileconversations) |
-| [dropResolvedFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L329) | [移除已解決對話對應的 findings。](#dropresolvedfindings) |
-| [addCarriedFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/resolve.js#L338) | [將仍成立但缺漏的 findings 補回清單。](#addcarriedfindings) |
-| [parseRoleFile](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L25) | [解析角色 Markdown 檔,取出 frontmatter 與本文。](#parserolefile) |
-| [loadRoles](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L71) | [只載入攻擊方角色。](#loadroles) |
-| [loadRole](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L85) | [依名稱查找單一角色。](#loadrole) |
-| [buildAnalysisPrompt](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L104) | [產生攻擊方角色的程式碼審查 system prompt。](#buildanalysisprompt) |
-| [buildLocateLinePrompt](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L149) | [產生用來補 finding 行號的 prompt。](#buildlocatelineprompt) |
-| [buildVerdictPrompt](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L173) | [產生單條 finding 的誤報裁決 prompt。](#buildverdictprompt) |
-| [getRoleIntro](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/roles.js#L206) | [產生 AI Code Review 團隊的 Markdown 介紹表。](#getroleintro) |
-| [extractUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L24) | [從 LLM 回應抽出 token usage。](#extractusage) |
-| [recordUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L65) | [記錄一次 LLM 呼叫使用量。](#recordusage) |
-| [getRunUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L77) | [取得目前累積的使用量。](#getrunusage) |
-| [resetRunUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L82) | [重置執行中的 usage 累計。](#resetrunusage) |
-| [recordRateLimit](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L109) | [記錄最近一次速率配額資訊。](#recordratelimit) |
-| [getRateLimit](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L130) | [取得最近一次 rate limit 快照。](#getratelimit) |
-| [resetRateLimit](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L135) | [重置 rate limit 快照。](#resetratelimit) |
-| [fetchAccountQuota](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L209) | [查詢指定平台的帳號額度。](#fetchaccountquota) |
-| [resolveRemainingPercent](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L278) | [計算可用額度剩餘百分比。](#resolveremainingpercent) |
-| [formatUsageStats](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L315) | [產生 AI 助理使用量 Markdown 區塊。](#formatusagestats) |
-| [formatUsageStatsLine](https://gitea.jsc.idv.tw/actions/ai-code-review/blob/develop/src/usage.js#L334) | [產生使用量單行摘要。](#formatusagestatsline) |
-
-## 使用範例
-
-### parseLocation
-
-解析 finding location 為檔案與行號。
-
-檔案位置:`src/comments.js` 第 66 行。
-
-```js
-const result = parseLocation(location);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### formatFindingsStats
-
-產生 findings 統計表格。
-
-檔案位置:`src/comments.js` 第 150 行。
-
-```js
-const result = formatFindingsStats(findings);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### formatFindingsStatsLine
-
-產生 findings 單行統計摘要。
-
-檔案位置:`src/comments.js` 第 171 行。
-
-```js
-const result = formatFindingsStatsLine(findings);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### postFindingsReview
-
-發布 findings 的 Gitea review。
-
-檔案位置:`src/comments.js` 第 220 行。
-
-```js
-const result = await postFindingsReview(findings, deps);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### saveFindings
-
-將 findings 寫入工作區與鏡像目錄。
-
-檔案位置:`src/comments.js` 第 259 行。
-
-```js
-const result = saveFindings(workspace, findings, mirrorDir);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### postOldFindingsComment
-
-發布舊問題的 comment。
-
-檔案位置:`src/comments.js` 第 274 行。
-
-```js
-const result = await postOldFindingsComment(findings);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### postNewNonCriticalComment
-
-發布新問題中的非嚴重 comment。
-
-檔案位置:`src/comments.js` 第 288 行。
-
-```js
-const result = await postNewNonCriticalComment(findings);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### postNewCriticalComments
-
-發布新嚴重問題的 comment。
-
-檔案位置:`src/comments.js` 第 304 行。
-
-```js
-const result = await postNewCriticalComments(findings, deps);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### getInsecureHttpsAgent
-
-取得一個關閉 TLS 憑證驗證的 HTTPS Agent 單例,供內部服務連線使用。
-
-檔案位置:`src/config.js` 第 60 行。
-
-```js
-const result = getInsecureHttpsAgent();
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getLLMConfig
-
-依環境變數解析目前可用的 CLIProxyAPI 設定。
-
-檔案位置:`src/config.js` 第 130 行。
-
-```js
-const result = getLLMConfig(commandExistsFn);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### analyzeWithRole
-
-用指定角色分析 diff 並產生 findings。
-
-檔案位置:`src/findings.js` 第 14 行。
-
-```js
-const result = await analyzeWithRole(role, diff);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### normalizeText
-
-將文字正規化成比對用形式。
-
-檔案位置:`src/findings.js` 第 120 行。
-
-```js
-const result = normalizeText(value);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### loadOldFindings
-
-讀取舊 findings 並標記為舊問題。
-
-檔案位置:`src/findings.js` 第 302 行。
-
-```js
-const result = loadOldFindings(workspace);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### mergeFindings
-
-合併新舊 findings 並去重。
-
-檔案位置:`src/findings.js` 第 319 行。
-
-```js
-const result = mergeFindings(oldFindings, newFindings);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### sortByLevel
-
-依嚴重度排序 findings。
-
-檔案位置:`src/findings.js` 第 335 行。
-
-```js
-const result = sortByLevel(findings);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### resolveMissingLineNumbers
-
-為缺少行號的 findings 補上行號。
-
-檔案位置:`src/findings.js` 第 376 行。
-
-```js
-const result = await resolveMissingLineNumbers(findings, diff, deps);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### deduplicateWithAI
-
-用 AI 進行 findings 語意去重。
-
-檔案位置:`src/findings.js` 第 423 行。
-
-```js
-const result = await deduplicateWithAI(findings);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### loadExclusions
-
-讀取並正規化 exclusions。
-
-檔案位置:`src/findings.js` 第 450 行。
-
-```js
-const result = loadExclusions(workspace, repoState, mirrorWorkspace);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### appendExclusions
-
-將新的排除條目追加到 exclusions 檔。
-
-檔案位置:`src/findings.js` 第 500 行。
-
-```js
-const result = appendExclusions(workspace, newEntries, mirrorWorkspace);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### applyExclusions
-
-依 exclusions 過濾 findings。
-
-檔案位置:`src/findings.js` 第 544 行。
-
-```js
-const result = applyExclusions(findings, exclusions);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### filterFalsePositivesWithAI
-
-用 AI 過濾誤報 findings。
-
-檔案位置:`src/findings.js` 第 577 行。
-
-```js
-const result = await filterFalsePositivesWithAI(findings, exclusions, chatFn);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### getRepoState
-
-讀取指定 git repo 的基本狀態快照,包含 HEAD、分支與 commit 時間。
-
-檔案位置:`src/git.js` 第 120 行。
-
-```js
-const result = getRepoState(repoDir, _spawnSync);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getHeadCommitMessage
-
-讀取指定 repo 的 HEAD commit 完整 commit message,失敗時保守回傳空字串。
-
-檔案位置:`src/git.js` 第 138 行。
-
-```js
-const result = getHeadCommitMessage(repoDir, _spawnSync);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### isBotAutoCommit
-
-判斷 HEAD commit 是否由 AI Review bot 自動產生,避免重複觸發後續流程。
-
-檔案位置:`src/git.js` 第 153 行。
-
-```js
-const result = isBotAutoCommit(repoDir, _spawnSync);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### verifyRemoteAccess
-
-先用 `git ls-remote` 驗證 remote 認證與連線是否可用,失敗時回傳結構化錯誤。
-
-檔案位置:`src/git.js` 第 163 行。
-
-```js
-const result = verifyRemoteAccess(workspace, _spawnSync);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳驗證成功/失敗的結構化結果。
-
-### cloneRepo
-
-以可重入方式抓取 PR head branch 到工作目錄內的 `repo` 資料夾。
-
-檔案位置:`src/git.js` 第 178 行。
-
-```js
-const result = cloneRepo(workspace, _spawnSync);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### commitAndPush
-
-將 AI 審查產出的檔案結轉、提交並推回 PR head branch,失敗時保守記錄 log 而不中斷流程。
-
-檔案位置:`src/git.js` 第 220 行。
-
-```js
-const result = await commitAndPush(workspace, repoDir, _spawnSync, _sourceRoot, reviewOutcome);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### getBotReviewOutcome
-
-解析 AI Review bot 的結果標記,回傳 success、failure 或 unknown。
-
-檔案位置:`src/gitea.js` 第 39 行。
-
-```js
-const result = getBotReviewOutcome(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### parseReviewIgnore
-
-把 `.reviewignore` 文字解析成可用的排除前綴陣列。
-
-檔案位置:`src/gitea.js` 第 62 行。
-
-```js
-const result = parseReviewIgnore(text);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getReviewIgnore
-
-讀取並解析 PR 的 `.reviewignore`,沒有自訂規則時回退預設排除清單。
-
-檔案位置:`src/gitea.js` 第 74 行。
-
-```js
-const result = await getReviewIgnore();
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getPRDiff
-
-取得目前 PR 的 unified diff,並套用 `.reviewignore` 與內建過濾規則。
-
-檔案位置:`src/gitea.js` 第 89 行。
-
-```js
-const result = await getPRDiff();
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getCommitMessageBySha
-
-依 commit SHA 讀取 Gitea 上的 commit message,失敗時保守回空字串。
-
-檔案位置:`src/gitea.js` 第 101 行。
-
-```js
-const result = await getCommitMessageBySha(sha);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getBranchHeadCommitMessage
-
-讀取指定分支 head commit 的訊息,失敗時保守回空字串。
-
-檔案位置:`src/gitea.js` 第 122 行。
-
-```js
-const result = await getBranchHeadCommitMessage(branch);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### shouldSkipBotCommit
-
-判斷目前 PR head 是否為 bot 自動提交,若是就跳過後續審查流程。
-
-檔案位置:`src/gitea.js` 第 147 行。
-
-```js
-const result = await shouldSkipBotCommit(sha, branch);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### filterDiff
-
-過濾 unified diff 中不需要審查的路徑區塊,保留其餘內容。
-
-檔案位置:`src/gitea.js` 第 164 行。
-
-```js
-const result = filterDiff(diff, excludePrefixes);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### postComment
-
-在 PR 底下發布一則 Markdown 留言,適合非行內評論用途。
-
-檔案位置:`src/gitea.js` 第 184 行。
-
-```js
-const result = await postComment(body);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### postPullReviewComment
-
-對 PR 指定檔案與行號發送單筆行內 review comment。
-
-檔案位置:`src/gitea.js` 第 203 行。
-
-```js
-const result = await postPullReviewComment(pathfilePath, line, body);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### postPullReview
-
-建立一則包含摘要與多筆行內 comment 的 PR review。
-
-檔案位置:`src/gitea.js` 第 226 行。
-
-```js
-const result = await postPullReview(body, comments);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### listPullReviews
-
-列出目前 PR 的所有 review,回應格式不正確時保守回空陣列。
-
-檔案位置:`src/gitea.js` 第 246 行。
-
-```js
-const result = await listPullReviews();
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### getPullReviewComments
-
-依 review ID 取得該 review 底下的所有行內 comment。
-
-檔案位置:`src/gitea.js` 第 260 行。
-
-```js
-const result = await getPullReviewComments(reviewId);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### listAllReviewComments
-
-彙整目前 PR 的所有 review comments,單筆失敗時略過並持續處理。
-
-檔案位置:`src/gitea.js` 第 274 行。
-
-```js
-const result = await listAllReviewComments();
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### resolvePullReviewComment
-
-解決指定 review comment 對話,對應 Gitea 的 resolve API。
-
-檔案位置:`src/gitea.js` 第 296 行。
-
-```js
-const result = await resolvePullReviewComment(commentId);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getFileContentAtRef
-
-讀取指定 ref 下的檔案文字內容,支援 base64 解碼並在失敗時保守回空字串。
-
-檔案位置:`src/gitea.js` 第 313 行。
-
-```js
-const result = await getFileContentAtRef(filePath, ref);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### stripCodeFence
-
-移除文字外層的 markdown code fence,並清理前後空白。
-
-檔案位置:`src/json.js` 第 17 行。
-
-```js
-const result = stripCodeFence(text);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### repairJSONArrayWithAI
-
-透過 LLM 將原始內容修復成可直接 JSON.parse 的 JSON 陣列字串。
-
-檔案位置:`src/json.js` 第 43 行。
-
-```js
-const result = await repairJSONArrayWithAI(fullPath, label, rawText, chatFn);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### validateJSONArrayFile
-
-驗證 JSON 檔案是否合法,必要時嘗試透過 AI 修復一次。
-
-檔案位置:`src/json.js` 第 93 行。
-
-```js
-const result = await validateJSONArrayFile(fullPath, label, repairer);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳驗證成功/失敗的結構化結果。
-
-### ensureJSONArrayFileExists
-
-確保指定路徑存在一個 JSON 檔案,不存在時建立空陣列檔。
-
-檔案位置:`src/json.js` 第 134 行。
-
-```js
-const result = ensureJSONArrayFileExists(fullPath, label);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### mapWithConcurrency
-
-以可控制併發數的方式並行處理陣列項目。
-
-檔案位置:`src/llm.js` 第 25 行。
-
-```js
-const result = await mapWithConcurrency(items, limit, fn);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### extractMeaningfulError
-
-從 CLI 原始輸出中擷取最有用的錯誤訊息。
-
-檔案位置:`src/llm.js` 第 96 行。
-
-```js
-const result = extractMeaningfulError(raw, limit);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### chat
-
-呼叫 CLIProxyAPI,並回傳文字回應。
-
-檔案位置:`src/llm.js` 第 190 行。
-
-```js
-const result = await chat(systemPrompt, userContent);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### chatJSON
-
-呼叫 AI 助理並把回應解析成 JSON。
-
-檔案位置:`src/llm.js` 第 218 行。
-
-```js
-const result = await chatJSON(systemPrompt, userContent);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### extractBalancedJSON
-
-從指定索引開始擷取完整平衡的 JSON 片段。
-
-檔案位置:`src/llm.js` 第 255 行。
-
-```js
-const result = extractBalancedJSON(text, startIndex);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### extractJSONText
-
-從雜訊文字中抽出最可能的 JSON 內容。
-
-檔案位置:`src/llm.js` 第 298 行。
-
-```js
-const result = extractJSONText(text);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### section
-
-輸出最上層區塊標題,用來切分整體執行流程。
-
-檔案位置:`src/log.js` 第 9 行。
-
-```js
-const result = section(title);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### step
-
-輸出流程中的步驟標題,標示某一小段工作內容。
-
-檔案位置:`src/log.js` 第 22 行。
-
-```js
-const result = step(stepName, title);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### line
-
-輸出一行中性的明細資訊。
-
-檔案位置:`src/log.js` 第 34 行。
-
-```js
-const result = line(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### input
-
-輸出某一步驟的輸入描述,方便追蹤資料來源。
-
-檔案位置:`src/log.js` 第 45 行。
-
-```js
-const result = input(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### output
-
-輸出某一步驟的產出描述,方便追蹤結果。
-
-檔案位置:`src/log.js` 第 56 行。
-
-```js
-const result = output(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### result
-
-依布林值輸出成功或失敗的檢查結果。
-
-檔案位置:`src/log.js` 第 69 行。
-
-```js
-const result = result(passed, message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### ok
-
-輸出一筆成功或完成訊息。
-
-檔案位置:`src/log.js` 第 81 行。
-
-```js
-const result = ok(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### warn
-
-輸出一筆警告訊息到 stderr。
-
-檔案位置:`src/log.js` 第 93 行。
-
-```js
-const result = warn(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### error
-
-輸出一筆錯誤訊息到 stderr。
-
-檔案位置:`src/log.js` 第 105 行。
-
-```js
-const result = error(message);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### main
-
-執行 AI Code Review Pipeline 的完整流程。
-
-檔案位置:`src/main.js` 第 55 行。
-
-```js
-const result = await main();
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### checkRequiredEnv
-
-檢查前置驗證所需的必要環境值是否齊全。
-
-檔案位置:`src/preflight.js` 第 60 行。
-
-```js
-const result = checkRequiredEnv(token, repo, pr);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### verifyGiteaToken
-
-驗證 Gitea token 是否可讀取指定 repository。
-
-檔案位置:`src/preflight.js` 第 77 行。
-
-```js
-const result = await verifyGiteaToken(token, repo);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳驗證成功/失敗的結構化結果。
-
-### verifyCommentToken
-
-驗證 comment token 是否可用;未提供時回傳 skipped。
-
-檔案位置:`src/preflight.js` 第 94 行。
-
-```js
-const result = await verifyCommentToken(token);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳驗證成功/失敗的結構化結果。
-
-### fetchLLMModels
-
-呼叫 CLIProxyAPI 的模型清單端點並取得目前可用的模型 id 清單。
-
-檔案位置:`src/preflight.js` 第 118 行。
-
-```js
-const result = await fetchLLMModels();
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### verifyLLM
-
-驗證目前環境是否有可用的 CLIProxyAPI 設定與對應模型。
-
-檔案位置:`src/preflight.js` 第 171 行。
-
-```js
-const result = await verifyLLM(fetchLLMModelsFn);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:回傳驗證成功/失敗的結構化結果。
-
-### runPreflight
-
-執行所有前置驗證流程,任一失敗即回傳 false。
-
-檔案位置:`src/preflight.js` 第 203 行。
-
-```js
-const result = await runPreflight(workspace, deps);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### parseBotReviewComment
-
-解析 bot 產生的 review comment,還原成 finding 欄位物件。
-
-檔案位置:`src/resolve.js` 第 51 行。
-
-```js
-const result = parseBotReviewComment(body);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### groupConversations
-
-依檔案路徑與行號把 review comments 收斂成對話群組。
-
-檔案位置:`src/resolve.js` 第 73 行。
-
-```js
-const result = groupConversations(comments);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### codeWindow
-
-擷取目標行附近的程式碼片段,供 AI 判讀。
-
-檔案位置:`src/resolve.js` 第 100 行。
-
-```js
-const result = codeWindow(content, lineNum, radius);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### judgeConversations
-
-處理 judgeConversations 相關邏輯。
-
-檔案位置:`src/resolve.js` 第 126 行。
-
-```js
-const result = await judgeConversations(items, chatFn);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### isSafeRepoPath
-
-檢查路徑是否安全,避免讀取 repo 外檔案。
-
-檔案位置:`src/resolve.js` 第 174 行。
-
-```js
-const result = isSafeRepoPath(p);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### reconcileConversations
-
-收斂 PR review 對話,並依 AI 裁定回填 findings 的去向。
-
-檔案位置:`src/resolve.js` 第 190 行。
-
-```js
-const result = await reconcileConversations(deps);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### dropResolvedFindings
-
-移除已解決對話對應的 findings。
-
-檔案位置:`src/resolve.js` 第 329 行。
-
-```js
-const result = dropResolvedFindings(findings, resolvedFindings);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### addCarriedFindings
-
-將仍成立但缺漏的 findings 補回清單。
-
-檔案位置:`src/resolve.js` 第 338 行。
-
-```js
-const result = addCarriedFindings(findings, carriedFindings);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### parseRoleFile
-
-解析角色 Markdown 檔,取出 frontmatter 與本文。
-
-檔案位置:`src/roles.js` 第 25 行。
-
-```js
-const result = parseRoleFile(content);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### loadRoles
-
-只載入攻擊方角色。
-
-檔案位置:`src/roles.js` 第 71 行。
-
-```js
-const result = loadRoles();
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### loadRole
-
-依名稱查找單一角色。
-
-檔案位置:`src/roles.js` 第 85 行。
-
-```js
-const result = loadRole(name);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### buildAnalysisPrompt
-
-產生攻擊方角色的程式碼審查 system prompt。
-
-檔案位置:`src/roles.js` 第 104 行。
-
-```js
-const result = buildAnalysisPrompt(role);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### buildLocateLinePrompt
-
-產生用來補 finding 行號的 prompt。
-
-檔案位置:`src/roles.js` 第 149 行。
-
-```js
-const result = buildLocateLinePrompt(role);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### buildVerdictPrompt
-
-產生單條 finding 的誤報裁決 prompt。
-
-檔案位置:`src/roles.js` 第 173 行。
-
-```js
-const result = buildVerdictPrompt(role, exclusionHint);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### getRoleIntro
-
-產生 AI Code Review 團隊的 Markdown 介紹表。
-
-檔案位置:`src/roles.js` 第 206 行。
-
-```js
-const result = getRoleIntro(roles);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### extractUsage
-
-從 LLM 回應抽出 token usage。
-
-檔案位置:`src/usage.js` 第 24 行。
-
-```js
-const result = extractUsage(data);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### recordUsage
-
-記錄一次 LLM 呼叫使用量。
-
-檔案位置:`src/usage.js` 第 65 行。
-
-```js
-const result = recordUsage(data);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### getRunUsage
-
-取得目前累積的使用量。
-
-檔案位置:`src/usage.js` 第 77 行。
-
-```js
-const result = getRunUsage();
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### resetRunUsage
-
-重置執行中的 usage 累計。
-
-檔案位置:`src/usage.js` 第 82 行。
-
-```js
-const result = resetRunUsage();
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### recordRateLimit
-
-記錄最近一次速率配額資訊。
-
-檔案位置:`src/usage.js` 第 109 行。
-
-```js
-const result = recordRateLimit(headers);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:完成對外部系統的寫入、發佈或執行動作。
-
-### getRateLimit
-
-取得最近一次 rate limit 快照。
-
-檔案位置:`src/usage.js` 第 130 行。
-
-```js
-const result = getRateLimit();
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### resetRateLimit
-
-重置 rate limit 快照。
-
-檔案位置:`src/usage.js` 第 135 行。
-
-```js
-const result = resetRateLimit();
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### fetchAccountQuota
-
-查詢指定平台的帳號額度。
-
-檔案位置:`src/usage.js` 第 209 行。
-
-```js
-const result = await fetchAccountQuota(provider, config, deps);
-```
-
-使用情境:通常在需要等待外部 I/O 或其他非同步回應時呼叫。
-
-預期結果:依函式用途回傳對應結果。
-
-### resolveRemainingPercent
-
-計算可用額度剩餘百分比。
-
-檔案位置:`src/usage.js` 第 278 行。
-
-```js
-const result = resolveRemainingPercent(quota, rate);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### formatUsageStats
-
-產生 AI 助理使用量 Markdown 區塊。
-
-檔案位置:`src/usage.js` 第 315 行。
-
-```js
-const result = formatUsageStats(provider, model, usage, quota, rate);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
-
-### formatUsageStatsLine
-
-產生使用量單行摘要。
-
-檔案位置:`src/usage.js` 第 334 行。
-
-```js
-const result = formatUsageStatsLine(provider, model, usage, quota, rate);
-```
-
-使用情境:通常在本地資料處理或同步查詢時呼叫。
-
-預期結果:回傳對應資料、設定、字串或布林值。
diff --git a/action.yml b/action.yml
index ad77435..367c9c0 100644
--- a/action.yml
+++ b/action.yml
@@ -8,6 +8,9 @@ inputs:
comment_token:
description: '操作 Gitea Commit API 的 Token'
required: false
+ model:
+ description: '使用的 AI 模型,僅允許英數字、點、底線、連字號與斜線'
+ required: false
runs:
using: 'docker'
image: 'Dockerfile'
@@ -15,4 +18,5 @@ runs:
GITEA_TOKEN: ${{ inputs.token || secrets.TOKEN || gitea.token }}
GITEA_COMMENT_TOKEN: ${{ inputs.comment_token || inputs.token || secrets.TOKEN || gitea.token }}
CLI_PROXY_API: ${{ vars.CLI_PROXY_API }}
- CLI_PROXY_API_KEY: ${{ secrets.CLI_PROXY_API_KEY }}
\ No newline at end of file
+ CLI_PROXY_API_KEY: ${{ secrets.CLI_PROXY_API_KEY }}
+ CLI_PROXY_API_MODEL: ${{ inputs.model || vars.CLI_PROXY_API_MODEL }}
diff --git a/entrypoint.sh b/entrypoint.sh
index 6ce4408..7fd80d0 100755
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -1,4 +1,8 @@
#!/bin/sh
+# Docker 容器 action 的進入點腳本,於容器啟動時執行 Node 主程式並轉傳所有參數。
+
+# 遇到任何指令執行失敗時立即中止腳本,避免錯誤被吞掉而繼續往下執行
set -e
+# 以 exec 取代目前 shell 程序執行 Node 主程式,並將容器收到的所有參數("$@")原樣轉傳給它
exec node /action/src/main.js "$@"
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..0c85410
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,1703 @@
+# AI Code Review
+
+更新時間:2026/08/07 13:51:53
+
+## 專案列表
+
+| 專案名稱 | 專案描述 |
+| --- | --- |
+| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src) | Gitea Docker 容器 action:對 PR 的 diff 派多個角色進行 AI 程式碼審查,產生 findings 並依對話收斂、排除規則與 AI 誤報裁決收斂結果;負責 Gitea PR API(diff/comment/review/resolve)串接、CLIProxyAPI 對話與 usage/額度統計、git clone/commit/push 持久化 findings,以及執行前的 token/LLM/git 遠端前置驗證。 |
+
+| 專案名稱 | 參考專案列表 |
+| --- | --- |
+| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src) | 無 |
+
+| 專案名稱 | npm 套件列表 |
+| --- | --- |
+| [AI Code Review](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src) | axios ^1.6.7
js-yaml ^4.1.0 |
+
+## 功能列表
+
+### AI Code Review
+
+| 功能名稱 | 功能描述 |
+| --- | --- |
+| [parseLocation](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L77) | [解析 finding 的 location 字串,取出檔案路徑與起始行號,供行內 comment 定位使用。](#parselocation) |
+| [formatFindingsStats](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L173) | [產生新舊問題依嚴重等級(嚴重/警告/建議/無法標示)分類統計的 Markdown 表格。](#formatfindingsstats) |
+| [formatFindingsStatsLine](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L194) | [產生與統計表相同內容的單行文字摘要,供 log 輸出使用。](#formatfindingsstatsline) |
+| [postFindingsReview](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L261) | [發布整批 findings 的 Gitea review(摘要+行內 comment),並提供多層降級機制。](#postfindingsreview) |
+| [saveFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L311) | [將 findings 包成新版 wrapper 後寫入 workspace(及可選的鏡像目錄)。](#savefindings) |
+| [postOldFindingsComment](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L335) | [發布所有舊有未解決問題的彙總 comment。](#postoldfindingscomment) |
+| [postNewNonCriticalComment](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L359) | [發布新問題中非 critical 等級者的彙總 comment。](#postnewnoncriticalcomment) |
+| [postNewCriticalComments](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/comments.js#L388) | [針對每個新的 critical 問題逐筆發布行內 comment,無法定位或失敗時降級為一般 comment。](#postnewcriticalcomments) |
+| [getInsecureHttpsAgent](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/config.js#L57) | [取得關閉 TLS 憑證驗證的 HTTPS Agent 單例,供連接自簽憑證的內部服務使用。](#getinsecurehttpsagent) |
+| [getLLMConfig](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/config.js#L77) | [依環境變數解析目前可用的 CLIProxyAPI 設定(base URL、model、API key)。](#getllmconfig) |
+| [analyzeWithRole](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L20) | [用指定角色分析 diff,呼叫 LLM 產生該角色視角下的 findings 陣列。](#analyzewithrole) |
+| [normalizeText](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L127) | [將文字正規化(NFKC、轉小寫、壓縮空白)為比對用形式,並以快取加速重複呼叫。](#normalizetext) |
+| [loadOldFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L314) | [讀取來源分支的舊 findings 檔案,標記為非新問題並記錄診斷日誌。](#loadoldfindings) |
+| [mergeFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L337) | [依 role/location/suggestion 組成的 key 合併新舊 findings 並去重。](#mergefindings) |
+| [sortByLevel](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L358) | [依 critical/warning/info 順序排序 findings。](#sortbylevel) |
+| [resolveMissingLineNumbers](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L425) | [對只有檔名缺行號的 findings,反問原角色依 diff 補上行號。](#resolvemissinglinenumbers) |
+| [deduplicateWithAI](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L478) | [呼叫 LLM 對 findings 做語意去重,合併同位置同問題本質的重複項。](#deduplicatewithai) |
+| [loadExclusions](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L513) | [讀取並正規化 exclusions 檔案,相容多種舊格式並就地修正為標準陣列。](#loadexclusions) |
+| [appendExclusions](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L570) | [將新的排除條目去重後追加寫入 exclusions.json。](#appendexclusions) |
+| [applyExclusions](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L625) | [依 exclusions 規則過濾 findings,移除符合排除條件的問題。](#applyexclusions) |
+| [filterFalsePositivesWithAI](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/findings.js#L671) | [由防守方角色逐條裁決 findings 是否為誤報並剔除。](#filterfalsepositiveswithai) |
+| [getBotReviewOutcome](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L39) | [解析文字中的 `[ai-review-bot]` 標記,回傳 success/failure/unknown。](#getbotreviewoutcome) |
+| [parseReviewIgnore](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L62) | [把 `.reviewignore` 文字解析成排除前綴陣列。](#parsereviewignore) |
+| [getReviewIgnore](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L75) | [讀取並解析 PR 的 `.reviewignore`,沒有規則時退回內建預設清單。](#getreviewignore) |
+| [getPRDiff](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L90) | [取得目前 PR 的 diff,並套用 `.reviewignore` 與內建過濾規則。](#getprdiff) |
+| [getCommitMessageBySha](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L102) | [依 commit SHA 向 Gitea 查詢該 commit 的訊息。](#getcommitmessagebysha) |
+| [getBranchHeadCommitMessage](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L123) | [讀取指定分支 head commit 的訊息。](#getbranchheadcommitmessage) |
+| [shouldSkipBotCommit](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L148) | [判斷目前 PR head 是否為 bot 自動提交,決定是否跳過審查。](#shouldskipbotcommit) |
+| [filterDiff](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L166) | [過濾 unified diff 中不需要審查的路徑區塊。](#filterdiff) |
+| [postComment](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L186) | [在 PR 下發布一則一般 Markdown 留言。](#postcomment) |
+| [postPullReviewComment](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L205) | [對 PR 指定檔案行號發送單筆行內 review comment。](#postpullreviewcomment) |
+| [postPullReview](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L228) | [建立包含摘要與多筆行內 comment 的 PR review。](#postpullreview) |
+| [listPullReviews](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L248) | [列出目前 PR 的所有 review。](#listpullreviews) |
+| [getPullReviewComments](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L262) | [依 review ID 取得該 review 底下的所有行內 comment。](#getpullreviewcomments) |
+| [listAllReviewComments](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L276) | [彙整目前 PR 所有 review 的行內 comments 成單一陣列。](#listallreviewcomments) |
+| [resolvePullReviewComment](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L298) | [解決指定 review comment 所屬的對話。](#resolvepullreviewcomment) |
+| [getFileContentAtRef](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/gitea.js#L315) | [讀取指定 ref 下檔案的文字內容(自動 base64 解碼)。](#getfilecontentatref) |
+| [getRepoState](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/git.js#L120) | [讀取指定 git repo 目錄的 HEAD SHA、分支與 commit 時間等狀態快照。](#getrepostate) |
+| [getHeadCommitMessage](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/git.js#L138) | [讀取 HEAD commit 的完整 commit message。](#getheadcommitmessage) |
+| [isBotAutoCommit](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/git.js#L153) | [判斷 HEAD commit 是否為 AI Review bot 自動產生的 commit。](#isbotautocommit) |
+| [verifyRemoteAccess](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/git.js#L171) | [用 `git ls-remote` 驗證 remote 認證與連線是否可用。](#verifyremoteaccess) |
+| [cloneRepo](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/git.js#L197) | [以可重入方式將 PR head branch clone/fetch 到工作目錄。](#clonerepo) |
+| [commitAndPush](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/git.js#L241) | [將 findings/exclusions 結轉到 repo 並 commit、push 回 PR head branch。](#commitandpush) |
+| [stripCodeFence](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/json.js#L17) | [移除文字外層的 markdown code fence 並清理前後空白。](#stripcodefence) |
+| [repairJSONArrayWithAI](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/json.js#L43) | [透過 LLM 將原始內容修復成可直接 JSON.parse 的 JSON 陣列字串。](#repairjsonarraywithai) |
+| [validateJSONArrayFile](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/json.js#L93) | [驗證 JSON 檔案是否合法,格式錯誤時嘗試以 AI 修復一次。](#validatejsonarrayfile) |
+| [ensureJSONArrayFileExists](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/json.js#L137) | [確保指定路徑存在 JSON 檔案,不存在時建立空陣列或 findings wrapper。](#ensurejsonarrayfileexists) |
+| [mapWithConcurrency](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/llm.js#L26) | [以可控併發數並行處理陣列項目並保序回傳結果。](#mapwithconcurrency) |
+| [extractMeaningfulError](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/llm.js#L102) | [從 CLI/HTTP 原始輸出中擷取最有用的錯誤訊息片段。](#extractmeaningfulerror) |
+| [chat](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/llm.js#L209) | [呼叫 CLIProxyAPI 送出對話請求並回傳純文字回應。](#chat) |
+| [chatJSON](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/llm.js#L248) | [呼叫 chat 取得回應後,將文字解析為 JSON。](#chatjson) |
+| [extractBalancedJSON](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/llm.js#L285) | [從指定索引以括號平衡方式擷取完整的 JSON 子字串。](#extractbalancedjson) |
+| [extractJSONText](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/llm.js#L328) | [從雜訊文字中盡力抽出可被 JSON.parse 解析的片段。](#extractjsontext) |
+| [section](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L32) | [輸出最上層的區塊分隔標題,切分整體執行流程。](#section) |
+| [step](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L46) | [輸出流程中某個步驟的標題。](#step) |
+| [line](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L59) | [輸出一行縮排的中性明細資訊。](#line) |
+| [input](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L71) | [輸出「階段輸入」描述,標示目前步驟吃進了什麼資料。](#input) |
+| [output](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L83) | [輸出「階段輸出」描述,標示目前步驟產出了什麼結果。](#output) |
+| [result](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L97) | [依布林結果輸出成功或失敗的檢查/把關結果列。](#result) |
+| [ok](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L110) | [輸出一筆成功/完成訊息。](#ok) |
+| [warn](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L123) | [輸出一筆警告訊息(寫入 stderr)。](#warn) |
+| [error](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/log.js#L136) | [輸出一筆錯誤訊息(寫入 stderr)。](#error) |
+| [main](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/main.js#L60) | [AI Code Review Pipeline 的總指揮,依序執行 Step1~Step11 並依結果決定 exit code。](#main) |
+| [checkRequiredEnv](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/preflight.js#L57) | [檢查 code review 所需的必要環境變數是否齊全。](#checkrequiredenv) |
+| [verifyGiteaToken](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/preflight.js#L75) | [驗證 Gitea token 有效且對指定 repo 有讀取權限。](#verifygiteatoken) |
+| [verifyCommentToken](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/preflight.js#L92) | [驗證選用的 comment token(GITEA_COMMENT_TOKEN)是否可用。](#verifycommenttoken) |
+| [fetchLLMModels](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/preflight.js#L133) | [呼叫 CLIProxyAPI 的 `/v1/models`,確認 proxy 可用與模型清單可讀。](#fetchllmmodels) |
+| [verifyLLM](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/preflight.js#L182) | [驗證 LLM proxy 設定可用,且設定的模型在可用清單內。](#verifyllm) |
+| [runPreflight](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/preflight.js#L214) | [執行所有前置驗證(環境變數、Gitea token、comment token、git 遠端、LLM proxy)。](#runpreflight) |
+| [parseBotReviewComment](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L55) | [嘗試把一則 review comment 內文解析回 bot 產生的 finding 欄位。](#parsebotreviewcomment) |
+| [groupConversations](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L84) | [把 PR 上的行內 review comment 依檔案路徑+行號收斂成對話。](#groupconversations) |
+| [codeWindow](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L119) | [取目標行附近的程式碼片段,供 AI 對照判斷問題是否已解決。](#codewindow) |
+| [judgeConversations](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L149) | [批次請 AI 將每個對話判為 resolved / false_positive / open。](#judgeconversations) |
+| [isSafeRepoPath](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L202) | [安全守衛:判定路徑是否為 repo 內的相對路徑,拒絕路徑穿越。](#issaferepopath) |
+| [reconcileConversations](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L223) | [對話收斂主流程:關閉未解決 comment,並依 AI 判斷把 findings 分流為已修復/誤報/仍成立。](#reconcileconversations) |
+| [dropResolvedFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L371) | [從 findings 中移除已判定為「已解決對話」對應的問題。](#dropresolvedfindings) |
+| [addCarriedFindings](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/resolve.js#L384) | [把仍成立但目前 findings 清單中遺漏的問題加回。](#addcarriedfindings) |
+| [parseRoleFile](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L25) | [解析角色 Markdown 檔內容,拆出 frontmatter 與本文。](#parserolefile) |
+| [loadRoles](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L71) | [載入所有「攻擊方」角色定義(`side === 'attack'`)。](#loadroles) |
+| [loadRole](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L85) | [依名稱(不分大小寫)取得單一角色定義。](#loadrole) |
+| [buildAnalysisPrompt](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L104) | [由攻擊方角色定義組出分析 diff 用的 system prompt。](#buildanalysisprompt) |
+| [buildLocateLinePrompt](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L149) | [組出「補行號」用的 system prompt。](#buildlocatelineprompt) |
+| [buildVerdictPrompt](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L173) | [由防守方角色定義組出單條 finding 誤報裁決用的 system prompt。](#buildverdictprompt) |
+| [getRoleIntro](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/roles.js#L206) | [由角色陣列產生「AI Code Review 團隊」介紹用的 Markdown 表格。](#getroleintro) |
+| [extractUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L29) | [把各平台回應中的 token usage 正規化成統一格式。](#extractusage) |
+| [recordUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L75) | [記錄一次 LLM 呼叫的 usage 並累加進模組層級統計。](#recordusage) |
+| [getRunUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L91) | [取得本次執行至今的 token 累計(複本)。](#getrunusage) |
+| [resetRunUsage](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L99) | [重置本次執行的 token 累計(測試用)。](#resetrunusage) |
+| [recordRateLimit](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L128) | [從回應 header 擷取速率配額剩餘量/上限並記錄。](#recordratelimit) |
+| [getRateLimit](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L153) | [取得最近一次的速率配額快照(複本)。](#getratelimit) |
+| [resetRateLimit](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L161) | [重置速率配額快照(測試用)。](#resetratelimit) |
+| [fetchAccountQuota](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L245) | [取得指定平台的帳號額度資訊,任何失敗都降級回報無法取得。](#fetchaccountquota) |
+| [resolveRemainingPercent](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L320) | [依優先序(帳號額度→速率配額)計算「剩餘可用百分比」。](#resolveremainingpercent) |
+| [formatUsageStats](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L366) | [產生 PR Review 本文用的「AI 助理使用量」Markdown 區塊。](#formatusagestats) |
+| [formatUsageStatsLine](https://gitea.jsc.idv.tw/actions/ai-code-review/src/branch/develop/src/usage.js#L396) | [產生單行 log 用的使用量摘要文字。](#formatusagestatsline) |
+
+## 使用範例
+
+
+
+### parseLocation
+
+解析 finding 的 `location` 欄位,取出檔案路徑與(起始)行號,供行內 comment 標註使用。支援 `"file:19"`(單行)與 `"file:70-82"`(範圍,僅取起始行);若 `location` 非字串、包含逗號(代表對應多個檔案),或無法比對出行號格式,一律回傳 `null`,呼叫端應據此降級為一般(非行內)comment。
+
+- 參數:`location`(`string`)- finding 的位置字串。
+- 回傳:`{ file: string, line: number } | null`。
+
+```javascript
+import { parseLocation } from './src/comments.js';
+
+parseLocation('src/config.js:57');
+// => { file: 'src/config.js', line: 57 }
+
+parseLocation('src/config.js:70-82');
+// => { file: 'src/config.js', line: 70 }(範圍格式僅取起始行)
+
+parseLocation('a.js:1,b.js:2');
+// => null(多檔案不支援)
+```
+
+
+
+### formatFindingsStats
+
+產生 findings 統計的 Markdown 表格:以 `is_new === false` 判定為舊問題、其餘為新問題,分別統計嚴重(critical)/警告(warning)/建議(info)/無法標示(level 不在三者之內)四欄的筆數,輸出含表頭、分隔列與兩筆資料列的表格字串。空陣列時仍會輸出表格(各欄為 0 筆)。
+
+- 參數:`findings`(`Array