[
  {
    "level": "critical",
    "role": "Assassin",
    "location": "src/config.js:7",
    "problem": "這裡把 `NODE_TLS_REJECT_UNAUTHORIZED` 全域設為 `0`，等於讓整個 Node 程序放棄 TLS 憑證驗證。攻擊者只要能站到 runner 與 Gitea／LLM／任何 HTTPS API 之間，就能用偽造憑證攔截或竄改 diff、review 結果、token 驗證流程，甚至偷走 Authorization header。",
    "suggestion": "移除全域停用 TLS 的設定。若內部自簽 CA 是必要情境，請改用可設定的 CA bundle（例如 `NODE_EXTRA_CA_CERTS`）或僅對明確允許的內部 host 使用專用 agent，且預設必須啟用憑證驗證。",
    "is_new": true
  },
  {
    "level": "critical",
    "role": "Assassin",
    "location": "src/config.js:53",
    "problem": "這個 helper 直接建立 `rejectUnauthorized: false` 的 HTTPS agent，後續 Gitea API 與 preflight 都會用它。攻擊者若能進行中間人攻擊，就能假冒 Gitea 回傳惡意 diff、偽造 comment/review API 回應，或攔截寫入用 token。",
    "suggestion": "不要提供預設不驗證憑證的 agent。改成預設安全驗證；若真的要支援自簽憑證，請要求使用者明確提供信任的 CA 憑證，或以白名單 host 加上明確 opt-in 的設定限制風險。",
    "is_new": true
  },
  {
    "level": "critical",
    "role": "Assassin",
    "location": "src/resolve.js:253",
    "problem": "這裡會把 PR 上所有未解決的 review comment ID 全部送去 resolve，而不是只處理 AI Review bot 自己建立的 thread。攻擊者只要開 PR 觸發這個 action，就可能讓 bot 關閉人類審查者留下的安全疑慮或阻擋性對話，繞過人工審查流程。",
    "suggestion": "只 resolve 可證明由本 bot 建立且格式符合預期的 comment，例如檢查作者、固定 marker、review body 簽章或 botFinding 解析結果；人類留言與未知格式留言不得自動關閉。",
    "is_new": true
  },
  {
    "level": "critical",
    "role": "Mage",
    "location": "src/findings.js:471",
    "problem": "當排除條目有 location 或 role 時，這裡直接把文字比對結果短路成 true。最小重現：exclusions.json 只有 `{ \"location\": \"app/a.js:10\", \"original_finding\": \"誤報 A\" }`，新的 finding 是 `app/a.js:99` 且 suggestion 完全不同，仍會因同檔案而被排除，導致真問題被靜默丟掉。",
    "suggestion": "不要用 `exPath || ex.role ? true : textMatches` 跳過文字比對；應至少要求位置精確匹配到同一行，或在同檔/同角色時仍必須通過 `textMatches`，例如 `return locationMatches && roleMatches && textMatches`，並明確定義 suggestion 空白時才是萬用規則。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Assassin",
    "location": "src/findings.js:14",
    "problem": "這裡把未信任的 Git diff 直接送進 LLM。攻擊者可以在新增程式碼或註解中塞入提示詞注入內容，例如要求模型忽略安全問題、回傳空陣列或偽造低風險 findings，藉此讓自動安全審查失明。",
    "suggestion": "在分析 prompt 中明確標示 diff 是不可信資料，要求模型忽略 diff 內任何指令；同時加入結構化封裝、輸出 schema 驗證與必要的規則式安全檢查，避免完全依賴可被 prompt injection 操控的 LLM 判斷。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Bard",
    "location": "src/main.js:2",
    "problem": "這一行 import 把大量設定常數擠成長長一串，讀起來像沒有換氣的樂句，與後續同檔案多個長 import 一起讓檔案開頭難以掃描。",
    "suggestion": "將多項具名 import 改成多行排列，並依來源模組分組維持一致節奏，例如每個匯入項目獨立一行。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Bard",
    "location": "src/findings.js:4",
    "problem": "這行把四個 prompt/role helper 壓在同一行，與檔案中龐大的流程函式相比，開頭的依賴清單先失了拍，降低可讀性。",
    "suggestion": "改為多行具名 import，讓每個 helper 名稱清楚露出，並與其他長 import 採相同格式。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Bard",
    "location": "src/gitea.js:2",
    "problem": "Gitea 設定匯入一口氣列出八個名稱，行寬過長，讓讀者難以快速分辨這個模組真正依賴哪些環境值。",
    "suggestion": "將具名 import 拆成多行，必要時依 token、repo/PR、TLS helper 等語意排序。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Bard",
    "location": "src/comments.js:11",
    "problem": "大量私有輔助函式都配上篇幅很長的 JSDoc，許多內容只是重述程式碼表面行為，註解的聲量蓋過了旋律本身。",
    "suggestion": "保留公開 API 或非直覺決策的文件即可；私有小函式改用簡短註解，或讓函式命名本身說明用途。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Bard",
    "location": "src/main.js:18",
    "problem": "main() 前的 JSDoc 幾乎把整條 pipeline 逐步重寫一次，和函式內 Step 註解重複，維護時很容易變成兩份會走調的文件。",
    "suggestion": "縮短為高階摘要與退出規則；Step 細節留在程式碼附近，避免文件與實作雙重維護。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Leo",
    "location": "src/main.js:36",
    "problem": "`main()` 把前置驗證、bot commit 判斷、對話收斂、角色分析、合併去重、排除、發布、JSON 驗證、commit/push 與 gate 全部塞在同一個 190 行左右的函式裡，且中間散落多個 `process.exit()`。六個月後要改其中任一步驟時，很難隔離副作用，也不容易針對單一階段寫單元測試。",
    "suggestion": "將每個 Step 拆成可注入相依、回傳明確結果的函式，例如 `runAnalysisStep()`、`runFilteringStep()`、`runPublishStep()`；最外層再統一把結果轉成 exit code，讓流程控制與業務邏輯分離。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Leo",
    "location": "src/findings.js:381",
    "problem": "`loadExclusions()` 同時負責讀檔、解析多種格式、正規化、去重、記錄 repo 狀態、改寫原檔、鏡像寫入與建立 AI prompt 摘要。這個函式的職責過多，之後只要調整 exclusions 格式或同步策略，就很容易牽動不相關行為。",
    "suggestion": "拆成 `readExclusionsFile()`、`normalizeExclusionsData()`、`canonicalizeExclusionsFile()`、`logExclusionMetadata()` 等小函式，讓讀取、轉換、寫回與診斷各自可測。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Mage",
    "location": "src/findings.js:466",
    "problem": "這裡優先使用 `ex.textKey`，但 `textKey` 是由 `toKeyText()` 產生的無分隔且未轉小寫文字，而 findingText 是 `normalizeText()` 產生的小寫、以空白分隔文字。最小重現：排除文字 `Update tests` 會變成 `Updatetests`，finding suggestion 會變成 `update tests`，兩邊互相 `includes` 都不成立，導致純文字排除規則失效。",
    "suggestion": "排除條目與 finding 應使用同一套正規化函式比對；例如改存並使用 `normalizeText(ex.text || ex.suggestion || ex.title || '')`，或讓 finding 也轉成同樣的 compact/lowercase key。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Mage",
    "location": "src/main.js:221",
    "problem": "若 Step6 的 `cloneRepo()` 失敗，`repoDir` 會是 undefined，但這裡仍呼叫 `commitAndPush(WORKSPACE, repoDir || WORKSPACE, ...)`。最小重現：遠端 clone 因分支不存在或網路錯誤失敗後，流程降級繼續，最後 Step10 會在 `/workspace` 這個非 git repo 執行 `git config/status/commit`，錯誤只被 `commitAndPush` 吞掉；findings/exclusions 已發布但不會被持久化到 PR 分支，下一輪會遺失記憶狀態。",
    "suggestion": "Step10 應在 `repoDir` 不存在時明確跳過 commit/push 並標記持久化失敗，或讓 clone 失敗成為會終止流程的錯誤；不要把 WORKSPACE 當成 repoDir fallback。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Mage",
    "location": "src/resolve.js:92",
    "problem": "對話分組行號只讀 `position` 或 `original_position`，但新增留言發布時使用的是 `new_position`。若 Gitea 回傳 review comment 只帶 `new_position`，最小重現：同一檔案第 10 行與第 20 行兩則未解決 bot comment 都沒有 `position`，兩者會被合併成 `file|0`，只解析第一個 finding，後續 resolved/open/false_positive 判斷會套錯問題。",
    "suggestion": "分組行號應納入 `new_position`，例如 `Number(c?.position) || Number(c?.new_position) || Number(c?.original_position) || 0`，並針對缺行號的 comment 避免把同檔不同對話合併成同一組。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Mage",
    "location": "src/findings.js:336",
    "problem": "AI 去重回傳只要是非空陣列就被接受，沒有檢查是否比原始 findings 更多。最小重現：原本 3 筆 findings，LLM 異常回傳 20 筆或加入不存在的 location，這裡會直接採用並進入發布與失敗判定，導致憑空產生問題或讓 workflow 誤失敗。",
    "suggestion": "去重結果應驗證每筆都能對應回原始 finding，且數量不得大於輸入；無法對應或數量異常時應降級回原始 findings，或只保留 `origMap` 命中的項目。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Maya",
    "location": "src/main.js:118",
    "problem": "Step5 的角色分析與流程分支是整個 action 的核心，但目前測試沒有覆蓋 main orchestrator：例如所有角色分析都失敗時應 exit 1、部分角色失敗時仍繼續、diff 為空時 exit 0、critical finding 最後應讓 workflow 失敗。這些行為沒有被驗證，等於 pipeline 成敗判斷還沒通過試煉。",
    "suggestion": "補上 main 流程層級測試，透過 mock getPRDiff、loadRoles、analyzeWithRole、postFindingsReview、process.exit 等相依，至少覆蓋：diff 空、全部分析失敗、部分分析失敗但繼續、產生 critical 後 exit 1、無 critical 後正常通過。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Maya",
    "location": "src/main.js:166",
    "problem": "Step7 會把 reconcile.excludedFindings 追加到 exclusions，接著再載入並套用排除規則，但目前缺少整合測試驗證「誤報對話 → 寫入 exclusions → 後續 findings 被排除」這條關鍵路徑。若 append/load/apply 任一環節接錯 workspace 或 mirror，單元測試不一定會抓到。",
    "suggestion": "補一個接近流程層級的測試，mock reconcileConversations 回傳 excludedFindings，準備一筆會被排除的新 finding，驗證 appendExclusions 寫入的檔案被 loadExclusions 讀到，且最後 save/post 的 filtered findings 不含該誤報。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Maya",
    "location": "src/findings.js:416",
    "problem": "applyExclusions 的核心比對支援「只有文字、沒有路徑/角色」的排除規則，但現有測試多半靠相同檔案路徑命中，沒有驗證純文字排除、空文字排除、大小寫/標點差異等邊界。這條排除規則的最脆弱分支還沒被測到。",
    "suggestion": "補上 applyExclusions 的邊界測試：只有 suggestion/title 文字沒有 location 的 exclusion 應如何比對；空文字 exclusion 不應意外排除全部；標點、空白、大小寫正規化後相同的文字應依預期排除。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Maya",
    "location": "src/gitea.js:95",
    "problem": "shouldSkipBotCommit 目前只看到命中 bot marker 的測試，缺少「commit API 失敗、分支查詢失敗、sha/branch 都沒有 marker」時應回 false 的失敗與保守路徑驗證。這是避免 workflow 誤跳過審查的關鍵判斷，不能只測快樂路徑。",
    "suggestion": "新增測試讓 getCommitMessageBySha / getBranchHeadCommitMessage 對應的 axios 呼叫拋錯或回一般 commit message，斷言 shouldSkipBotCommit 回 false，且不會把查詢失敗誤判成 bot commit。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Maya",
    "location": "src/llm.js:91",
    "problem": "runAssistantCLI 有 timeout 與 maxBuffer 兩條重要失敗路徑，但目前測試只覆蓋 CLI 非零退出，沒有驗證逾時會 kill 子程序並拒絕、輸出超過限制會中止且不產生未處理的重複 reject。這些是 CI 上最常見的失敗情境。",
    "suggestion": "新增 llm 測試：用假的 CLI sleep 超過 AI_ASSISTANT_TIMEOUT_MS，斷言錯誤訊息包含逾時；再用大量 stdout/stderr 超過 AI_ASSISTANT_MAX_BUFFER，斷言錯誤訊息正確且測試過程沒有 unhandled rejection。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Rogue",
    "location": "src/main.js:132",
    "problem": "這裡把每個角色的 LLM 分析逐一 await，6 個角色就把總耗時堆成約 6 倍單次模型延遲；這些分析彼此獨立，CPU 沒偷到時間，反而把整條 pipeline 卡在序列網路/CLI 呼叫上。",
    "suggestion": "改用 Promise.allSettled 平行執行 roles.map(role => analyzeWithRole(role, diff))，再彙整 fulfilled 結果與 warning；保留 fulfilledAnalyses 的判斷即可。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Rogue",
    "location": "src/gitea.js:239",
    "problem": "這裡逐一 await 每個 review 的 comments，PR review 一多就變成 N 次遠端呼叫的線性延遲累加；例如 30 個 review 就是 30 個 round-trip 排隊等，時間都被網路空轉偷走。",
    "suggestion": "把 reviews.map(review => getPullReviewComments(review.id).catch(...)) 丟進 Promise.all 或 Promise.allSettled 平行抓取，再 flat 結果；單筆失敗仍可記 warn 後略過。",
    "is_new": true
  },
  {
    "level": "warning",
    "role": "Rogue",
    "location": "src/findings.js:430",
    "problem": "applyExclusions 在 findings × exclusions 的巢狀比對裡，每遇到一條 exclusion 就重算同一個 finding 的 normalizeText；F 筆 finding、E 條 exclusion 會做最多 F×E 次正規化與正則替換，這是很明顯的 CPU 浪費。",
    "suggestion": "先把 findings 預處理成含 fPath、normalizedFindingText 的陣列，exclusions 也先補齊 normalizedExclusionText，再做比對；同一筆文字只正規化一次。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Bard",
    "location": "src/findings.js:138",
    "problem": "註解中留下「不確定」這種未定案語氣，像樂譜上的猶豫記號；讀者無法判斷這是刻意設計、待辦事項，還是審查遺留。",
    "suggestion": "若是刻意差異，改寫成明確理由；若待確認，改成可追蹤的 TODO 並標明決策者或議題。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Bard",
    "location": "src/git.js:226",
    "problem": "_sourceRoot` 的參數文件寫著「不確定，待確認」，讓公開函式簽名帶著未完成的旁白，破壞 API 文件的一致與可信度。",
    "suggestion": "若參數已不使用，移除它；若為相容性保留，明確寫成 deprecated/compatibility note，不要留下模糊語句。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Bard",
    "location": "src/config.js:4",
    "problem": "註解說「需要內部服務相容時才使用 getInsecureHttpsAgent()」，下一行卻在模組載入時全域設定 TLS 環境變數，文件與程式碼唱了不同旋律。",
    "suggestion": "讓註解忠實描述目前行為，或把全域設定移到明確命名的初始化函式；至少避免文件暗示這是選擇性使用。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Leo",
    "location": "src/json.js:13",
    "problem": "`stripCodeFence()` 與 `src/llm.js` 內的 `stripOuterFence()` 幾乎是同一個功能，未來如果要支援更多 fence 格式或修 bug，兩邊需要同步修改，容易產生行為漂移。",
    "suggestion": "抽成共用的 JSON/text utility，例如 `src/text.js` 或 `src/json.js` 匯出單一 fence 清理函式，讓 LLM JSON 解析與 JSON repair 共用同一套邏輯。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Leo",
    "location": "src/findings.js:104",
    "problem": "文字正規化邏輯分散在 `normalizeText()`、`toKeyText()`，而 `src/resolve.js` 也有另一套 `normalizeKey()`。這些函式對大小寫、標點與空白的處理不完全一致，長期會讓 finding 去重、排除與對話收斂出現難追的差異。",
    "suggestion": "建立單一 normalization 模組，明確定義 `normalizeForDisplayMatch`、`normalizeForSignature` 等用途，再讓 findings、resolve、exclusions 共用，並補上跨模組測試鎖定語意。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Leo",
    "location": "src/roles.js:7",
    "problem": "`ROLES_DIR` 用 `fileURLToPath(import.meta.url)` 直接接 `..` 來推目錄，雖然目前可運作，但語意上把檔案路徑當目錄路徑處理，未來搬檔或重構時不直覺。",
    "suggestion": "先用 `path.dirname(fileURLToPath(import.meta.url))` 取得目前模組目錄，再組 `prompts/roles`，讓路徑意圖清楚且不依賴 `..` 抵銷檔名的技巧。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Maya",
    "location": "src/comments.js:25",
    "problem": "Markdown 表格列直接嵌入 role、location、suggestion，但測試沒有覆蓋 suggestion 含 `|`、換行或 Markdown 特殊字元時的輸出。這不是要求現在一定要改格式，而是目前缺少案例確認表格在真實 LLM 輸出下不會被破壞。",
    "suggestion": "補一個 comment body 格式測試，輸入 suggestion 含 pipe、換行與粗體符號，斷言輸出的 Markdown 結構符合預期；若目前行為會破表，應先定義轉義或替換規則再測。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Rogue",
    "location": "src/comments.js:126",
    "problem": "countBy 用 filter(predicate).length 只為了計數卻配置中間陣列；formatFindingsStats/formatFindingsStatsLine 每列又重複掃多次，雖然 findings 通常不大，但這是在白白丟記憶體與掃描週期。",
    "suggestion": "改成單趟 reduce 統計 new/old × level 的計數表，或讓 countBy 用 for-of 累加數字、不建立 filter 結果陣列。",
    "is_new": true
  },
  {
    "level": "info",
    "role": "Rogue",
    "location": "src/findings.js:382",
    "problem": "loadExclusions 前面已經 normalizeExclusionEntry + dedupeExclusions，這裡又呼叫 buildExclusionContext(exclusions) 重新 normalize、dedupe、group 一輪，只為了 log groups 數；排除規則多時會多跑一趟 O(e log e) 的整理成本。",
    "suggestion": "讓 buildExclusionContext 可接受已正規化/已去重的 exclusions，或直接在 loadExclusions 重用現有 exclusions 進行 group 統計，避免重複正規化與排序。",
    "is_new": true
  }
]
