[
  {
    "location": "src/config.js:7",
    "role": "Assassin",
    "original_finding": "這裡把 `NODE_TLS_REJECT_UNAUTHORIZED` 全域設為 `0`，等於讓整個 Node 程序放棄 TLS 憑證驗證。攻擊者只要能站到 runner 與 Gitea／LLM／任何 HTTPS API 之間，就能用偽造憑證攔截或竄改 diff、review 結果、token 驗證流程，甚至偷走 Authorization header。",
    "reason": "內部自架 Gitea／自簽憑證環境的刻意設計（見 config.js 註解）。如需強化可改用 NODE_EXTRA_CA_CERTS，屬人工決策而非誤判。"
  },
  {
    "location": "src/config.js:53",
    "role": "Assassin",
    "original_finding": "這個 helper 直接建立 `rejectUnauthorized: false` 的 HTTPS agent，後續 Gitea API 與 preflight 都會用它。攻擊者若能進行中間人攻擊，就能假冒 Gitea 回傳惡意 diff、偽造 comment/review API 回應，或攔截寫入用 token。",
    "reason": "同 config.js:7，為相容內部自簽憑證的刻意設計；預設不驗證憑證僅限受信任內網使用。"
  },
  {
    "location": "src/main.js:2",
    "role": "Bard",
    "original_finding": "這一行 import 把大量設定常數擠成長長一串，讀起來像沒有換氣的樂句，與後續同檔案多個長 import 一起讓檔案開頭難以掃描。",
    "reason": "多行 import 排版為風格偏好，非缺陷；本專案採現行單行分組匯入風格。"
  },
  {
    "location": "src/findings.js:4",
    "role": "Bard",
    "original_finding": "這行把四個 prompt/role helper 壓在同一行，與檔案中龐大的流程函式相比，開頭的依賴清單先失了拍，降低可讀性。",
    "reason": "多行 import 排版為風格偏好，非缺陷。"
  },
  {
    "location": "src/gitea.js:2",
    "role": "Bard",
    "original_finding": "Gitea 設定匯入一口氣列出八個名稱，行寬過長，讓讀者難以快速分辨這個模組真正依賴哪些環境值。",
    "reason": "多行 import 排版為風格偏好，非缺陷。"
  },
  {
    "location": "src/comments.js:11",
    "role": "Bard",
    "original_finding": "大量私有輔助函式都配上篇幅很長的 JSDoc，許多內容只是重述程式碼表面行為，註解的聲量蓋過了旋律本身。",
    "reason": "詳盡 JSDoc 為本專案 doc-funcs 流程的刻意文件化風格，非過度註解。"
  },
  {
    "location": "src/main.js:18",
    "role": "Bard",
    "original_finding": "main() 前的 JSDoc 幾乎把整條 pipeline 逐步重寫一次，和函式內 Step 註解重複，維護時很容易變成兩份會走調的文件。",
    "reason": "main() 的 pipeline 概述 JSDoc 為刻意文件化風格，與 Step 註解並存屬設計選擇。"
  },
  {
    "location": "src/resolve.js:253",
    "role": "Assassin",
    "original_finding": "這裡會把 PR 上所有未解決的 review comment ID 全部送去 resolve，而不是只處理 AI Review bot 自己建立的 thread。攻擊者只要開 PR 觸發這個 action，就可能讓 bot 關閉人類審查者留下的安全疑慮或阻擋性對話，繞過人工審查流程。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/gitea.js:66",
    "role": "Assassin",
    "original_finding": "這裡直接從 PR head 讀取 `.reviewignore`，再拿它當成排除規則。攻擊者可以在自己的分支塞入排除條目，讓 bot 故意跳過包含惡意變更的檔案或整個目錄，等於自己決定哪些地方不被審查。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:352",
    "role": "Assassin",
    "original_finding": "這裡會直接讀取 PR 工作樹中的 `.gitea/ai-review/exclusions.json` 當成可信排除來源。攻擊者可以先在分支裡放一份藏在 `.gitea/` 下的 exclusions 檔，利用被忽略的路徑把自己的問題先排除掉，讓後續的 findings 被靜默吃掉。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/main.js:158",
    "role": "Assassin",
    "original_finding": "這裡直接載入 PR 工作樹中的 `.gitea/ai-review/exclusions.json` 當成既有排除規則。攻擊者可以先在 PR 內預埋一份排除清單，因為 `.gitea/` 又被預設排除於 diff 之外，這些惡意排除不會被審查到，卻會被流程直接拿來吞掉真正的 findings，形成靜默的審查繞過。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/gitea.js:118",
    "role": "Assassin",
    "original_finding": "這裡只靠 commit 訊息是否包含 `[ai-review-bot]` 來判斷要不要跳過審查，等於把信任建立在可由任何提交者自行偽造的字串上。攻擊者只要把自己的 PR head commit 訊息改成這個標記，整個審查流程就會被直接略過。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/llm.js:21",
    "role": "Assassin",
    "original_finding": "這裡把未清洗的 `userContent` 直接塞進模型提示詞，等於讓 PR 內容、留言內容或其他外部文字能反過來操控 LLM。攻擊者可以在 diff 裡埋入『忽略前述規則、回傳空陣列』這類指令，讓審查模型漏報真正的風險或把嚴重問題降級成誤報。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/main.js:69",
    "role": "Mage",
    "original_finding": "這裡先檢查 head SHA 對應的訊息是否為 failure，但如果 SHA 查詢失敗或是空值，後面的 `shouldSkipBotCommit()` 仍可能只看到分支 head 上的 `[ai-review-bot]` 標記就直接跳過。最小重現：`getCommitMessageBySha()` 因 Gitea API 暫時失敗回空字串，而分支 head 正好是 `[ai-review-bot][failure]`，流程就會 exit 0，等於把本來應該失敗的 bot commit 當成可跳過的自動提交。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/main.js:132",
    "role": "Rogue",
    "original_finding": "這裡把每個角色的 LLM 分析逐一 await，6 個角色就把總耗時堆成約 6 倍單次模型延遲；這些分析彼此獨立，CPU 沒偷到時間，反而把整條 pipeline 卡在序列網路/CLI 呼叫上。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:381",
    "role": "Leo",
    "original_finding": "`loadExclusions()` 同時負責讀檔、解析多種格式、正規化、去重、記錄 repo 狀態、改寫原檔、鏡像寫入與建立 AI prompt 摘要。這個函式的職責過多，之後只要調整 exclusions 格式或同步策略，就很容易牽動不相關行為。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:466",
    "role": "Mage",
    "original_finding": "這裡優先使用 `ex.textKey`，但 `textKey` 是由 `toKeyText()` 產生的無分隔且未轉小寫文字，而 findingText 是 `normalizeText()` 產生的小寫、以空白分隔文字。最小重現：排除文字 `Update tests` 會變成 `Updatetests`，finding suggestion 會變成 `update tests`，兩邊互相 `includes` 都不成立，導致純文字排除規則失效。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:416",
    "role": "Maya",
    "original_finding": "applyExclusions 的核心比對支援「只有文字、沒有路徑/角色」的排除規則，但現有測試多半靠相同檔案路徑命中，沒有驗證純文字排除、空文字排除、大小寫/標點差異等邊界。這條排除規則的最脆弱分支還沒被測到。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/gitea.js:95",
    "role": "Maya",
    "original_finding": "shouldSkipBotCommit 目前只看到命中 bot marker 的測試，缺少「commit API 失敗、分支查詢失敗、sha/branch 都沒有 marker」時應回 false 的失敗與保守路徑驗證。這是避免 workflow 誤跳過審查的關鍵判斷，不能只測快樂路徑。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/llm.js:66",
    "role": "Maya",
    "original_finding": "`runAssistantCLI()` 目前只有成功與一般失敗的測試，沒有覆蓋 timeout、`maxBuffer` 超限、以及 `opencode` 分支建立的暫存 prompt 檔在例外發生時是否確實清理。這些都是外部 CLI 整合最常出問題的失敗路徑，沒有測到就很難確定不會留下殘檔或把流程卡死。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/gitea.js:239",
    "role": "Rogue",
    "original_finding": "這裡逐一 await 每個 review 的 comments，PR review 一多就變成 N 次遠端呼叫的線性延遲累加；例如 30 個 review 就是 30 個 round-trip 排隊等，時間都被網路空轉偷走。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:442",
    "role": "Rogue",
    "original_finding": "這裡每一筆 finding 都要跟整包 exclusions 做一次 `.some()`，而且內層還反覆跑 `normalizeText` 和字串包含比對，資料一多就直接變成 O(F×E) 的熱點。像 300 筆 finding 配 500 筆 exclusions，會吃掉 15 萬次以上的比對與正規化，CPU 和字串配置都在浪費。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:213",
    "role": "Mage",
    "original_finding": "`findingSig` 只用檔案路徑加上 `suggestion` 來識別問題，忽略了 `problem`、`role`，也沒有留下任何穩定的 thread 識別；最小重現：同一個 `a.js` 內有兩條都建議「加上 null 檢查」但其實是不同位置的 finding，先解掉其中一條後，另一條也會被當成同一筆而被 `dropResolvedFindings` / `addCarriedFindings` 誤合併或誤刪。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:40",
    "role": "Mage",
    "original_finding": "這裡把 `file:0` 也視為有效行號；最小重現：只要上游傳進 `app/foo.js:0`，`parseLocation()` 會回傳 line=0，後續 `postPullReviewComment` 會帶著 `new_position: 0` 發到 Gitea，通常會被拒絕或定位失敗。也就是說，0 行號沒有被當成缺值處理。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:233",
    "role": "Maya",
    "original_finding": "`postFindingsReview` 的降級流程有兩層：先嘗試批次 review，再失敗時改成 summary-only，最後 summary-only 也失敗才退回一般 comment。現在的測試只驗到第一層失敗後、第二層成功的情境，沒有驗證 summary-only 也失敗時是否真的會呼叫 `postIssue(body)`，這是最脆弱的 fallback 路徑之一。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/config.js:31",
    "role": "Maya",
    "original_finding": "這裡是整個 action 讀取 `INPUT_*`、`GITEA_*` 與事件 payload 的入口，但測試只覆蓋了 `getLLMConfig()`，沒有把 `GITEA_TOKEN`、`GITEA_COMMENT_TOKEN`、`PR_NUMBER`、`PR_HEAD_SHA` 這些環境與 payload 的優先序鎖住。特別是 comment token 退回主 token、以及 event 檔讀不到時回到空值的情境，都是 CI 最容易因環境差異壞掉的地方。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/config.js:27",
    "role": "Bard",
    "original_finding": "這段註解已經跟著介面走音了。它宣稱使用端「只需傳 `with: token`」，但這次 action 其實已新增 `comment_token` 與 `model` 等輸入，註解仍停留在舊旋律，容易讓讀者誤判介面現況。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:227",
    "role": "Mage",
    "original_finding": "這個抽取器一旦命中目標檔案，就一路把後面的 diff 全部帶進去，沒有在下一個 `diff --git` 區塊時停下來。最小重現：diff 同時有 `a.js` 和 `b.js`，要補 `a.js` 的行號時，送給 LLM 的內容會混進 `b.js` 的 hunks，結果很容易定位到錯的行，或讓模型把別檔的內容誤認成目標檔上下文。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:277",
    "role": "Maya",
    "original_finding": "`deduplicateWithAI` 是新的核心語意去重流程，但目前完全沒有直接測試它的成功與失敗分支。尤其是 LLM 回傳排序不同、夾雜幻覺項目、回傳空陣列或超量結果時，程式會改走保守 fallback，這些都是很容易壞掉但現在沒被驗證的邊界。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:322",
    "role": "Rogue",
    "original_finding": "每一筆缺行號的 finding 都重新呼叫 `extractFileDiff(diff, file)` 掃完整份 diff，若同一檔案有 k 筆問題，就會重複做 k 次整份 diff 解析，浪費量是 O(k × diff長度)。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/llm.js:56",
    "role": "Bard",
    "original_finding": "`cliArgs` 把不同提供者的參數拼湊在同一個分支裡，還讓 `opencode` 走了另一套文字輸入路線，整個 helper 的節奏忽然一分為二。讀起來像兩個介面硬塞進同一支笛子。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:1",
    "role": "Leo",
    "original_finding": "這個模組同時處理舊 findings 載入、合併去重、缺行號補齊、排除規則正規化、誤報過濾、AI 去重、以及 exclusions 的讀寫，職責已經混成一包。更麻煩的是 `loadExclusions`、`appendExclusions`、`applyExclusions` 各自都有一套相近但不完全一致的比對邏輯，未來只要規則改一處，另一處沒同步就會開始出現不可預期的行為差異。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/json.js:113",
    "role": "Mage",
    "original_finding": "這裡只檢查 `JSON.parse(normalized)` 能不能成功，沒有確認修復後的內容真的是陣列。最小重現是 AI 把 `findings.json` 修成 `{ \"a\": 1 }`，函式會照樣寫回檔案並回報成功，但下一輪讀取時 `readJSONArray` 會把它當成非陣列而視為空值，等於把資料靜默吃掉。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:74",
    "role": "Mage",
    "original_finding": "這裡用 `path + line` 當唯一群組鍵，且只保留第一筆 `botFinding`。最小重現是同一個檔案同一行同時被兩個角色指出不同問題，`groupConversations` 會把它們合成同一組，後來的那筆 finding 會被吞掉，導致後續關閉、回寫或保留時少掉一個問題。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:214",
    "role": "Maya",
    "original_finding": "這裡新增了 `postOldFindingsComment` 與 `postNewNonCriticalComment` 兩條公開的留言分流路徑，但現有測試只驗證了 `postNewCriticalComments` 與 `postFindingsReview`，完全沒有案例確認這兩個函式的篩選條件、空陣列時是否跳過、以及輸出的 Markdown 內容是否真的只包含對應的 findings。這種分流邏輯一旦算錯，就會發生該發的沒發、或不該公告的問題被貼出去。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:171",
    "role": "Maya",
    "original_finding": "`postFindingsReview` 的救援路徑只測到「批次 review 失敗後，改發逐筆 inline comment」這一段，卻沒有驗證第二次 `postReview({ comments: [] })` 也失敗時，會正確降級到 `postIssue(body)`。這條路徑是 Gitea review API 整個故障時保住摘要的最後保險絲，沒測到的話，真正出事時很容易靜默漏報。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/main.js:17",
    "role": "Maya",
    "original_finding": "`main()` 整個流程目前沒有任何直接測試，只能靠零散的子函式單測推測結果；但這裡包含多個關鍵分支與 `process.exit` 行為，例如 preflight 失敗、bot 自動提交跳過、空 diff 提早結束、JSON 驗證失敗、以及偵測到 critical 後結束失敗。只要接線順序或退出碼改壞，現有測試不會第一時間抓到。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/gitea.js:71",
    "role": "Assassin",
    "original_finding": "`.reviewignore` 是從被審查的 PR head 直接讀回來的，提交者自己就能在同一個 PR 裡新增排除規則，把惡意檔案或關鍵目錄整批從 diff 中消失。攻擊者只要加幾條前綴，就能讓這個審查流程根本看不到真正危險的變更。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/roles.js:41",
    "role": "Assassin",
    "original_finding": "角色 prompt 直接從目前 checkout 的 `src/prompts/roles/*.md` 載入，等於把 system prompt 放在可被 PR 修改的位置。攻擊者只要改這些 markdown，就能改寫審查角色的指令，命令模型忽略漏洞、輸出空陣列，或把所有問題打成誤報。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:16",
    "role": "Assassin",
    "original_finding": "這裡把整份 diff 直接塞進 LLM 輸入，沒有做結構化封裝或輸出約束。惡意提交者可以在程式碼註解、字串或檔案內容裡埋 prompt injection，誘導模型少報、漏報，甚至捏造不該存在的問題，讓後續去重與發布流程建立在被污染的判斷上。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:153",
    "role": "Leo",
    "original_finding": "這裡開始對 `is_new` 的解讀就和前面的統計邏輯不一致了：`!f.is_new` 會把 `undefined` 當成舊問題，但同檔前面的 `newFindingsOnly()` 又把 `undefined` 當新問題。之後 `formatFindingsStats`、舊問題留言、新問題留言會各自走不同分類，未來只要上游少填一個欄位，結果就會悄悄分岔，很難追。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:91",
    "role": "Leo",
    "original_finding": "這裡的文字正規化規則和 `normalizeText()` 不一致，還在註解裡直接寫了「不確定是否預期」。再往下又有 `mergeFindings`、`appendExclusions`、`applyExclusions` 各自用不同簽章做去重/比對，等於同一份排除資料在不同流程可能被視為不同東西。這種規則分裂最容易在半年後變成『怎麼這筆有時候去重，有時候又新增』的維運災難。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:426",
    "role": "Mage",
    "original_finding": "這裡只要 `exPath` 或 `ex.role` 存在，就直接把 `textMatches` 跳過。實際結果是：同一個檔案、同一個角色的任何其他 finding，只要碰上這筆排除規則就會被整包濾掉，哪怕問題本質完全不同。最小重現：先把 `app/a.js` 某個誤報加入 exclusions，之後同檔同角色的另一個真問題也會一起消失。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:202",
    "role": "Mage",
    "original_finding": "這個去重 key 把 `suggestion` 截成前 50 個字元。只要兩筆 finding 在同檔、同角色、同位置，且建議文字前 50 字相同，後面的差異就會被吃掉。最小重現：兩個不同問題的 suggestion 都以相同開頭描述，第二筆會被當成重複直接丟失。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:303",
    "role": "Mage",
    "original_finding": "這裡回填 AI 去重結果時，也用同一個 `location + suggestion 前 50 字` 當對照鍵。只要兩筆原始 finding 的 key 撞到，`origMap` 會只留最後一筆，AI 回傳的結果就可能對到錯的原始 finding，或直接被 `filter(Boolean)` 吃掉。最小重現：同檔同位置兩筆 suggestion 前 50 字相同，去重後會錯配或少一筆。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:75",
    "role": "Mage",
    "original_finding": "這裡把 `file:0` 當成有效行號回傳。後續 `postFindingsReview` 和行內 critical comment 會把它送進 Gitea，但 `new_position = 0` 並不是有效 diff 行號，結果不是 API 拒絕，就是整筆 comment 被降級/略過。最小重現：LLM 回 `app/a.js:0`，流程仍會嘗試建立行內註解。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:91",
    "role": "Mage",
    "original_finding": "同一個 `path|line` 的多筆 bot comment 只會保留第一筆 `botFinding`，後面的 finding 會被靜默丟掉。最小重現：同一行上有兩個不同角色或不同建議的 bot comment，reconcile 時只會帶回第一筆，另一筆不會進入 resolved/excluded/carried 清單。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/main.js:31",
    "role": "Maya",
    "original_finding": "這個 orchestrator 是整條 pipeline 的入口，但目前測試都停在零件層，沒有直接驗證 `main()` 的關鍵分支：前置驗證失敗、bot 自動提交直接退出、diff 為空直接退出、JSON 驗證失敗退出、以及發現 critical 時的 exit 1。這些流程一旦接線錯了，單元測試還是可能全綠。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:267",
    "role": "Maya",
    "original_finding": "`postOldFindingsComment` 和緊接著的 `postNewNonCriticalComment` 都是新公開行為，但測試只覆蓋 `postNewCriticalComments` 與 `postFindingsReview`，沒有直接驗證這兩個 comment helper 的內容格式、空陣列跳過、以及 `is_new`/`level` 過濾是否正確。這會讓留言分流一旦退化，測試完全抓不到。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:249",
    "role": "Maya",
    "original_finding": "`mergeFindings` 與 `sortByLevel` 是 Step6 的核心邏輯，但目前沒有直接測到去重 key 的行為，也沒有測到合併後的排序是否真的維持 critical > warning > info。只靠上層流程的間接測試，對這種資料整理規則不夠穩。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/llm.js:17",
    "role": "Rogue",
    "original_finding": "這裡把 `limit <= 0` 解讀成「不限制」，直接開到 `items.length` 個 worker。只要 finding 或對話一多，就會同時 spawn 一整排 LLM 子行程，CPU、記憶體、檔案描述元一起被打爆，熱路徑很容易從平行加速變成資源風暴。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:169",
    "role": "Rogue",
    "original_finding": "這段為了產生約 41 行的 `codeWindow`，先把整個檔案內容從 Gitea 抓回來。大檔案時等於每個 open conversation 都在付整份檔案的網路與記憶體成本，實際只用到一小段片段，浪費量會跟檔案大小線性成長。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:228",
    "role": "Rogue",
    "original_finding": "這個 `extractFileDiff` 一旦開始抓到目標檔案，就一路把後面的所有 diff 都塞進去，根本沒有在下一個 `diff --git` 停下來。結果本來只想餵單一檔案的提示，可能膨脹成接近整份 PR diff，每次補行號都在多燒 token 與傳輸時間。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:406",
    "role": "Rogue",
    "original_finding": "這裡是典型的 `findings × exclusions` 雙層掃描，而且內層每次還要做字串正規化與比對。排除規則一多就變成 O(F*E) 熱點，幾百筆 finding 配幾百條 exclusions 時，白白重複掃描與正規化的成本會很明顯。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:25",
    "role": "Maya",
    "original_finding": "Markdown 表格列直接嵌入 role、location、suggestion，但測試沒有覆蓋 suggestion 含 `|`、換行或 Markdown 特殊字元時的輸出。這不是要求現在一定要改格式，而是目前缺少案例確認表格在真實 LLM 輸出下不會被破壞。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:102",
    "role": "Rogue",
    "original_finding": "統計表與單行摘要各欄位都用 `filter(...).length` 重掃多次，同一批 findings 會被走 4 到 8 次。資料量一大，連 log 文字本身都開始吃不必要的掃描成本。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:266",
    "role": "Maya",
    "original_finding": "`postOldFindingsComment` 與緊接著的 `postNewNonCriticalComment` 都是這次新加的對外 comment 發布行為，但目前沒有專門測試它們的空陣列早退、標題文字與表格內容。這會讓 comment 分流邏輯只靠間接測試支撐，回歸時很容易漏掉。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/findings.js:393",
    "role": "Rogue",
    "original_finding": "前面已經把 exclusions 正規化、去重過一次了，這裡為了 log 又再丟進 `buildExclusionContext` 重做 normalize / dedupe / group。等於同一批資料在同一輪流程裡被重算兩次，白白多吃一輪 O(n) 到 O(n log n) 的 CPU。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:88",
    "role": "Rogue",
    "original_finding": "這個 `codeWindow` 每遇到一筆 open conversation 就對整份檔案內容再 `split('\\n')` 一次。若同一個檔案有多條 thread，O(L) 的切割和陣列配置會被重複吃掉，明明同一份內容卻一直重複解剖。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:201",
    "role": "Leo",
    "original_finding": "`reconcileConversations()` 同時在做 comment 分組、關閉遠端 review、讀檔、抽 code window、AI 裁決、再把結果拆成 resolved / excluded / carried 三條路徑，流程很完整，但也很難局部理解或替換。未來任何一段判斷要調整，都得先吞下整個函式的心智負擔，維護門檻偏高。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/usage.js:212",
    "role": "Mage",
    "original_finding": "這個百分比計算只擋了 `limit <= 0`，沒有擋 `remaining < 0`。最小重現是 `resolveRemainingPercent({ available: true, used: 150, limit: 100 }, null)` 或 `remaining = -1`，會算出負百分比，讓使用量摘要出現不合理的 `-50%` 之類結果，和函式註解宣告的「負數視為無法計算」不一致。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:21",
    "role": "Leo",
    "original_finding": "Markdown 表格列直接把 `role`、`location`、`suggestion` 原樣插進去，沒有處理 `|`、換行或其他會破壞表格結構的字元。現在看起來能跑，但只要 LLM 產出一個含管線符號的建議，表格格式就會裂掉，後續維護者會一直在修奇怪的留言排版。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/preflight.js:126",
    "role": "Leo",
    "original_finding": "`clientVersion` 被硬編成 `0.142.5`，這種版本字串沒有單一來源，時間一久幾乎一定會過期。到時候 preflight 會因為一個靜態常數失效，維護者還得回頭搜尋到底是哪裡卡住，排查成本很高。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/roles.js:181",
    "role": "Maya",
    "original_finding": "`buildVerdictPrompt` 是防守方裁決流程的 prompt 契約，但目前沒有任何測試確認它會帶入預設 Paladin 人設、`exclusionHint`、以及 `confirmed / false_positive` 的輸出格式。這類 prompt 一旦格式偏掉，後面的對話收斂會很難診斷。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/resolve.js:54",
    "role": "Maya",
    "original_finding": "`groupConversations` 目前有測 `position` 與 `original_position`，但沒有測 `new_position` 這個常見的 Gitea review comment 欄位。這代表如果 API 回來的是 `new_position`，對話分組與 bot finding 對位是否正確，現在沒有被試煉過。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  },
  {
    "location": "src/comments.js:186",
    "role": "Rogue",
    "original_finding": "這裡先把 `commentFindings` 全部排序，再過濾掉 `is_new === false` 的舊問題。等於對一批最後根本不會送出的資料先付一次 O(n log n) 排序成本，舊 finding 越多，這筆白工越大。",
    "reason": "使用者裁定：本輪僅修復 main() 整合測試（critical #2），其餘 findings 一律判為排除（可接受現況／待後續人工處理）。"
  }
]
