fix: post-merge audit — 不會執行的修法、會毀資料的腳本、不會失敗的測試 - #319
Merged
Conversation
對 8d0ec33..737dbe0 跑 ensemble 稽核(#295 的 R7 從未被審過,#306–#314 的 21 張 issue 完全沒有 ensemble)。4 CRITICAL + 17 HIGH。未被審的那半有一個一致的模式: **我把文件當成了實作**,而三個「已修好」的東西根本不會執行 —— 它們都通過了我的測試, 因為那些測試驗的是我寫的那塊,從沒驗它與 gh/檔案系統/讀者相接的那道縫。 CRITICAL - migrate-idd-config.sh 會搬走掃描範圍外的檔案並回報 ✓ migrated(find -print 配 read -r,含換行的路徑被拆成兩筆,第二筆是相對路徑、對呼叫端 CWD 解析)。改 -print0 + 拒絕掃描根外的路徑 + prune archive/worktrees + breadcrumb 不覆蓋既有檔。 - 取得層的修法從來沒有運作:gh api --paginate --jq 每頁各吐一個 array,--argjson 收到非法 JSON → 整個稽核在任何含長 issue 的 repo 上靜默停擺。改 jq -s add,另加 「補回來變少就拒絕」與 .number 的整數驗型。 - 同一個修法從未落到 /idd-list --audit-closes —— 那才是實際印出 --retroactive 邀請的 介面。已補上。 - bare_re 的尾錨把帶尾綴的強調標題送進 missing;新增 emph_re,錨點保留(它是散文 提及不會誤救的原因)。 修法無效者 - #286:gh release upload FILE#TEXT 的 # 是 display label 不是檔名,且更早之前 upload_name 就是 dead code —— 命名慣例從未被套用。改為 stage 成目標 basename 再上傳, 並還原 --clobber(拿掉它會讓合法重傳失敗)。 - #293/#305:七個呼叫點的 .[0] 一行沒改,只加了散文警告。精篩與時序檢查已進 code。 - #302:global 層沒有任何 reader,而我寫的理由(「已在 walk-up 路線上,只需多認一個 檔名」)描述的是一個沒做的改動。reader 已補,其餘 consumer 誠實記為 residue。 不會失敗的測試 - prose-drift 掃描大小寫敏感,而 marker 是大寫;且 positive control 種小寫 —— 控制項 通過而檢查是瞎的。已改為大小寫不敏感 + canary 用 canonical 大寫。 - 新增 acquisition-truncation suite:用 PATH 上的 gh stub 讓 live-gh 分支真的被執行 (既有 suite 全被 --json-file 短路,所以整段 19 行刪掉仍 46/46 綠)。acid 5/5 各自 轉紅;達到這個狀態改了三次斷言,三次都記在檔案裡。 其餘:idd-repo-map 補 sanitiser(它從零重造了隔壁腳本花七輪關掉的 row-forging)、 兩處 callout 被插進 bash fence 內部、marketplace.json 落後五個版本。 47/47 suites 綠(新增 acquisition-truncation 12 條)。
kiki830621
added a commit
that referenced
this pull request
Aug 15, 2026
…修的 CRITICAL (#320) * fix: migrate-idd-config 只肯處理一般檔案,且用 ln 取得 mv 沒有的 no-clobber 跨模型(Codex)在 post-merge audit 指出四個 CRITICAL:`.idd` 可以是 symlink、breadcrumb 的 `>` 會跟隨 symlink、目的檔只用 `[ -f ]` 檢查、 `find` 只比對檔名。#319 修掉了路徑切割那個(`-print0`),這四個沒修。 共同根因是同一句沒被說出口的假設:**這些路徑都是一般檔案與一般目錄**。 腳本沒有檢查它,於是由 shell 自己「跟隨 symlink」的預設替使用者的資料 做決定。`--apply` 預設走遍整個 ~/Developer,所以「我 clone 下來的 repo」 就在威脅模型內,而上面每一種形狀都能被 commit 進 repo 裡。 - `.idd` 是 symlink → `mkdir -p` 靜默接受並回報成功,config 被搬出樹外 - 目的檔是目錄 → `mv` 把檔案搬**進去**,變成 `local.json/issue-…json`, 然後印「✓ migrated」 - 目的檔是懸空 symlink → `-e` 為 false,沒有任何警告 - 目的檔是指向樹外一般檔的 symlink → `-f` 跟隨它,稽核印出「both present, current wins」——一句在「current 其實不在這個 repo 裡」時為假的話 - breadcrumb 路徑是懸空 symlink → `-e` 看不見,`>` 直接**建立**目標 - `-name` 會撈到 doc sample / fixture,在別人的樹裡憑空造出 `.idd` `-f`、`-d`、`-e` 都會跟隨 symlink,所以順序不是風格問題:`-L` 必須先測, 否則一個連結是由它指向的東西受審。 `mv` 沒有原子的 no-clobber——檢查與 rename 是兩個 syscall,中間有窗口。 `ln` 在目的存在時原子失敗,所以 hardlink-then-unlink 給得出 `mv` 給不出的 保證,且中途被打斷時留下兩個 link 指向同一 inode(config 兩邊都讀得到, 不會兩邊都沒有)。`mv -n` 是另一個候選,但 BSD 上它的 exit status 分不出 「已搬移」與「因目的已存在而跳過」。 測試:hostile tree 自成一個 scan root、自己一次 `--apply`(拒絕算 failure, 混進 happy path 會讓「apply exits 0」變成空話)。7 種形狀 + 1 條回歸鎖 (`find -type f` 用 lstat,所以 legacy 路徑是 symlink 時本來就不會被跟隨; 釘住它是因為這個保護藏在一個 flag 裡、看不見,將來有人改成 `find -L` 會安靜地開始跟隨)。30 assertions,全 suite 47/47。 acid(逐條還原、確認 suite 會紅):7 個機制裡 6 個單獨可轉紅。`ln` 在預設 組態下被前面兩個守衛遮住,把它們一起關掉、只變動 ln↔mv,紅的數量從 4 變 6——它是活的縱深防禦、不是裝飾。**mkdir 之後的 .idd 複查(M2)沒有測試 重量**:它只窄化 TOCTOU 窗口,單一 process 的測試碰不到,如實記在這裡。 * fix: repo-map 的列緩衝改用真的定位/換行字元,並檢查 find 的退出碼 `rows` 用 `\t` / `\n` 兩字元跳脫存放、再用 `printf '%b'` 印出,而 `%b` 也會 展開**資料裡**的跳脫。所以路徑或 `github_repo` 裡的一個反斜線是被執行、 不是被印出。sanitizer 擋掉了控制字元與 bidi mark,從來沒碰過反斜線——當時 下游沒有東西會解讀它。 `\c` 最糟:`%b` 遇到它會停掉**全部**輸出。一個被構造的值就能安靜刪掉它後面 的每一列與 totals 行,而少了一半列的地圖,讀起來跟「這台機器本來就只有這些 repo」一模一樣。`\n` 造出獨立的偽列,`\t` 位移欄位。 **更正一項我在 audit 裡寫錯的話**:當時我說「`--json` 路徑是安全的(jq 會 正確跳脫)」。那句話只對 newline payload 成立。同一個 `%b` 也餵給 jq,所以 反斜線 payload 一樣會截斷 JSON 那一列(實測 `config_format` 變成 `null`)。 我測了我寫的那段(sanitizer),沒測它與 printf 相接的那個縫。 改法:列緩衝直接放真的 TAB / NEWLINE,三處 `%b` 改 `%s`。資料不可能再帶回 真的控制字元,因為 sanitize_field 會刪掉 tab、把 newline 折成空白。 `find` 的退出碼:process substitution 會丟掉它。這件事在別的掃描器只是少幾 列,在這裡是要害——這張地圖的全部工作就是回答「這一層在不在」,而一個讀不 到的目錄回答「這裡沒有 config」,跟真的沒有 config **無法區分**,那正是 #301 的錯誤向上解析。改成先導向暫存檔、檢查退出碼、不完整時明講。順帶把 `-print` 換成 `-print0`(同 #319 對 migrate 的處置,消掉含換行目錄名那一類)。 新增 `refute_grep_re`(+ 自身 3 條測試)。這個 helper 值得存在,是因為固定 字串的否定斷言是本 repo 最容易寫錯的一種:`o/FORGED-ROW` 合法地出現在它被 寫進去的那一列**之內**,`refute_grep` 會因為一個測試根本沒打算檢查的理由而 失敗。要錨定就需要 regex。 測試自身也修過一次:第一版用 printf 寫 payload,檔案裡是 `\c`——那不是合法 的 JSON 跳脫,jq 直接拒收,值根本沒進到列緩衝,於是**探針通過、卻什麼都沒 測到**。改用 quoted heredoc 讓磁碟上真的是 `\\`,並加一條 probe self-check 斷言 payload 確實活著到 jq 之後。 acid:`%s`→`%b`(含還原跳脫式列緩衝)紅 2 條;拿掉 find 退出碼檢查紅 1 條。 全 suite 47/47。 * feat: --retroactive 的 precondition 改成真的執行 classifier(--issue N gate) `idd-close/SKILL.md` 原本自己寫著:「本 gate 是散文,不是機械強制…機械判定 只存在於 check-closed-without-summary.sh,而本 skill **並未呼叫它**…未做」。 揭露是誠實的,但揭露不是緩解——**七輪 verify 的全部成果,要等 agent 剛好讀到 那張表才生效**,而它守的是一個不可逆動作(在可能已有 summary 的 issue 上再 貼一份)。 helper 新增 `--issue N` 單一 issue 模式,輸出一個 JSON 物件,並且**用退出碼 下判決**: 0 class == missing 且 comment 集合完整 → 唯一放行 1 其他分類(compliant / casing / present) → 拒絕 2 無法判定(未 CLOSED / 截斷 / 抓取或解析失敗)→ 拒絕 審計模式的「永遠 exit 0」契約原樣不動(有斷言釘住)。那條契約是對**人**報告 用的;gate 是不可逆動作的前置條件,一個永遠 exit 0 的 gate 不是 gate——呼叫端 只能去解讀散文,而那正是這七輪維持 advisory 的原因。 gate 模式另外**不走** `--json comments` 那條路:它用 REST `--paginate` 抓 comment。那條巢狀 connection 硬上限 100 則且回**最舊**的 100 則,而 closing summary 依定義是**最新**一則。審計端是事後修補截斷(#319),gate 端根本不走。 fail-closed 的範圍寫死在 skill 裡,包含「helper 不在」——找不到 gate 等於沒有 gate,不是「那就跳過吧」。 prose-drift 測試補兩類斷言:(1) skill 必須真的**執行** helper(比對呼叫式與 退出碼分支),不是只「指向 normative source」——指向正是它散文化七輪期間一直 在做的事;(2) helper 必須真的有那個 flag,否則 skill 呼叫一個不存在的旗標會 以最壞的方式 fail open(未知旗標只警告不中止,等於把審計的 always-exit-0 搬到破壞性路徑上)。 順帶修掉 drift 測試的 canary:它往 repo 寫固定檔名、沒有 trap。中途被打斷就 留下一個檔案,之後每一次執行都紅、且會被 `git add -A` 帶進 commit;固定檔名 還讓兩個並行執行互刪對方的 canary,各自把對方的刪除讀成「掃描看不到植入的 字串」。改成唯一檔名 + trap。 測試自身修過兩次: - `jq -r ".comments_complete // \"null\""` 把真正的 `false` 報成 "null" (jq 的 `//` 視 false 為空),那條截斷斷言因此不可能失敗。改 `tostring`。 - 一個斷言字串裡的反引號被 shell 當成命令替換執行掉了。 acid:G1 截斷仍放行→紅 2;G2 非 missing 仍放行→紅 4;G3 未 CLOSED 不再拒絕 →紅 1;G5 skill 不再呼叫 gate→紅 1。G4(--issue 整數驗證)第一輪**沒有測試 重量**——拿掉它 `--issue abc` 仍然 exit 2,只是改由下游 jq 崩潰造成,退出碼 分不出兩者。補一條斷言檢查錯誤訊息確實來自驗證本身後才轉紅(那條驗證真正 要守的是 live 路徑:數字會被插進 API URL,中間沒有 jq 擋著)。 全 suite 47/47,classifier suite 114 assertions。 * fix: 內容判定與首行判定共用同一個「讀者看得見」的定義 `lead_line` 找「讀者實際看到的第一行」時會跳過整行 HTML marker(本 plugin 要求 機器可定位的 marker 放第一行,不能讓它把一份逐字正確的 summary 降級)。 `lead_has_content` 掃後續行找內容時**沒有套同一條規則**,於是 HTML 註解被算成 summary。 ## Closing Summary <!-- TODO: write this up properly --> 判成 `compliant`——**印在任何區段都沒有**,而 `--retroactive` 也拒絕它。那正是 內容判定當初要關掉的靜音管道,在下一層又開了一扇門。非 canonical 大小寫的同一 形狀則落 `casing`,那一類是**不帶 ⚠ 印出、並宣稱「summary 在」**。 抽出 `invisible_line`,兩邊共用。對讀者來說空行與看不見的行是同一件事,在這裡 就該是同一件事。 **方向誠實話**:這整類在**便宜**的一側——錯的答案是把 issue 藏起來,不是授權 重複貼文。仍然修,是因為前一輪明講要關掉這個管道、卻只關了一扇。 只認**單行** HTML 註解,這是刻意的:跨行的需要第 1-4 輪移除掉的 fence/comment 狀態機,而殘留誤差同樣落在便宜那一側。 fixture #162 / #163 + 6 條斷言;acid:拿掉後續行的可見性過濾 → 紅 3。 全 suite 47/47,classifier 120 assertions。 * fix: 寬鬆比對不得用來做正面斷言(idd-find / idd-update),附件 URL 三種包裹形式 normative source 把 predicate 拆成兩個,判準不是「哪個比較準」而是**過度偵測 會往哪個方向錯**:`present_re`(寬)回答「讀者看得到嗎」,多報一次只是不採取 破壞性動作;`lead_re`(嚴)回答「這則 comment 是不是以它開頭」,多報一次就是 講了一句假話。這條規則不只屬於 classifier,但有兩個散文 reader 站錯邊: - **`idd-find`** 用寬鬆比對標 `📜 closing summary(可考古的結案紀錄)`。一則 只是引用模板來提問的 comment 會拿到一模一樣的標籤。改成 `📜 summary marker` ——**標籤的字面就是它能保證的全部**。比對維持寬鬆(搜尋結果漏報的誤導成本 更高),改的是它敢宣稱什麼。render 範例那行一起改:只改被點到的那行、留下 隔壁那行,正是這個 marker 五輪漂移的原樣。 - **`idd-update`** 的 phase 推斷同樣用寬鬆比對,而 phase 是正面斷言(「這個 階段發生過」)。一則引用 `> ## Closing Summary` 的討論就能把一張**還開著**的 issue 推成 `closed`。改成要求該 heading 是那則 comment 的首行、不帶 blockquote 前綴、縮排 ≤3 空格 —— 即 `lead_re`。 **附件 URL 抽取**:字元類只排除 `)` 與空白,也就是只涵蓋 markdown link。實測 另外三種真實 issue body 的包裹方式全部把包裹字元黏了回來: <https://…/a.pdf> autolink → 尾巴多 `>` <img src="https://…/b.png"> HTML 屬性 → 尾巴多 `">` see https://…/c.pdf. 句尾 → 尾巴多 `.` 三種都會 404,而抓不下來的附件就是被忽略的來源 —— 本 plugin 把那當成違規而非 不便。`<` `>` `"` `'` 不可能未編碼地出現在 URL 裡,排除掉是零成本;句尾標點改 成事後去掉一個(`.` 在 URL 中段合法,而這些網址都以副檔名結尾)。 **jq 錯誤路徑**:註解寫著「strip control characters and bidi overrides」,程式 只做前者 —— `tr` 在 LC_ALL=C 下處理**位元組**,看不到多位元組的 U+202E / U+2028。 先量了這個縫**通不通**:三種 payload(U+202E / U+2028 / ESC)放在會讓 `.state` 撞上裸字串的位置,jq 1.7 回 `Cannot index string with string ("state")`,**完全 不回顯輸入值**。所以改的是註解不是程式碼,並如實記下這個乾淨的否定結果 —— 補一份 Unicode-aware scrub 等於多一份字元類副本,而「安全定義的副本各自漂移」 正是這個檔案最長的病史。哪天遇到會回顯的 jq,要改的就是那一行。 新增:process-attachments 的 `wrapped` gh-stub mode + 5 條斷言(RED 3 條); prose-drift 補 3 條斷言釘住兩個 reader 的方向。全 suite 47/47。 * fix: idd-verify 的暫存路徑真的改掉了(#288 的 24 處殘留),並用測試釘住 #288 的 closing summary 說「每個 scratch path 現在都掛在 `mktemp -d` 底下」。 實際出貨的是一段**這樣寫的註記**,後面接著二十四處繼續 MUST 舊的固定路徑 —— 包含交給 reviewer agent 的 OUTPUT 指示,也就是真正決定 findings 落在哪裡的那 幾行。規則和違反它的例子相隔四行,共存了兩個版本。 **一份先寫規則、隔一行又自打嘴巴的規格,比什麼都沒寫更糟**:去查規則的人查得 到,照著抄命令的人抄到缺陷。 固定名稱不帶 repo 身分:同一個 issue 號在不同 repo 的兩個 session 共用檔名, 前一輪的殘檔會被當成這一輪的 findings 讀進來 —— 靜默,且方向最壞(把別的 repo 的判決併進這份報告)。 全部改掛 `$VERIFY_DIR`(Step 0 新增 `resolve_scratch_dir` task,在任何寫檔或 spawn 之前解析一次)。過程中另外撞到兩個**同一段迴圈的兩份副本**——egress body (`master.md` / `pointer.md` / `pointer_template.md`)也用固定名,而那是要被 **貼到別人 issue 上的文字**:共用路徑上的殘檔或半寫檔不會大聲失敗,它會發布錯的 留言。兩份都改了;第二份是機械掃描抓到的,不是我看到的。 新增 `scripts/tests/verify-scratch-paths/`(第 48 個 suite):掃固定 /tmp 路徑, 帶 positive control(唯一檔名 + trap)。 **scope 明講**:只掃 `idd-verify`。同一個 grep 也會打到 `idd-edit` 的 `/tmp/idd-edit-backup/` 與 `/tmp/idd-edit-repl-*` —— 那是另一回事、**未涵蓋**: backup 目錄是文件叫使用者去 `ls` 的復原位置,搬它是行為變更,且碰撞後果是看得見 的衝突而非被靜默併入的判決。這條寫進測試檔本身,免得有人把它的綠讀成對 idd-edit 的背書。 註記本身也不再逐字寫出被禁的舊路徑 —— 同 prose-drift 測試禁止複述 classifier regex 的理由:不存在的副本不會漂移,而寫進文件的違例字面正是機械檢查第一個踩到 的東西。 全 suite 48/48。 * fix: idd-clarify 的 worked example 第三欄還是舊的分析式寫法 #294 依使用者原話把 `Question for you` 那一欄從描述式分析改成問句 —— > 「iddclarify我覺得你可以直接放對使用者的問題」 理由在讀者是誰:那一欄給的是**要做裁決的人**。寫成分析,他讀完還得先把分析 還原成一個可回答的問題。 出貨的是**規則**、一張 ❌/✅ 對照表示範規則 —— 然後四十行後的 worked example 第三欄還是分析(`分群變數 / distinguishing variable (per K-means context…)`)。 照規則做的人得到新行為,照範例抄的人得到舊行為,而**範例才是會被抄的那半**。 跟 #288 的暫存路徑同一類:先寫規則、再在自己的範例裡違反它。 改掉兩列,並新增 `scripts/tests/clarify-question-column/`(第 49 個 suite): 掃所有 `surfaced` 範例列的第三欄,沒有問號就紅(`...` 佔位、`passed`、 `deferred` 三種豁免——前者不是措辭範例,後兩者沒有東西可問)。帶 positive control(唯一檔名 + trap)。在修改前的樹上跑會紅,已驗。 全 suite 49/49。 * chore: 2.110.0 — CHANGELOG + plugin.json/marketplace.json 同步 * fix: idd-plan 對 idd-all 的 Plan routing 描述改成分述(#317) #292 之後 **attended** 的 `/idd-all` 走 `/idd-plan`(`EnterPlanMode` 閘門住在 那裡、不在 `/idd-implement`)。`idd-all` 更新了,`idd-plan` 的「與 idd-all 的 整合」段沒有,而且它把舊規則寫成**全稱句**: > **idd-all 不該走 Plan path**…應該 fallback 走 Simple path 那句話的 unattended 半段是對的。錯在把一個分支的結論寫成無條件的 —— 以 idd-plan 為準的讀者,會得到與實際執行相反的結論。 改成兩欄分述(attended / unattended),並明講 normative source 是 `idd-all` 的 dispatch table。這正是 repo 自己那條規則說的:**能列舉的就列舉,不要先寫一句 總括判準再補例外** —— 總括與例外是兩份不會一起改的規格。 新增 `scripts/tests/plan-routing-consistency/`(第 50 個 suite):先對 `idd-all` 斷言 routing 本身仍成立(否則下面那些斷言只是在釘一份虛構),再對 `idd-plan` 斷言分述存在、全稱句消失。在修改前的樹上跑紅 4 條,已驗。 全 suite 50/50。 * docs: CHANGELOG 補 #317 條目 + suite 數更正 49→50 * feat: ensemble 現在會被告知 diff 之外的寫入(#315) `idd-verify` 的 scope 是 diff,但 `idd-implement` 的 sister sweep 與 cross-reference note **會寫到 diff 以外的表面**:別的 issue 的 comment、別的 repo 的新 issue。沒有 任何 lens 看得到它們。 實例(`macdoc#143`):實作筆記裡一個事實錯誤被原樣擴散到另一張 issue 的參照 note。 四個 lens 都只評了 diff 內的措辭,是 devil's advocate **越出自己的 scope** 去讀 Implementation Complete 的 blast radius 紀錄才抓到。**某個 reviewer 臨場越界不是 機制。** 把 implement 自己記下的外部寫入清單塞進 reviewer context,**兩個 backend 都給** —— pai 與 manual fan-out 拿到不同的 context,會讓一個 finding 取決於當時解析到哪個 backend,而 skill 自己的契約說 Step 3 之後兩者可互換。 **沒有紀錄時報 UNKNOWN,不報「沒有外部寫入」**:漏跑的 sister sweep 與跑了但沒找到 的 sister sweep,留下的痕跡一模一樣。 過程中自己踩到一次同型缺陷:第一版的插入點鎖在只有 requirements 那個 prompt 才有 的 attachment 行,於是**五個 prompt 只改到一個**、另外四個靜靜留著舊 context —— 正是這一輪一直在抓的「改了被點到的那行、留下隔壁」。改成鎖每個 prompt 都有的 `Diff path:` 行,並在測試裡**數**:annotated 數必須等於 prompt 數,且 prompt 數 ≥4(防 0 == 0 的空洞相等)。 這是 #315 的**選項 1**。選項 2(idd-implement 寫 machine-readable manifest、 idd-verify 逐筆抽查內容)是**兩個 skill 之間的新契約**,而整輪 audit 的結論正是 「沒經過自己那輪 review 的新契約就是缺陷住的地方」。不做,理由與殘餘風險寫進 CHANGELOG,不留在 PR 討論裡。 新增 `scripts/tests/verify-external-writes/`(第 51 個 suite,7 assertions)。 全 suite 51/51。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
對
8d0ec33..737dbe0的 ensemble 稽核(#295 的 R7 從未被審過;#306–#314 的 21 張 issue 完全沒有 ensemble)。4 CRITICAL + 17 HIGH。未被審的那半有一個一致的模式:文件被當成了實作。三個「已修好」的東西不會執行,而且每一個都通過了我的測試——因為那些測試驗的是我寫的那塊,從沒驗它與
gh/檔案系統/讀者相接的那道縫。migrate-idd-config.sh搬走掃描範圍外的檔案並回報 ✓ migrated--paginate --jq每頁一個 array)--json-file讓所有既有 suite 都跳過它#286的#是 display label 不是檔名tr管線,沒驗折好的名字有沒有到 GitHub#293/#305七個呼叫點一行沒改#302global 層沒有 reader新增
acquisition-truncationsuite:用 PATH 上的ghstub 讓 live-gh分支真的被執行(既有 suite 全被--json-file短路,所以整段 19 行刪掉仍 46/46 綠)。acid 5/5 各自轉紅——達到這個狀態改了三次斷言,三次都記在檔案裡。47/47 suites 綠。