Skip to content

fix: merge-completeness gate no longer flags squash-merged branches whose lines were rewritten — 3.5.1 (#399) - #418

Merged
kiki830621 merged 33 commits into
mainfrom
idd/399-bug-idd-close-step-1-55-flags-squash-mer
Nov 3, 2026
Merged

kiki830621 merged 33 commits into
mainfrom
idd/399-bug-idd-close-step-1-55-flags-squash-mer

Conversation

@kiki830621

@kiki830621 kiki830621 commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Refs #399

Summary

idd-close Step 1.55 不再把 squash 合併、行被分支後續 commit 改寫的 commit 誤報成 orphan(PR #408 曾有 39 個)。Gate 回答「分支的內容有沒有落地」:tip 已有 head 的內容就回 0;否則找到分支落地的 commit,逐 commit 就地比對。檔案依路徑讀,不做 merge。Update branch 之後的第二個 PR 不再誤報第一個 PR 的 commit。

git 記成搬移且碰到分支檔案的,exit 4(只印 note)。已知限制見 SPEC.md 與 CHANGELOG,由維護者 2026-11-02、2026-11-03 決定。

Verify

第 27 輪無 HIGH,MEDIUM 已修(之後的文件與 partial-clone 檢查順序修正沒有再跑 verify)。suites 65/65,CI 通過,337 個已合併 PR 的答案與第 20 輪相同。

…net change as landed (#399)

The per-commit line-presence check flagged every branch commit whose added
lines a later branch commit rewrote; a squash keeps only the final content,
so those lines exist in no final tree. PR #408 drew 39 false orphans.

Before the per-commit check, walk the first-parent baseline commits after
the merge-base (oldest first, at most 200): if merging the branch head into
some commit C is clean and leaves C's tree unchanged, every branch change
is already in C and the gate exits 0. Checking every C, not only the tip,
covers a later follow-up on the same file; the 3-way merge covers a squash
of a branch that was never rebased. Anything undecided falls through to the
existing check, which can only flag more.

Fixtures 8-12: rewrite + squash, + a follow-up on the same file, a
non-rebased squash, a genuine orphan next to the rewrite shape (still
flagged), and the 200-commit cap.

Refs #399
)

Helper header Method step 0 and its hand-resolved-squash limit; idd-close
Step 1.55 rationale and fixture list (the list still said 6 fixtures).

Refs #399
plugin.json, marketplace.json, CHANGELOG, and both READMEs.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 1 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 3 blocking, 6 in-scope fix, 3 follow-up / documented(25 則原始 finding 去重後 13 項)

Scope coverage

PR refs: #399(PR body 另提及 #402、#408,兩者是 PR,不在驗證範圍)
Verified scope: #399 · freshness gate: frozen 66791a6 = PR head · CI(ubuntu)pass


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage: 原 issue 的 repro、#408 實例(39 → 0)、fixture 8–12 皆如宣稱(requirements 實跑確認;RED-first 屬實)。但包含檢查引入了兩個新的漏報方向,見 #1、#2。

# Severity Finding Source Action
1 HIGH **merge 後被 revert 不再報出。**包含檢查接受 merge-base 之後任一歷史 commit,沒有確認改動還在 tip。多 commit 分支 squash 後 git revert:舊 helper rc=3、新 helper rc=0;後續 commit 刪掉修正檔亦同。Diagnosis Risks「不會比現在更容易漏報」、helper 註解「can only flag more, never less」、CHANGELOG「a genuine orphan is still flagged」三處宣稱都不成立,限制清單也沒寫。DA 補充:單 commit 分支舊版本來就漏(patch-id 相同),最危險的是後來解衝突整檔覆蓋、歷史裡看不到 revert(check-merge-completeness.sh:88) logic+requirements+security+regression+codex+devils-advocate Blocking
2 MEDIUM 從未合併的分支被判 clean。rev-list --first-parent mb..B 列的是「從 mb 走不到」的 commit,不等於 mb 的後代;分支從 side branch 開出、main 在 merge 前有平行 commit 時,非後代 C 會讓「把 side 改回原狀」的分支被判包含(舊 rc=3、新 rc=0,DA 實測;本 repo PR #283 的拓樸即有此形)。修法:--ancestry-path(check-merge-completeness.sh:85) devils-advocate Blocking
3 MEDIUM **|| continue 有實際作用,實作者說「觀察不到」是錯的。**modify/delete 衝突時 merge-tree exit 1,但輸出第一行的 tree 保留 C 的版本、與 C^{tree} 相同;拿掉這行會把衝突判成包含(實測 rc 3→0)。缺 fixture(:89) logic Blocking
4 MEDIUM 包含檢查判不出時退回逐 commit 檢查,#399 的誤報(被分支自己改掉的行)會跟真 orphan 一起回來;建議逐 commit 檢查略過「分支 head 自己也已沒有」的新增行(:94) logic In-scope fix
5 MEDIUM 200 上限是全有或全無:超過就整段跳過,而非只走最舊的 200 個;squash 通常就在 merge-base 後幾個(:87) logic+requirements In-scope fix
6 LOW .gitattributes 的 merge driver 會套用到包含檢查:merge=ours 類 driver 讓衝突的分支改動看起來已包含(舊 3、新 0),也讓 idd-close 執行使用者設定的外部 driver(:89) logic+security+regression In-scope fix
7 LOW 包含檢查被略過的原因(無 merge-base、git 太舊、超過上限、merge-tree 錯誤)全部靜默,rc=3 的輸出看不出有沒有跑過(:82) logic In-scope fix
8 LOW 新 fixture 假設 git ≥ 2.38;舊 git 上 fixture 8/9/10/12a 會 FAIL 而不是跳過或驗退回行為(test.sh:133) codex+requirements+logic+regression In-scope fix
9 LOW fixture 11 只斷言 rc=3,殘留的 add x 誤報單獨就能造成 rc=3,沒有釘住「真 orphan 有被列出」(test.sh:201) logic+devils-advocate In-scope fix
10 LOW helper 原本唯讀,現在 merge-tree 會把 tree/blob 寫進使用者 repo 的 object store(不可達、gc 會清);最壞情況約 3 秒 regression+security Follow-up(改為在 helper 說明揭露,不另做隔離)
11 INFO rev-list 在套用上限前列舉全部 commit;merge-tree 沒有時間上限 security Follow-up(記錄,不修)
12 INFO 其餘需求逐項實跑確認:repro、#408、13/13、RED-first、文件與版本一致;無 prompt injection requirements —
13 INFO diff 外寫入(Sister Concerns/Sister Bugs 皆 none)與 diff 一致 devils-advocate —

Scope Check

9 個檔案都在 #399 範圍內;_manifest.json 符合 repo 慣例。

修正方向(第 2 輪)

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315089709 ### Risks 「假陰性」一項宣稱包含檢查不會比現在更容易漏報;merge 後被 revert 的多 commit 分支,舊版會報、新版不報(見 #1) /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
2 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到;modify/delete 衝突時觀察得到(見 #3) /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'

本輪在 /idd-all unattended 下執行:只列不做,/idd-edit 未呼叫。替換內容在第 2 輪修正後依實際結果寫入上列檔案。

…de-branch commits, or merge drivers (#399)

Round-1 verify found that the containment step could report clean when
nothing had landed. Fixes:

- a squash that main reverted wholesale is not trusted: when every path the
  branch changed is back to its merge-base state at the tip, fall through
- only baseline commits that descend from the merge-base are tried
  (--ancestry-path); a parallel first-parent commit measured against an
  older base made a never-merged branch look contained
- repo merge drivers are neutralised (empty --attr-source, no
  core.attributesFile) so merge=ours cannot make a conflict look contained
- the walk tries the oldest 200 commits instead of giving up past 200
- the per-commit check skips lines the branch head itself no longer has,
  so a genuine orphan is no longer listed next to rewritten-line noise
- exit 3 prints what the containment step concluded

Accepted and documented: a later commit that overwrites only part of a
landed change is not reported (indistinguishable from a follow-up edit).
Fixtures 9b and 11-16; containment fixtures expect the per-commit fallback
on git without merge-tree --write-tree / --attr-source.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 2 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 4 blocking, 7 in-scope fix, 7 docs / test / follow-up(37 則原始 finding 去重後 18 項)

Scope coverage

PR refs: #399(PR body 另提及 #402、#408,兩者是 PR,不在驗證範圍)
Verified scope: #399 · freshness gate: frozen 82c6aea = PR head(第 1 輪審 66791a6,本輪審完整 PR diff,重點放在 66791a6..82c6aea)· CI(ubuntu)pass
重現:blocking #1–#4 與 #6 由 orchestrator 另在 scratch repo 以新、舊 helper(da47674)各跑一次確認,結果與 reviewer 回報一致。


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage:issue 的 repro rc=0、PR #408 實例 rc=0、第 1 輪 #2/#7/#9 確認已處理。但第 2 輪加的 safeguard 有四類被推翻,每一類都是「舊 helper 會報、新 helper 回 clean」。

# Severity Finding Source Action
1 HIGH **revert 檢查只在最窄的形狀成立。**判準是「分支改過的每個路徑在 tip 都等於 merge-base」,main 之後只要改到其中任一檔就失效。實測:squash → revert → 下一次 release 改 CHANGELOG,新 rc=0、舊 rc=3;未 rebase 的 squash(fixture 10 形狀)再 revert 亦同。本 repo 每個 PR 都動 CHANGELOG/plugin.json,等於幾乎不會觸發。README「merge 後整個被 revert 的分支照樣報出」不成立(check-merge-completeness.sh:104) requirements+logic Blocking
2 HIGH **把某行改回 merge-base 原值的 commit 沒落地,回 clean。**分支 P 改 info→debug 並加一行,O 再改回 info;main 只收 P。包含檢查把「淨 diff 沒動的行」當成分支沒意見,NET_ADDED 也不含這行,兩道都放行(新 0、舊 3)。檔頭寫的判準是「branch head 已經沒有的行」,實作卻是「淨 diff 的 + 行」,兩者不同(:129、:157) logic+DA Blocking
3 HIGH NET_ADDED 用 porcelain git diff。diff.external、GIT_EXTERNAL_DIFF、或 repo 內 .gitattributes 指定的 diff driver 會讓輸出沒有 + 行,而 HAVE_NET=1 照設,逐 commit 檢查略過所有行,整個 gate 失效(新 0、舊 3)。同一呼叫也會對分支的 blob 執行 textconv;管線的退出碼是 sed 的,git 失敗也照樣放行。color.diff=always 讓新舊兩版都失效(既有問題)。DA 指出照文件寫的判準改成讀 head 的 blob(git cat-file)可一次消掉這類與 #5 logic+security+DA Blocking
4 HIGH 第 1 輪 #3 沒修。|| continue 仍無 fixture:拿掉後 20/20 照過。modify/delete 衝突(分支刪 d.txt 並加一行,main 落地後改了那行)實測 rc 3→0。實作者「觀察不到」的論證錯了,待貼的更正文字也照抄了這個錯(已改寫,見下) DA Blocking
5 MEDIUM -M 讓「先複製再刪除」的搬檔看成純 rename,從未落地的分支回 clean(新 0、舊 3)(:129) codex+regression In-scope fix
6 MEDIUM merge driver 只中和了工作樹 .gitattributes 與全域 attributes;.git/info/attributes、merge.default、系統 gitattributes 仍生效,從未落地的分支回 clean。皆為本機設定、PR 作者無法利用,但 CHANGELOG/SKILL「一律不套用」說過頭。建議在暫時的 bare repo(alternates 借用 objects)裡跑 merge-tree,順帶解決第 1 輪 follow-up 10 的 object 寫入(:92) codex+requirements+regression+logic+security In-scope fix
7 MEDIUM 只走最舊 200 個:idd-close 在 merge 後立即執行,squash 在 tip 附近;分支活得久的忙碌 repo 會再誤報,12b 把這個誤報釘成預期。exit-3 的 note 也分不出「沒走到」與「沒落地」。建議只走動過分支路徑的 commit(rev-list … -- <paths>)(:111) requirements+logic In-scope fix
8 MEDIUM --first-parent --ancestry-path:merge-base 落在後來以 merge commit 併入的 side branch 較舊位置時(stacked PR),清單為空,誤報回來,note 寫「no baseline commit descends」與事實不符。建議 first-parent 清單再用 merge-base --is-ancestor 過濾(:111) logic+DA In-scope fix
9 MEDIUM revert 檢查的 git diff --quiet 任何非 0 都當「沒 revert」:路徑清單超過 ARG_MAX(例如 2 萬個檔)會 E2BIG,revert 後回 clean;路徑列舉失敗也放行(:105) security+logic In-scope fix
10 MEDIUM 從未落地的分支,exit-3 的 note 寫「(reverted)」。這是最常見的真 orphan,「reverted」會讓操作者傾向選 close anyway(:106) regression+logic In-scope fix
11 LOW 單 commit squash、merge commit、rebase-merge 後被 revert,新舊 helper 都回 clean;rebase-merge 時 helper 已算出 reverted 卻在 git cherry 無 + 時直接 exit 0。不是回歸,但文件宣稱的 revert 覆蓋只對多 commit squash 成立 security+DA Docs
12 LOW test.sh 的 git 能力探測在呼叫者的 cwd 執行:從非 repo 目錄跑是 17/20(test.sh:127) requirements+logic+regression In-scope fix
13 LOW 第 2 輪 safeguard 中,拿掉 core.attributesFile、-M 改 --no-renames、拿掉 note 輸出、拿掉 --literal-pathspecs,20/20 都照過 DA In-scope fix
14 LOW git --attr-source 是 2.41 才有,五處寫 2.40 regression Docs
15 LOW fixture 區段標題寫 8–17,實際到 16 requirements+regression Docs
16 LOW 走完 200 個 commit 約 3.2 秒(舊 0.11 秒)、寫入約 6.6 MiB 不可達 object;CHANGELOG「gc 會清」沒寫保留期(預設兩週) regression Follow-up(#6 的隔離作法可一併消除)
17 INFO 舊 git(< 2.41)上 #408 仍有 35 個誤報,CHANGELOG 標題與 marketplace 描述沒有註明 regression Docs
18 INFO 確認通過:issue repro、#408、第 1 輪 #2/#7/#9;工作樹 .gitattributes(含 merge=union)確實被中和;奇怪檔名(-rf、換行、pathspec magic)與 ref 處理安全;未發現 prompt injection requirements+security —

Scope Check

所有改動仍在 #399 範圍內。

停在這裡的原因

/idd-all unattended 的 verify 上限是 2 輪,本輪 FAIL,依規則停止自動修正,交給 maintainer 決定。

兩輪下來的共同根因是語意沒有定案:#408 的形狀(squash 後 main 又改了分支的行)要不報,就必須接受「落地過就算」;而每加一道 revert 偵測,都只蓋住一部分形狀(#1、#11),又帶來新的失效路徑。需要決定的是 gate 回答哪一個問題:

不論選哪一個,#4、#6、#7、#8、#12、#13 與文件項都要修。

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315089709 ### Risks 宣稱包含檢查不會比現在更容易漏報;第 1、2 輪實測到多種舊版會報、新版不報的形狀(見 #1–#3、#5、#6) /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
2 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到;modify/delete 衝突時 rc 由 3 變 0(見 #4) /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'

第 1 輪報告附的這兩份替換文字,本輪 DA 指出其中又含有不成立的宣稱,已改寫為只陳述實測結果。本輪在 /idd-all unattended 下執行:只列不做,/idd-edit 未呼叫。

…anded (#399)

Verify round 2 showed that "contained somewhere, then exit 0" kept opening
false negatives the old helper did not have, and that each revert detector
covered only part of the shapes. The maintainer chose one question for the
gate: did the branch's content land.

- Find the newest first-parent baseline commit after the merge-base that holds
  the branch's net change (merge-tree in a scratch bare repo with no config, so
  no merge driver is defined or run and no objects are written), then check
  each candidate's added lines against it instead of exiting 0. A dropped
  commit that restores a merge-base value is reported again.
- Skip a line only when the branch head's version of the touched files lacks
  it (cat-file), replacing the net-diff filter that rename detection, external
  diff drivers and color settings could empty.
- Read patches and contents with plumbing and NUL-separated paths; read each
  touched path once (PR 408: 3 s).
- Walk newest-first with an is-ancestor stop instead of --ancestry-path, and
  drop the revert check: reverts after landing are out of scope by design.
- Fixtures 11-27 cover every round-2 shape; docs, CHANGELOG and manifests
  describe the landing semantics.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 3 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 0 HIGH;4 blocking(MEDIUM,其中兩項是本 PR 帶進來的回歸),10 in-scope fix,8 docs / follow-up(38 則原始 finding 去重後 22 項)

Scope coverage

PR refs: #399(PR body 另提及 #402、#408,兩者是 PR,不在驗證範圍)
Verified scope: #399 · freshness gate: frozen 9f78f83 = PR head(第 3 輪,maintainer 授權超過 unattended 兩輪上限)· CI(ubuntu)pass


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage:A 語意(「分支的內容有沒有落地」)的主幹成立。issue repro rc=0、PR #408 rc=0(約 3 秒,舊版約 6 秒);本 repo 99 個已合併 PR 以各自的 merge commit 當 baseline 全部 rc=0(舊版誤報 43 個);未合併的 #402 rc=3。第 2 輪的四類漏報都已修正並有 fixture。剩下的問題在落地 commit 的選法與幾條讀取路徑。

# Severity Finding Source Action
1 MEDIUM **落地 commit 選的是「最新一個包含淨改動的 commit」,不是落地的那一個。**分支內把某行改回 merge-base 原值(例如分支內 git revert),squash 後 main 又改那一行:那個後續 commit 仍算包含淨改動而被選中,改回原值的 commit 被誤報(squash 後 rc=0,加上後續 commit 變 rc=3)。違反 A,也與 README/SKILL 的「收尾 commit 改到同一行不影響」矛盾(check-merge-completeness.sh:134) codex+requirements Blocking
2 MEDIUM **submodule bump 沒落地也回 clean(回歸)。**gitlink 不是 blob,cat-file blob 失敗,head 端內容為空,+Subproject commit … 被當成分支自己改掉的行略過。舊版 rc=3、新版 rc=0(:166) logic+regression Blocking
3 MEDIUM **路徑去重是平方複雜度(回歸)。**搬 2000 個檔案的分支:舊版 26 秒、新版 bash 3.2 下 162 秒;單一 commit 超過約 1.3 萬個路徑時 cat "${tf[@]}" 會 E2BIG,兩邊內容都空,真 orphan 回 clean(:157、:175) regression+security Blocking
4 MEDIUM **找不到落地 commit 就全部改對 tip。**分支只要有一個真 orphan,main 落地後改過的行全部回到清單,和真 orphan 混在一起;照「abort + land」cherry-pick 那些 commit 會蓋掉 main 的刻意修改。建議找「已落地的最長前綴」 DA Blocking
5 MEDIUM -diff/binary attribute、diff.<drv>.binary、含 NUL 的檔案仍讓 patch 沒有 + 行,真 orphan 回 clean。既有問題,但第 3 輪宣稱已關掉「藏行」這一類。修法:diff-tree 加 --text 並改用不受 binary 影響的擷取(:177) logic+security+DA In-scope fix
6 MEDIUM 待貼的 correction-399-1(note 6283603692 ### Details)仍寫「找到包含就 exit 0」,正是第 3 輪推翻的規則 DA In-scope fix
7 LOW 錯誤一律變成「不是 orphan」:awk 失敗、head blob 讀不到、diff-tree 或 cherry 失敗都回 0;merge-tree 出錯(例如 shallow clone 缺 parent)被當成衝突,note 說「沒有 commit 包含」不實 logic+requirements In-scope fix
8 LOW SHA-256 repo:暫時 repo 一律是 SHA-1,落地步驟永遠跳過,note 卻說 git 版本太舊 logic+regression In-scope fix
9 LOW 真 orphan 時落地步驟一定跑滿 200 次 merge-tree:比舊版慢約 40 倍(本 repo 0.16→7 秒)。建議只試動過分支路徑的 commit regression In-scope fix
10 LOW 分支淨改動為空時,每個 commit 都「包含」,tip 被當成落地 commit,note 不實 logic In-scope fix
11 LOW git log 會照 log.showSignature 執行使用者的 gpg 程式,驗章輸出混進落地那一行;commit subject(PR 標題)原樣輸出,控制字元可以偽造一行「all clear」給 idd-close 讀 security In-scope fix
12 LOW 暫時 repo 對 config 是隔離的,對 attributes 不是:XDG attributes、系統 gitattributes、GIT_ATTR_SOURCE 仍可設 merge=union,讓衝突變成 C 的 tree(假的落地 commit);註解說「不會」過頭 security In-scope fix
13 LOW 有實際後果卻沒有 fixture 的保護:清除 GIT_COMMON_DIR(不清會把使用者 repo 改成 core.bare=true)、GIT_OBJECT_DIRECTORY、GIT_CONFIG_PARAMETERS;head 端的檔案間換行;--first-parent(side branch 上被 -s ours 丟掉的 commit) security+DA In-scope fix
14 LOW 開頭是 ++ 的新增行被當成 +++ 檔頭濾掉(既有) logic In-scope fix
15 LOW 測試以 PATH 上的 bash 跑 helper:「/bin/bash 3.2 全過」實際是 bash 5 跑 helper(強制 3.2 重跑仍 48/48) regression In-scope fix
16 LOW 未 rebase 的 squash 若 main 同時改了分支改回原值的那一行,仍會報;foxtrot merge(main tip 的 first parent 是本地線)時 first-parent 走訪看不到 squash,誤報且 note 不實 requirements+DA Docs(列為已知限制)
17 LOW idd-close 的 rc=4 訊息只說「branch SHA 無法解析」,但 rc=4 現在也代表沒有暫存目錄 regression+logic In-scope fix
18 LOW PR #418 本文仍描述第 1 輪的演算法;PR #402 標題仍寫「squash 檢查」 DA In-scope fix
19 LOW correction-399-2(Risks)把不完整的清單寫成完整清單,效能只引用最好的情況 DA In-scope fix
20 INFO test.sh 檔頭仍寫「3 fixtures ARE the spec」 requirements+regression Docs
21 INFO #402 的 59b2901 若在 rebase 時保留舊版 helper,會把本 PR 的修正蓋回去;目前只有文字提醒,沒有機械保護 regression Follow-up(rebase #402 時核對)
22 INFO 確認通過:沒有任何 merge driver 被執行、repo 沒有被寫入、暫時目錄無逃逸、無 option/path injection、無 prompt injection;空白行略過(M17)因檔案間換行而多餘,可接受 security+DA —

Scope Check

所有改動仍在 #399 範圍內;#402 的修改是 maintainer 指示。

修正方向(第 4 輪)

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315089709 ### Risks 宣稱包含檢查不會比現在更容易漏報 /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
2 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到 /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'
3 comment:6283603692 ### Details 建議「找到包含就 exit 0」,第 3 輪已推翻(見 #6) /idd-edit comment:6283603692 --correct --section '### Details' --body-file '$HOME/.cache/idd/corrections/correction-399-1.md'

替換文字在第 4 輪定案後依最終實作改寫;本輪只列不做。

…mmit (#399)

Verify round 3 (no HIGH, four blocking MEDIUM) showed the newest commit that
still holds the branch's net change is not where it landed, that one genuine
orphan sent every line back to the tip, that submodule bumps and binary-marked
files hid lines, and that path dedup was quadratic.

- Landing commit: the oldest commit of the first run (walking back from the
  tip) that holds the net change; only first-parent commits touching the
  branch's paths are tried, then the merge-base; 200 merge-tree calls in all.
- Partial merges: when the whole branch never landed, its earlier commits are
  tried newest first; commits up to the one that landed are checked there.
- Reading: one cat-file --batch-check per side, each distinct blob read once;
  gitlinks compare as "Subproject commit <oid>"; diff-tree --text and hunk-aware
  awk extraction; the scratch repo gets the shallow file and object format, and
  no attributes either.
- Errors exit 4 with the reason, which idd-close prints; subjects are printed
  without signature checks or control bytes and marked as repository text.
- Fixtures 27f-j, 28-40 (78 asserts), docs, CHANGELOG, manifests updated.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 4 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 0 HIGH;3 blocking(MEDIUM,其中兩項是較舊 git 或特殊路徑上的回歸),14 in-scope fix,6 docs / accepted(36 則原始 finding 去重後 23 項)

Scope coverage

PR refs: #399 · freshness gate: frozen 28ae3f6 = PR head(第 4 輪)· CI(ubuntu)pass


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage:issue repro、PR #408(2.6 秒,舊版約 5 秒)、78/78(bash 5 與 bash 3.2)、CI 皆通過;本 repo 99 個已合併 PR 中新版只標出 1 個(PR #36,手動解衝突的 squash,已列為限制),舊版標出的 48 個裡 47 個經 patch-id 獨立核對確實已落地。第 3 輪的 #2–#18 大多已到位,以下是剩下的問題。

# Severity Finding Source Action
1 MEDIUM git 2.38–2.50 上 submodule bump 沒落地回 clean(回歸)。cat-file --batch-check 在 2.51 之前對 gitlink 輸出 <rev:path> missing,被 "$rev:$f missing" 那一支歸成 none,後面的 ls-tree 後備永遠到不了;Ubuntu 24.04、Debian 12 的 git 都在這個範圍(:269) codex+logic+regression+DA Blocking
2 MEDIUM 路徑結尾是 CR 時查到別的檔案(回歸)。cat-file --batch-check 讀輸入會去掉行尾 CR,x.txt\r 被當成 x.txt 查,真 orphan 回 clean;rev-list --stdin 的 pathspec 也一樣(:239、:164) security+DA Blocking
3 MEDIUM **選到的仍可能不是落地的 commit。**取的是「從 tip 往回第一段包含區間的最舊 commit」;main 落地後打斷包含狀態又恢復時,第一段區間從落地之後才開始,改回原值的行被誤報,note 也指錯落地 commit(違反 A)(:186) requirements+DA Blocking
4 MEDIUM 部分合併的規則沒有被測試釘住:use_landing 不看 PREFIX_TIP、祖先判斷方向寫反、拿掉區間中斷,三個 mutation 都 78/78 照過,各有可區分的歷史 DA In-scope fix
5 LOW listed 的 blob 讀不到(partial clone、損毀)時也被歸成 none,真 orphan 回 clean;應回 4 logic In-scope fix(#1 的修法一併處理)
6 LOW 數個錯誤路徑仍會靜默變成「不是 orphan」:建索引的 awk、sort -u 管線、awk 的 getline 回 -1、`diff-tree tr` 的狀態 logic
7 LOW 前綴搜尋失敗或 budget 用完時 note 仍是第一次的原因;merge-base --is-ancestor 出錯被當成「不是後代」 logic In-scope fix
8 LOW budget 在包含區間中途用完時,指名的落地 commit 是錯的 logic In-scope fix(#3 的修法一併處理)
9 LOW submodule.<name>.ignore=all(config 或 .gitmodules)讓 diff-tree 不輸出 gitlink,沒落地的 bump 回 clean(既有);兩個 diff-tree 加 --ignore-submodules=none 即可 DA In-scope fix
10 LOW stacked branch 先 squash 進 main、之後才以 merge commit 併入 base branch 時,squash 不是 merge-base 的後代,走訪在那裡停下,#408 的誤報回來,note 不實 DA In-scope fix(#3 的修法一併處理)
11 LOW 前綴搜尋的輪數不受 merge-tree 上限約束(walk 在第一次 merge-tree 前就停下時);300 commit 的 stacked 分支 18.9 秒(舊 11.7 秒);長分支的成本也與 CHANGELOG 的「最多約 5 秒」不符 DA+regression In-scope fix
12 LOW subject 只去掉 C0 與 DEL:UTF-8 的 C1 控制字元與 bidi override 仍會通過;subject 直接嵌在 helper 自己的句子裡,沒有標示是 repo 文字 security In-scope fix
13 LOW idd-close 的 rc=4 note 取 stderr 前三行,git 自己的錯誤訊息(可能含 repo 路徑文字與本機路徑)會排在 helper 的原因前面;` exit 4` 的早期退出沒有寫原因
14 LOW 「git 指令失敗回 4」說過頭:落地步驟的失敗是退回 tip 並只在 rc=3 時附註 requirements Docs
15 LOW SKILL.md 的限制清單漏了 git < 2.38 與超過 merge-tree 上限;fixture 分組寫錯;test.sh 的 #399 區段標題仍說「newest」、exit 4 的說明過時 requirements+regression Docs
16 LOW macOS 的 awk 會在 NUL 處截斷一行,只改了 NUL 之後位元組的 binary 修改會被放過(舊版同樣放過,DA 確認非回歸);文件「NUL 藏不住新增行」說過頭 logic+DA Docs(列為限制)
17 LOW 單一 commit 的改名沒落地時新舊版都回 0(內容在 target 的舊路徑裡找得到);文件把這說成「巧合」 DA Docs(列為限制)
18 LOW partial clone 中,讀取會逐一按需 fetch 缺的 blob(使用者自己的 git 行為) security Accepted(記錄)
19 INFO 「git pull merge」那條限制寫得比實際寬:實際只發生在第二個 parent 那側於 squash 後、pull 前又改了分支的行 logic Docs
20 INFO 暫時 repo 透過 alternates 會更新使用者 object 檔的 mtime(不新增 object);core.fsmonitor 在主 repo 的 diff-tree 時會被呼叫 security Docs/In-scope(core.fsmonitor=false)
21 INFO 更正稿尚未套用:note 6283603692、Diagnosis 的 Risks、dashboard 仍與 A 矛盾;#402 本文驗證段仍把「merge 2 個」算進去 requirements+DA In-scope fix(dashboard、#402)/Proposed Corrections
22 INFO 無法用環境變數改系統 attributes 檔的位置(M19c)、空白行略過(M22)、路徑過濾只影響效能(M26):存活可接受 DA Accepted
23 INFO 確認通過:沒有任何 driver、textconv、filter、hook、gpg、pager 被執行;無 prompt injection security+DA —

Scope Check

所有改動仍在 #399 範圍內。

修正方向(第 5 輪)

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315089709 ### Risks 宣稱包含檢查不會比現在更容易漏報 /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
2 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到 /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'
3 comment:6283603692 ### Details 建議「找到包含就 exit 0」 /idd-edit comment:6283603692 --correct --section '### Details' --body-file '$HOME/.cache/idd/corrections/correction-399-1.md'

替換文字在第 5 輪定案後依最終實作改寫;本輪只列不做。

…399)

Verify round 4 (no HIGH, three blocking MEDIUM): on git 2.38-2.50 a gitlink
was classified absent because cat-file --batch-check reported it as missing;
a path ending in CR was looked up as its CR-less sibling; and the oldest commit
of the newest holding run was not always where the branch landed.

- Landing: walk the baseline's first-parent history forward from the
  merge-base (merge-base, first descendant, then path-touching descendants,
  descendants taken from rev-list --ancestry-path) and take the earliest commit
  that holds the net change. Fixes stacked branches squashed before their base
  was merged, and a hold broken and restored after landing.
- Partial merges: try at most the newest 20 earlier commits.
- Each side's tree comes from one ls-tree -r -z matched by exact path; a listed
  blob that cannot be read exits 4; CR or LF paths exit 4; diff-tree runs with
  --ignore-submodules=none.
- awk read errors exit 4; subjects are quoted and stripped of C1 controls and
  bidi overrides; idd-close keeps only the helper's own reason and the ref on
  rc=4; core.fsmonitor is off for the helper's reads.
- Fixtures 12 (reworked), 41-47b; docs, CHANGELOG, test header updated.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 5 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 1 HIGH(兩個 reviewer 各自回報)、5 MEDIUM blocking、12 in-scope fix、若干 docs/INFO(30 則原始 finding 去重後 22 項)

Scope coverage

PR refs: #399 · freshness gate: frozen 13e1b61 = PR head(第 5 輪)· CI(ubuntu)pass


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage:issue repro、PR #408(約 2.6 秒,舊版約 5 秒,39 個誤報消失)、91/91(bash 5 與 bash 3.2)、舊 git 模擬、CI 皆通過;第 4 輪的程式面修正(ls-tree、CR 路徑、往前走的落地 commit、前綴、讀不到的 blob、ignore=all、stacked、subject)都有對應程式與 fixture。

# Severity Finding Source Action
1 HIGH **macOS 的 tr 在 UTF-8 locale 下遇到非 UTF-8 位元組就停止輸出(回歸)。**tree 裡只要有一個 Latin-1 檔名,排在它後面的路徑在 head 那側都讀成不存在,真 orphan 被判 clean(新 0、舊 3);管線狀態是 awk 的,失敗被吞掉。ubuntu 的 GNU tr 不受影響,所以 CI 測不到 logic+DA Blocking
2 MEDIUM 分支把某行改回原值、前一個 commit 被 cherry-pick 進 main,「改回」那個沒進去時被判 clean(回歸)。「最早包含淨改動的 commit」可能早於 PR 真正進 main 的時間:淨改動為空時就是 merge-base,或是一個先進來的 hotfix DA Blocking
3 MEDIUM tree 任何地方有 CR 檔名(macOS 的 Icon\r)就整支回 4,連真 orphan 都不報(舊版會報),原因訊息也看不出是 CR;awk 本來就保留行內的 CR,只有 LF 會切斷紀錄 regression+DA+requirements+logic Blocking
4 MEDIUM --repo 給子目錄時,rev-list --stdin 以子目錄解讀根目錄的相對路徑,#399 的誤報回來(idd-close 傳的是 ${CWD:-$PWD}) security+DA Blocking
5 MEDIUM clean() 只掃一遍:刪掉內層序列後兩側拼回 C1 控制字元或 bidi override;subject 裡的 " 可以結束引號,接一段看起來像 helper 自己說的話 security Blocking
6 MEDIUM plugin README 的 v3.5.1 那列仍寫「從 baseline 頂端往回」 requirements+regression Docs
7 LOW 前綴最多試 20 個沒有寫進 SKILL.md 與各個「退回 tip」清單;已落地的前綴後面若有超過 20 個沒落地的 commit,已落地的會被列出 requirements+logic Docs
8 LOW 自我抵銷的前綴「落地」在 merge-base,note 誤稱有一部分已落地,也讓搜尋停在那裡 logic In-scope fix
9 LOW use_landing 的 is-ancestor 出錯被當成「不是祖先」;CANDIDATES 的 awk 沒有檢查狀態 requirements+logic In-scope fix
10 LOW 前綴搜尋每一輪都重建同樣的歷史清單;寬 tree 加上大量動到同一檔的 main 歷史時,最壞情況是數十秒(花在 200 次 merge-tree) regression+DA In-scope fix+Docs
11 LOW 存活的 mutation:PREFIX_TRIES=1、拿掉 --full-tree、拿掉 --literal-pathspecs、前綴出錯時不中斷 DA In-scope fix(補 fixture 53、50)
12 LOW Step 1.55 用沒加大括號的 $CLAUDE_PLUGIN_ROOT 呼叫 helper,TaskCreate 描述寫的是相對路徑(會解析到被稽核的 repo);#401 追蹤,但本 PR 改到這兩行 security In-scope fix(改用 ${CLAUDE_PLUGIN_ROOT})
13 LOW 終端機下 merge-base --is-ancestor 會啟動使用者設定的 pager security In-scope fix(--no-pager)
14 LOW test.sh 標題寫 8–48、exit 4 的說明過寬、「after the merge-base」的措辭 requirements+regression Docs
15 LOW Codex:diff.submodule 可能改變 submodule 的 patch 格式;在 git 2.55 實測不重現(plumbing 不讀它) codex In-scope(加 --submodule=short 作為保險)
16 LOW dashboard 未同步;Diagnosis 的 Strategy 與 Implementation Plan 仍寫「任一 C 包含就 exit 0」,不在 Proposed Corrections 裡 DA In-scope fix
17 INFO CHANGELOG 的 3.5.1 日期要在 merge 時更新(照 3.5.0 的做法) requirements Merge 時處理
18 INFO 確認通過:沒有任何 driver、textconv、filter、hook、gpg、pager(除 #13)被執行;pathspec magic 形狀的檔名被照字面處理;99 個已合併 PR 新版只標出 1 個(手動解衝突的 squash);無 prompt injection security+regression+DA —

Scope Check

所有改動仍在 #399 範圍內。

修正方向(第 6 輪)

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315089709 ### Risks 宣稱包含檢查不會比現在更容易漏報 /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
2 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到 /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'
3 comment:6283603692 ### Details 建議「找到包含就 exit 0」 /idd-edit comment:6283603692 --correct --section '### Details' --body-file '$HOME/.cache/idd/corrections/correction-399-1.md'

Diagnosis 的 Strategy 一併在第 6 輪結束後列入。本輪只列不做。

…ise merges (#399)

Verify round 5 (one HIGH): in a UTF-8 locale macOS tr stopped at the first
non-UTF-8 byte of a tree listing, so every later path looked absent from the
branch head and a real orphan passed; the pipeline status was awk's.

- export LC_ALL=C and set -o pipefail; run from the repository's top level
  (rev-list --stdin reads pathspecs relative to the current directory).
- Refuse only LF in paths; CR stays inside awk's line and matches exactly, so a
  macOS Icon<CR> no longer disables the gate.
- When git cherry finds a branch commit's patch in the baseline after the
  landing commit, the branch arrived piece by piece: check against the tip.
- A prefix that undid itself is not a landed prefix; use_landing errors and
  awk/cherry parse failures exit 4; history lists reused per merge-base.
- clean() replaces control, invisible and bidi characters with "?" and escapes
  quotes; --no-pager; --submodule=short; braced ${CLAUDE_PLUGIN_ROOT} in
  idd-close Step 1.55.
- Fixtures 41 (now flagged), 48-54; docs, CHANGELOG, README row, test header.

Refs #399
…ts own text (#399)

Verify round 6 on PR #418 found no HIGH findings; this round handles its
MEDIUM ones.

- Deletions: when no commit of the target holds the branch's net change,
  every line the branch adds is there, and a file or non-blank line the
  net change deletes is still at the tip, the tip-side commits that delete
  something are listed. A squash whose conflicts were resolved by hand
  also holds no net change, but its deletions are made, so nothing is
  listed (fixtures 55, 55b, 56, 61; the merged-PR scan is unchanged).
- The copy rule now applies only when the whole branch has no net change,
  so a squash equal to one branch commit no longer trips it (fixture 57).
- Output: refs are printed in double quotes, the helper's own sentences
  are not escaped, and apostrophes in repository text are left as they
  are (fixtures 1, 62).
- Reading: the tree listing tolerates a newline in a path elsewhere in the
  tree, and a failing filter in a pipeline now exits 4 (fixtures 58, 59).
- Prefix search: the 20-commit limit is pinned on both sides (fixtures
  60a, 60b).

Suite 121/121 under bash 5 and bash 3.2; 105/105 with git 2.38;
run-all-tests 63 suites, 0 failed; 99 merged PRs give the same result as
round 6.

Refs #399
Verify round 6 on PR #418 asked that the untested defenses be stated;
the exit-4 wording now says the newline refusal is about a path a checked
commit touches.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 6 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 0 HIGH;4 項 MEDIUM 被判 blocking,其中一項由 DA 反駁為決策 A 的設計結果;另有 16 項 LOW、6 項 INFO。原始 35 則 finding 去重後共 26 項。

Scope coverage

PR refs: #399 · freshness gate: frozen d57f43a = PR head(第 6 輪)· CI(ubuntu)pass


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage(requirements 與 regression 兩個 reviewer 各自實測):

# Severity Finding Source Action
1 MEDIUM 第 6 輪造成的回歸:每個印出的 subject 結尾都多一個 ?。 clean() 從刪除控制字元改成換成 ?,git log --format=%s 結尾的換行也因此變成 ?。文件說 ? 代表被換掉的控制字元,所以每個 PR 標題看起來都像藏了控制字元。fixture 只做子字串比對,沒抓到 requirements+logic+security+regression Blocking
2 MEDIUM idd-close 用 zsh 的 echo 印 helper 的輸出,\\ 被還原成 \,subject 可以結束自己的引號,後面接上一段像指示的文字 security Blocking
3 MEDIUM **淨改動只剩刪除時,從未落地的分支(或落地前綴之後的尾段)回 0,main 回 3(回歸)。**例如先改檔再刪掉、加 debug 再拿掉並刪死碼。「略過 head 已經沒有的行」在沒有落地 commit 時也照樣套用 DA Blocking
4 MEDIUM revert 之後重新落地、再部分合併:別的 PR 曾經帶進同樣的內容,後來被 revert,這個 commit 被當成落地 commit,真 orphan 回 0 logic DA 反駁(#5):分支的內容確實在 main 出現過,在決策 A 之下屬於範圍外;寫進限制
5 LOW 複本規則在「整個分支 squash、revert、再 cherry-pick 一部分回來」時誤判為分批進來,說明句也不對(分支在 squash 時已完整落地) DA In-scope fix
6 LOW clean() 漏掉 U+061C、U+2060–206F、U+00AD、U+180E、Hangul filler、variation selector(U+FE00–FE0F、U+E0100–E01EF);單獨的 C1 位元組也沒處理 requirements+logic+security In-scope fix+Docs
7 LOW 單引號包住的 ref 名稱可以結束自己的引號;idd-close 的 rc=4 note 印出的 BRANCH_REF 沒經過清理 security In-scope fix
8 LOW 沒有 fixture 覆蓋 pipefail(M38 存活)。git cherry 那條管線並沒有先寫進檔案,拿掉 pipefail 會讓真 orphan 回 0;CHERRY_DASH 沒檢查 awk 狀態 logic+regression+DA In-scope fix(補 fixture)
9 LOW orphan_p 把 awk 的 exit 1 當成「已落地」,awk 崩潰時(busybox)commit 會被放行 logic In-scope fix
10 LOW classify 兩種失敗共用 return 2,I/O 失敗被說成「路徑含換行」 logic+regression In-scope fix
11 LOW tree 裡任何地方有含換行的路徑,整支就回 4,連無關的檔案都會讓真 orphan 變成 note security In-scope fix
12 LOW 複本檢查失敗時回 4,文件卻說落地步驟的失敗會退回 tip regression In-scope fix
13 LOW 第 5 輪點名的 mutation 仍存活:--literal-pathspecs、--full-tree、前綴出錯時的 break、PREFIX_TRIES 上限 requirements In-scope fix+Docs
14 LOW 部分合併時,落地前綴之後的 commit 都對 tip 檢查;main 之後改過的已落地 commit 會被列出,文件沒寫 requirements Docs
15 LOW partial clone 會為了缺的 blob 去 fetch(執行使用者的 transport、寫進 promisor pack) security Docs
16 LOW CHANGELOG 沒說:改用 ${CLAUDE_PLUGIN_ROOT} 之後,gate 在 idd-close 裡才真的會跑,使用者會開始看到 rc=3 的詢問 regression Docs
17 LOW TaskCreate 範例的 description 裡有沒跳脫的雙引號 codex+regression Docs
18 LOW issue body 與 dashboard 仍寫「等待 A/B 決定」;Diagnosis 的 Strategy 與 Implementation Plan 描述的是舊設計 requirements verify 通過後同步;更正稿只列不做
19 LOW 更正稿 2、4、5 與程式不符 DA In-scope fix
20 INFO fixture 48 在 macOS(APFS)上建立檔案失敗,但仍能區分修正前後 requirements In-scope fix
21 INFO 碰巧相同的 patch 也會觸發複本規則 logic —
22 INFO Step 1.55 沒有 --retroactive 那段的絕對路徑檢查 security —
23 INFO CHANGELOG 3.5.1 的日期要在 merge 時更新 requirements+regression Merge 時處理
24–26 INFO 確認通過:issue repro、PR #408、99 個 PR、bash 3.2、舊 git、CI、決策 A 的說法一致;沒有任何外部程式被執行;無 prompt injection requirements+regression+DA —

Scope Check

所有改動仍在 #399 範圍內。

第 7 輪的處理(d4b4cf8、90b9efc)

第 7 輪的檢查:

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6283603692 ### Details 建議「找到包含就 exit 0」 /idd-edit comment:6283603692 --correct --section '### Details' --body-file '$HOME/.cache/idd/corrections/correction-399-1.md'
2 comment:6315089709 ### Risks 宣稱包含檢查不會比現在更容易漏報 /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
3 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到 /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'
4 comment:6315089709 ### Strategy 「任一 C 包含就 exit 0」 /idd-edit comment:6315089709 --correct --section '### Strategy' --body-file '$HOME/.cache/idd/corrections/correction-399-4.md'
5 comment:6315114750 (整則) Implementation Plan 描述的是舊設計 /idd-edit comment:6315114750 --correct --body-file '$HOME/.cache/idd/corrections/correction-399-5.md'

本輪只列出,沒有套用。

Verify round 7 on PR #418 found that the round-7 deletion rule, which
looked for evidence in the branch's net change, did not run when the
landing step fell back to the tip (git < 2.38, the 200-call budget, a
merge-tree error), and that its count test listed hand-resolved squashes
that had made every deletion (a moved import, a closing brace main also
had).

- Deletions are now checked like added lines, for every candidate at its
  own side: a deleted non-blank line the branch head no longer has must be
  gone from where the branch landed (the tip when it did not), and a
  deleted path the head no longer has must be gone too. Deleted lines the
  head still has in those files (moves, shared lines) are skipped. The
  net-change rule, its listing of every deleting commit and its note are
  removed (fixtures 63-68, 71-73, 75; 55, 55b, 56 now assert on every git).
- A missing blob in a partial clone is not fetched into the repository:
  GIT_NO_LAZY_FETCH=1, exit 4 on git >= 2.44 (fixture 74).
- has_lf and has_crlf report a read failure (fixture 69); the final sed
  pipeline is gone.
- clean() replaces more invisible and format characters and escapes $
  and backticks (fixtures 70, 70b); idd-close filters the default-branch
  name in its rc=3 header.
- Docs: the blind-spot list is a closed enumeration (moved or duplicated
  lines, mode-only and blank-line deletions, files main renamed, merge
  commit content, any-form restore); stacked branches merged after their
  base are on the fall-back list; README's prefix bound and CHANGELOG's
  exit-4 wording are exact; test.sh lists every unpinned defense.

Suite 141/141 under bash 5 and bash 3.2; 131/131 with git 2.38;
run-all-tests 63 suites, 0 failed; PR #408 rc=0 in 3.4 s; 99 merged PRs
give the same result as round 7 against their merge commits and against
the current main.

Refs #399
@kiki830621

kiki830621 commented Oct 24, 2026 •

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 7 輪)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus

Aggregate

FAIL — 0 HIGH,6 項 MEDIUM:5 項來自第 7 輪新加的刪除規則,1 項是文件。另有 18 項 LOW、14 項 INFO。原始 38 則 finding,下表合併同一件事後共 26 項。

Scope coverage

PR refs: #399 · freshness gate: frozen 90b9efc = PR head(第 7 輪)· CI(ubuntu)pass


#399 — idd-close Step 1.55 flags squash-merged commits whose lines a later branch commit rewrote

Requirements coverage:以下各項由 reviewer 各自實測:

Codex 盲審沒有找到必須修的缺陷。

# Severity Finding Source Action
1 MEDIUM **落地步驟退回 tip 時,刪除規則不會執行。**情況包括 git < 2.38、200 次 merge-tree 用完、merge-tree 出錯。這時從未落地、只刪不加的分支安靜地回 0,舊 helper 會回 3。fixture 55、55b、56 在舊 git 下沒有斷言,所以看不到 logic+regression+requirements Blocking
2 MEDIUM **證據判準把搬動的行當成沒落地的刪除。**例如排序 import:已落地的手動解衝突 squash 會被列出,而且所有有刪除的 commit 都會列 logic Blocking
3 MEDIUM **證據判準只數 tip 上同一行出現幾次。**main 在 squash 前,或在落地之後,加了一個跟被刪行相同的 },已落地的分支就從 0 變成 3。這和文件說的「手動解衝突的 squash 不會列出」矛盾 requirements+DA Blocking
4 MEDIUM 盲點清單的「(不是同一個 patch)」限定已經過時。第 7 輪縮小了複本規則,同一個 patch 在落地 commit 之後才進來也一樣看不到 regression+logic Docs
5 LOW 列出的 commit 跟證據無關:55b 的 c2 只是拿掉分支自己加的 debug 行,也被列出。沒有 commit 有刪除時會全部列出;binary 檔的 numstat - 被當成刪除 logic In-scope fix
6 LOW 證據不管已落地的前綴:main 加回前綴已刪掉的行時,會列出無關的 tip 側 commit logic In-scope fix
7 LOW 前綴落地時,刪除的說明句沒有印出來 requirements+logic In-scope fix
8 LOW 「分支其他部分有落地時的純刪除」這個盲點已過時。真正漏掉的是刪除跟新增一起被丟下時,只會列出新增的那個 requirements In-scope fix
9 LOW 「沒落地的單一 commit 改名一定抓不到」已過時 DA Docs
10 LOW 刪除證據只看同一路徑和行數。main 改名、重複的行、只改 mode、只刪空白行,都會回 0,文件沒有列成盲點 DA Docs
11 LOW deletion_missing 把讀取錯誤當成「不存在」;刪掉的 gitlink 永遠找不到證據 logic In-scope fix
12 LOW 收尾的 sed 管線、has_lf/has_crlf 沒檢查狀態,失敗會讓真 orphan 回 0 logic In-scope fix
13 LOW partial clone:讀取時 git 按需 fetch,會寫 pack 進使用者的 repo,也會執行 hook 與 transport security In-scope fix
14 LOW clean() 漏掉幾個不可見字元(U+034F、U+FFF9–FFFB、U+0600–0605 等),文件卻寫成一般性質 security In-scope fix+Docs
15 LOW idd-close rc=3 的標頭印出沒清理過的預設分支名 security In-scope fix
16 LOW 分支上 merge commit 解衝突寫進的內容沒被檢查,文件沒列 regression Docs
17 LOW test.sh 列的「沒有 fixture 釘住的防護」不完整,另有 12 個 mutant 存活 requirements+DA In-scope fix+Docs
18 LOW diff 以外的文字與程式不符:PR #418 本文、第 6 輪報告的 #3 與 #8、更正稿 2 DA In-scope fix
19 INFO README 寫前綴在「最新 20 個 commit 之內」,實際是 head 之前的 20 個 requirements Docs
20 INFO CHANGELOG 的 exit 4 寫得比程式寬;\001 只出現在程式裡 requirements Docs
21 INFO 複本規則的例子改成純刪除形式時仍回 0 logic 隨 #1–#3 的改法解決
22 INFO 單獨的 C1 位元組仍會印出(已記錄的限制) security —
23 INFO 雙引號包住的文字看起來像 shell 字串,但 $ 和反引號沒有跳脫 security In-scope fix
24 INFO subject 長度沒有上限 security 不處理:rc=3 的詢問照樣會跳出
25 INFO 照一般順序合併、base 之後又有修正的 stacked PR 會誤報(不是回歸),但沒列進退回 tip 的清單 DA Docs
26 INFO 確認通過:99 個 PR、#408、#36、partial clone、bash 3.2、舊 git、CI、manifest;沒有 prompt injection 全部 —

另外,issue body 與 dashboard 仍是做決策前的狀態(requirements,INFO)。這兩處等 verify 通過後再同步。

Scope Check

所有改動仍在 #399 範圍內。

第 8 輪的處理

第 8 輪的檢查(本機 commit 4e9babe,尚未推送):

  • 測試 141/141,bash 5 與 bash 3.2 都通過;舊 git 131/131。
  • run-all-tests 63 個 suite,0 失敗。
  • PR feat(idd-edit): --correct replaces wrong issue content in place, archive kept out of AI reach (#351) #408 3.4 秒回 0。
  • 99 個已合併 PR:以各自的 merge commit 和以目前的 main 為 baseline,結果都和第 7 輪相同。
  • 新斷言在第 7 輪的 helper 上有 13 項失敗(RED)。
  • 第 8 輪新邏輯的 mutation:
    • 抓到 9 個:刪除行規則、刪除行不看 head、刪除路徑規則、刪除路徑不看 head、lazy fetch、has_lf 錯誤、$、反引號、新的不可見字元。
    • 存活 2 個,已列進 test.sh:刪除行的空白略過(拿掉只會多報),以及「路徑不在索引裡」的防禦檢查(照目前的流程不會發生)。
    • 「刪除路徑清單讀取失敗」的 || skip 與 awk 讀不到檔案時回 4 等價。

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6283603692 ### Details 建議「找到包含就 exit 0」 /idd-edit comment:6283603692 --correct --section '### Details' --body-file '$HOME/.cache/idd/corrections/correction-399-1.md'
2 comment:6315089709 ### Risks 宣稱包含檢查不會比現在更容易漏報 /idd-edit comment:6315089709 --correct --section '### Risks' --body-file '$HOME/.cache/idd/corrections/correction-399-2.md'
3 comment:6315687653 ### Verification 說拿掉 || continue 觀察不到 /idd-edit comment:6315687653 --correct --section '### Verification' --body-file '$HOME/.cache/idd/corrections/correction-399-3.md'
4 comment:6315089709 ### Strategy 「任一 C 包含就 exit 0」 /idd-edit comment:6315089709 --correct --section '### Strategy' --body-file '$HOME/.cache/idd/corrections/correction-399-4.md'
5 comment:6315114750 (整則) Implementation Plan 描述的是舊設計 /idd-edit comment:6315114750 --correct --body-file '$HOME/.cache/idd/corrections/correction-399-5.md'

本輪只列出,沒有套用。

@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 8 輪,本機 commit 4e9babe,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。第一次執行時有 4 個 agent 遇到網路中斷(ENOTFOUND),整輪重跑。

Aggregate

FAIL — 0 HIGH;5 項 MEDIUM 說的是同一件事,另有 15 項 LOW、16 項 INFO(原始 36 則)。

#399

# Severity Finding Source Action
1 MEDIUM **第 8 輪造成的回歸:在落地 commit 上檢查刪除時,main 在落地前自己加的同一行或同一個路徑,被當成「刪除沒落地」。**最常見的情況是平行發版:x 從 1.2.0 升到 1.3.0,同時另一個 PR 把 y 升到 1.2.0。也包括共用的 fi、return 0、main 自己建的同名檔。第 7 輪與舊 helper 在這些情況都回 0。輸出的說明句「the lines above are not in it」也不對 requirements+logic+regression+DA Blocking
2 MEDIUM 封閉的盲點清單又漏了幾類:只新增空檔、只加空白行、只改檔尾換行;main 改掉或搬走被刪的那一行 logic+requirements+DA Blocking
3 LOW 前綴落地時,前綴內的 commit 仍對整個 branch head 判斷是否被取代,所以已落地的 commit 會被列出 logic In-scope
4 LOW main 在落地前把分支改的檔案改名,落地處用原路徑找不到,仍會列出(第 8 輪以前就有);兩份清單都沒寫 requirements+logic Docs
5 LOW GIT_NO_LAZY_FETCH 要 git 2.45 才支援,文件寫成 2.44 logic+security+DA Docs
6 LOW 第 8 輪新防護有 7 個 mutant 存活,第 7 輪報告寫「存活 2 個」,算錯了。刪除路徑清單的 ` skip` 和 awk 的讀取檢查互為理由,兩個同時拿掉時會安靜地回 0
7 LOW PR 本文、更正稿 1、2、第 7 輪報告「#1–#3 已處理」與程式不符 requirements+DA In-scope
8 INFO DA 以 438 個真實已合併 PR 實測(本 repo、Akashic-Library、safari-browser、psychquant-claude-plugins):第 8 輪沒有比第 7 輪多報任何一處,#1 的形狀在實際歷史裡還沒出現過 DA —
9 INFO Codex 沒找到必須修的缺陷;第 8 輪還沒在 Linux CI 上跑過 codex+regression —

處理方式

第 7、8 輪的 blocking 都是前一輪修法造成的,所以不再逐輪修補(maintainer 2026-10-25 決定)。改成:

  1. 先把「每個 commit 什麼時候算落地」寫成判定表。
  2. 用有界窮舉把 helper 和判定表逐一比對,對不上的修好,或歸進由程式判定的封閉偏差清單。
  3. 最後跑一輪 verify。

#1 的修法已經在本機(第 9 輪,未推送):落地處的刪除改成同時對照落地前 main 的版本與 merge-base。另外新增了「新增路徑」的規則。fixture 76–80 在第 8 輪上 RED、在第 9 輪上通過。

#399)

Rounds 9 and 10. SPEC.md now defines the right answer and the only allowed
deviations; bounded.py runs the helper on every history of a small world and
checks each answer against that definition.

- Round 9: deletions at the landing commit are counted, paths a commit adds
  or deletes are checked, the tip compares presence only.
- Round 10: a 6000-case sample of three-commit branches found 19 misses
  (exit 0 instead of 3) in round 9's count rule (p + h - m with t<h / t>h
  guards), which is wrong when the landing commit is the merge-base, when
  its first parent already holds the branch, and when only a prefix landed.
  The landing commit is now compared with the clean merge of the landed
  part onto main just before it (merge-tree in the scratch repository);
  when that merge conflicts, the tip is used and the note says why.
- bounded.py: the tip rule is computed on the model (D1+D2), over-listing
  at the landing commit with the right verdict is D3, the listed blind spot
  is B1; --dump and --reclassify. Length <= 2: 0 unexplained of 7058;
  two length-3 samples of 6000: 0 unexplained.
- Fixtures 76-88; mutation: 16 mutants of the new rules, all caught.
- CHANGELOG, README, idd-close Step 1.55 and the helper header follow.
- Round-8 LOWs: GIT_NO_LAZY_FETCH needs git 2.45 (git's own docs), not
  2.44; blank-line-only and final-newline-only commits added to the blind
  spots (both confirmed: a never-landed one exits 0).

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 10 輪,本機 commit fc4842e,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...fc4842e,含本機的第 8 輪(4e9babe)與第 9–10 輪(fc4842e)。

Aggregate

FAIL — 1 HIGH、4 MEDIUM(3 件事),另有 12 項 LOW、8 項 INFO(合併後 25 則)。

#399

# Severity Finding Source Action
1 HIGH **落地 commit 本身帶有分支以外的改動時,計數規則誤報,而且結論錯。**落地處比對的是「落地前的 main 加上已落地部分的乾淨 merge」,這只在落地 commit 剛好等於兩者時成立。release merge(PR 先 squash 進 develop,main 再 merge develop)、git pull 產生的 merge、stacked PR 的 base 又多了 commit、手改過的 squash,都會讓 }、return 0; 這類共用行的份數偏離期望值,把已落地的 commit 列出來。三種形狀都重現:舊 helper(origin/main)回 0,第 10 輪回 3。bounded.py 的 squash 永遠剛好等於「main 落地前 + S」,main 也沒有 merge,所以 0 個無法解釋對這一類沒有證明力 logic+DA Blocking
2 MEDIUM **B1 是相對 3.5.0 的回歸,不是原本就有的限制。**branch 沒有淨改動、多個 commit 被 squash 進 main、之後的 commit 又改回去:3.5.0 回 3 並列出沒落地的 commit,第 10 輪回 0(無輸出)。原因是整條 branch「落地」在 merge-base,不再找前綴;git cherry 對多 commit squash 不留 -,所以 CHERRY_DASH 的保護不會觸發。建議:落地在 merge-base 時照樣做前綴搜尋,只有沒有前綴落在更後面時才接受 merge-base requirements+regression+DA Blocking
3 MEDIUM **bounded.py 的 D1 取自 helper 自己的 trace,沒有從模型判定。**把整個落地步驟停用,bounded.py --max-len 2 --sample 2500 照樣回報 0 個無法解釋:落地步驟修好的 126 例退回去之後,全部被當成已知偏差吸收。SPEC 說「每一類都從模型判定」不成立,第 10 輪的「0 unexplained」也就不能證明落地步驟是對的;目前只有 fixtures 在證明它 DA Blocking
4 LOW CHANGELOG 說舊的 Step 1.55 呼叫「一定失敗」,與 #399 自己 2026-10-15 的觀察矛盾(那次 gate 回了 rc=3);未加大括號的 $CLAUDE_PLUGIN_ROOT 屬於 #401,但沒有連過去;同一個 idd-close/SKILL.md 還有 6 處(371、389、749、790、1224、1260);Implementation Complete 的「Sister Bugs Filed: (none surfaced)」因此不正確 requirements+regression+DA In-scope
5 LOW holds() 讀不到 tree 時被當成「不含」,不是錯誤,走訪會繼續、選到更後面的 commit logic In-scope
6 LOW classify() 沒檢查 helper 的結束碼是 0 或 3:exit 4 會被當成 tip 偏差吸收;minus 扣光預期清單後,預期結束碼沒有重算 logic In-scope
7 LOW 給 bounded.py 用的 trace hook 留在正式 helper 裡,會覆寫環境變數指定的任何路徑(跟隨 symlink),寫不進去時回 4,而 exit-4 清單沒列這一項 security+regression In-scope
8 LOW 分支自帶 .gitattributes 的 * merge=union 目前被隔離擋住(已實測),但沒有 fixture 釘住 security In-scope
9 LOW 刪除檢查是新功能,卻寫在 3.5.1 的 ### Fixed;在 tip(git < 2.38 時是全部)會產生 3.5.0 沒有的誤報 regression In-scope
10 LOW idd-close Step 1.55 的說明從 959 位元組變成 7,979,成為限制清單與 fixture 索引的第四份手抄本,沒有 drift guard regression In-scope
11 LOW --after 那一列沒有測到任何東西:長度 1 的 530 例全部在找落地 commit 之前就 exit 0;預設模式的長度 1 也完全沒碰到落地那一側 DA In-scope
12 LOW Results 表與 CHANGELOG 沒交代結論錯的數量:seed 399 的樣本裡仍有 581 例該報 3 卻回 0(tip 565、B1 16),都歸進已知類別;「19 例漏報 → 0」讀起來像漏報清零 DA In-scope
13 LOW test.sh 開頭「沒有 fixture 釘住的防線(only these)」不封閉:同時拿掉 --no-ext-diff --no-textconv --no-color、--no-pager、--no-renames 與 ISOGIT 的 -u GIT_WORK_TREE/GIT_INDEX_FILE/GIT_CONFIG,166 個 fixture 全過 DA In-scope
14 INFO plugin.json 的描述沒寫 #408 形狀需要 git 2.38+ requirements In-scope
15 INFO 決策 A 仍不在 #399 正文裡,Current Status 停在「等 A/B 決定」 requirements In-scope
16 INFO 第 8–10 輪未推送,CI 沒跑過;PR 本文、3.5.1 日期都是舊的 requirements+regression —
17 INFO clean() 的註解說輸出是「安全的 shell 字」,但 ! 原樣通過(互動 shell 會做 history expansion) security In-scope
18 INFO Codex 盲審沒找到必須修的缺陷;它只讀 diff、沒跑測試 codex —
19 INFO requirements reviewer 獨立重跑 test.sh(166/0)與 bounded.py --max-len 1(412/384/28/0),數字都對得上;DA 重跑 SPEC 的各列數字也對得上 requirements+DA —

處理方式

第 10 輪的 blocking 有兩項(#1、#3)都在小世界之外,正好是判定表方法本身的盲區:小世界造不出「落地 commit 帶有別的改動」,而分類器又借用了 helper 自己的 trace。所以照第 8 輪定下的做法,先改規格與檢查器,再改 helper:

  1. 擴大小世界:main 可以在同一個 commit 裡落地 S 並帶自己的修改,也可以用 merge commit 落地(模擬 release merge 與 git pull)。
  2. D1 改由模型判定:commit 在 tip 檢查、但模型不符合 D1 任一前提時,算無法解釋;classify() 只接受結束碼 0 或 3(idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6)。
  3. 修 B1 回歸(idd-all: direct-commit mode 加 passive warning (dirty tree / on default branch) #2):落地在 merge-base 時仍做前綴搜尋。
  4. 修 idd-all: 增加 HITL(attended)模式 — direct-commit + 允許 sub-skill AskUserQuestion #1:一個候選做法是落地處只對「已落地部分沒有淨改動的文字」計數(分支內改回原值、加了又刪的那些),其餘文字已由三方合併的落地判定涵蓋,不受落地 commit 裡其他改動影響。先在擴大後的小世界驗證,不直接採用。
  5. LOW 與 INFO 一併處理;Results 表加「結論錯」一欄,CHANGELOG 改寫。

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315687653 ### Sister Bugs Filed (mid-impl, v2.44.0+ #526) 不是 none surfaced:Step 1.55 修的未加大括號 $CLAUDE_PLUGIN_ROOT 屬於 #401,同檔還有 6 處,應記錄並連到 #401 /idd-edit comment:6315687653 --correct --section '### Sister Bugs Filed (mid-impl, v2.44.0+ #526)' --body-file '$HOME/.cache/idd/corrections/correction-399-6.md'

(#399 正文的 Current Status 由這次的 /idd-update 重建;決策 A 寫回正文另列在下一步。)

…mmit (#399)

Round 10 verify found that a landing commit carrying changes of its own (a
release merge, a git pull merge, a squash edited by hand) made the count rule
list commits that landed, and that bounded.py could neither build that shape
nor tell a broken landing step apart, since it took the sides from the
helper's own trace.

- At the landing commit a commit is reported when undoing it (merge-tree
  --merge-base, with the commit as the base) changes the branch head and
  changes nothing on the landing commit: its change is still in the head and
  not in place where the branch landed. Main's own edits sit elsewhere and can
  neither list nor hide it. Git 2.38 and 2.39 lack --merge-base and keep
  counting lines against the clean merge (SPEC.md D4, fixture 94).
- Two intermediate rules did not hold up in bounded.py: exempting every text
  in the landed part's net change missed dropped commits whose text the branch
  also changed elsewhere (39 cases in two length-3 samples); confirming count
  listings in place still listed pairs of dropped commits that cancel out and
  missed dropped restores when main's own edit evened the count (131 cases in
  own/merge length-3 samples).
- A branch with no net change lands at the merge-base; the prefix search now
  runs there too, so a dropped restore after a squash of the first commits is
  reported again, as in 3.5.0 (B1 regression).
- holds() reports an unreadable tree as an error; the test-only trace never
  overwrites a file and never changes the answer.
- bounded.py: --landing own|merge (main's own edit in the squash, a merge of a
  side branch carrying another PR); where each commit is checked, and the
  in-place answer at the landing commit, come from a model of the landing step;
  exit codes other than 0 and 3 are bugs; the expected exit code is recomputed
  after git cherry; a coverage line.
- Fixtures 89-94, and a merge-completeness-count suite that runs every fixture
  under a shim refusing merge-tree --merge-base, so the git 2.38/2.39 count stays
  pinned on newer git; run-all-tests reads a per-suite timeout file (both
  merge-completeness suites ask for 300 s); SPEC rows 24-26, D3 and D4 redefined, B1, results with wrong
  verdicts; CHANGELOG (deletions marked as a new check, #401 wording),
  plugin.json and marketplace (git 2.38+, 2.40), README; idd-close Step 1.55
  rationale shortened to pointers; the test.sh list of unpinned defenses is
  closed.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 11 輪,本機 commit ce6b541,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...ce6b541,含本機的第 8 輪(4e9babe)、第 9–10 輪(fc4842e)、第 11 輪(ce6b541)。

Aggregate

FAIL — 1 HIGH、8 MEDIUM,另有 11 項 LOW、若干 INFO(合併後 35 則)。

第 11 輪把落地處的檢查改成「逐 commit 在原位檢查」:在 head 上撤銷它會改變內容、在落地 commit 上撤銷它卻不變,才列為沒落地。第 10 輪的 HIGH(落地 commit 帶 main 自己的改動時誤報)在 git ≥ 2.40 上解決了,但新規則把 merge-tree 的衝突一律當成「已落地」,出現了方向相反、更不容易察覺的漏報。

#399

# Severity Finding Source Action
1 HIGH 衝突被當成「已落地」,漏掉沒落地的還原 commit;3.5.0 與第 10 輪都抓得到。 undo_noop 把 merge-tree 衝突回成「撤銷會改變內容」,land_orphan_p 就判為已落地。git 的三方合併對相鄰行的改動也判衝突,所以只要 main(落地前,或落地 commit 本身)在被丟掉那一行的上下一行有改動,例如並行 PR 在設定檔加一個 key,沒落地的還原就被當成落地,exit 0。同一行被改成第三個值、或 rename 偵測把路徑配到相近的檔案,也一樣。重現:fixture 19 的形狀,加上另一個 PR 在 LOG 正下方插入 TIMEOUT = 5 → 第 11 輪 rc=0,第 10 輪與 3.5.0 rc=3。helper 開頭「main 自己的改動不會列出或藏住」、CHANGELOG 的同一句、SPEC 第 16、26 列都因此不成立。單純把衝突改成退回計數,會讓 fixture 90 的誤報回來,需要更細的判斷 logic+DA Blocking
2 MEDIUM 一個 commit 動了好幾處時,有一處落地就整個算落地。 撤銷 c 會改變落地 commit,只表示它至少一個效果在那裡,不表示全部都在。重現(Codex):c1 改 x、y;c2 把 x 改回、新增 z;squash 收了 y、z 但 x 還是 c1 的值 → 撤銷 c2 會刪掉 z,判為落地,x 的還原沒被列出 codex Blocking
3 MEDIUM 原位檢查用不到 EXPECT,卻仍在 EXPECT 合併衝突時整批退回 tip。 手動解衝突、tree 與 head 完全相同的 squash,再加一個落地後的修改 → rc=3(正是 #408 的形狀)。把這個退回只留給計數路徑(git 2.38/2.39)就回 0 requirements Blocking
4 MEDIUM 小世界造不出衝突,0 unexplained 對上面三項沒有證明力。 slot 之間有固定分隔行(永遠不相鄰)、每個 commit 只改一個 unit、main 不碰分支動過的 unit。SPEC 的「At the landing commit every answer agrees」、D3「只多列、結論正確」、CHANGELOG 的「neither hides nor flags」都建立在這個世界上,卻沒寫明。是第 10 輪 #3 的同一個模式 logic Blocking
5 MEDIUM B1 回歸只修了一半。 前綴搜尋靠 holds(),同樣怕相鄰行衝突:fixture 92 的歷史改成 release merge、另一個 PR 改了下一行 → 找不到前綴,落在 merge-base,rc=0(3.5.0 是 3) logic Blocking
6 MEDIUM main 落地前自己做了分支中間某個 commit 的同一個改動時,誤報被改回的 commit(0 → 3),而且照 idd-close 的補救 cherry-pick 會悄悄改回 main。 例:分支 c1 把 version 改 1.1、c2 改回 1.0;main 自己先 release 成 1.1 再 squash → 列出 c2。只看 tree 分不出「c2 落地了」與「c2 沒落地」,這是內在的歧義,卻不在封閉偏差清單裡(3.5.0 也會報;第 10 輪不會) DA Blocking
7 MEDIUM git 2.38/2.39(Debian 12 是 2.39)沒有 --merge-base,退回第 10 輪的計數,第 10 輪 HIGH 原樣保留,fixture 94 與 count suite 把錯的答案釘成契約。建議改退回 tip(3.5.0 的 presence 對這些形狀回 0),或在 CHANGELOG 明寫這是相對 3.5.0 的回歸 logic 待決定
8 MEDIUM 第 8 輪加的 GIT_NO_LAZY_FETCH=1 讓 blobless partial clone 的 git cherry 直接失敗,整個 gate 變成 rc=4(3.5.0 照樣抓得到 orphan);rc=4 的訊息也沒說原因是 partial clone。這是政策改變,#399 沒有紀錄決定 regression 待決定
9 MEDIUM 第 8–11 輪都沒跑過 CI;第 11 輪改了共用的 test runner、新增一整套 count suite。高負載下(verify 同時在跑)count suite 跑了 324 秒,超過自己的 300 秒上限 regression In-scope
10 LOW B1 的殘留仍是相對 3.5.0 的回歸(3 → 0),文件卻寫成固有盲點;「什麼都沒落地時結論 exit 3 正確」對淨改動為空的分支不成立(它落在 merge-base,不是 tip) requirements In-scope
11 LOW 描述過時:idd-close SKILL.md:329 的 TaskCreate 仍寫 line-presence;test.sh 對 fixture 89–90 與 IN_PLACE_RC 的註解描述的是被淘汰的中間規則;bounded.py classify 的 docstring 還寫 D4 與「共用文字」;plugin.json/marketplace 寫「checks each commit's lines」;README 寫「都不會誤報或藏住」,與 2.38/2.39 的說明矛盾;PR 本文與 3.5.1 日期仍是第 7 輪 requirements+logic+regression In-scope
12 LOW CHANGELOG 沒提 D3(落地處仍可能多列),還寫「a commit undone later in the branch is not reported」 requirements In-scope
13 LOW 第 10 輪 #8 仍缺 fixture:tree 裡 commit 的 * merge=union 被隔離擋住(已實測),但沒有測試釘住,也不在「沒有 fixture 釘住的防線」清單裡 requirements+security In-scope
14 LOW 刪除檢查是新功能、落地後 revert 不再回報是行為改變,都還放在 3.5.1 的 ### Fixed(第 10 輪 #9 只處理一半) requirements+regression In-scope
15 LOW 落地處現在看得到只改空白行、只改 file mode 的 commit(定義忽略這兩者),於是會列出 3.5.0 與第 10 輪不列的 commit;「Known blind spots(only these)」與 CHANGELOG「Still not seen」也因此不對 logic In-scope
16 LOW 第 10 輪 Proposed Correction #1 還沒套用:Implementation Complete 的 ### Sister Bugs Filed 仍寫 none surfaced,與 CHANGELOG「其餘未加大括號的用法留給 #401」不一致 logic+requirements+regression 待你執行
17 LOW clean() 只跳脫 ASCII 引號;全形 "、彎引號 “ ” 原樣通過,PR 標題可以在同一行偽造「引號已結束、後面是 helper 的判讀」 security In-scope
18 LOW 「最多 200 次 merge-tree」不成立:undo_noop、EXPECT、探測都沒扣預算,實際是 200 + 2N(N 由 PR 作者控制);文件的資源上限寫錯 security In-scope
19 LOW 一個候選動到含換行(或 \001)的路徑,整次檢查 rc=4,同分支其他正常 orphan 也不再列出(3.5.0 會列) regression In-scope
20 LOW 2026-10-24 回報的 Spectra archive 形狀(squash 後另一個 PR 把檔案搬進 archive/ 並修改)已修好,但沒有 fixture regression In-scope
21 INFO 第 10 輪的 #3、#5、#6、#7、#10–#15、#17 已解決(requirements、logic、regression 三個 lens 分別確認);#1 只在 git ≥ 2.40 解決,#2 部分解決(見上);兩個 suite 179/0、bounded.py --max-len 2 數字與 SPEC 一致;issue 的最小重現 rc=0 requirements+logic+regression —
22 INFO rc=3 的輸出格式變了(subject 加引號並跳脫、多一行 Landing check);只有 idd-close 消費,不影響別處 regression —
23 INFO 未落地分支變慢(舊 2.4 s、新 5.8 s,高負載下),與 CHANGELOG 的計時說明一致 regression —
24 INFO 未發現 prompt injection;安全面整體是淨改善(plumbing、隔離的 merge-tree、ref 先解析成 SHA) security —

處理方式

第 11 輪的 blocking 有三項(#1、#2、#5)都來自同一件事:小世界造不出 git 的衝突,所以 merge-tree 的衝突判斷沒被測過。這和第 10 輪的教訓同一型,照第 8 輪定下的做法,先改檢查器再改 helper:

  1. 擴大小世界:相鄰的 slot(中間沒有分隔行)、一個 commit 動兩個 unit、main 在落地前或落地 commit 裡改分支動過的 unit(相鄰或同一行)。先用它重現 idd-all: 增加 HITL(attended)模式 — direct-commit + 允許 sub-skill AskUserQuestion #1、idd-all: direct-commit mode 加 passive warning (dirty tree / on default branch) #2、idd-all Phase 0.5: fork detection 改 fail-closed (gh repo view 失敗時預設 PR mode) #5、idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6。
  2. 換判斷方式:不再用「撤銷整個 commit 的三方合併有沒有衝突」,改成逐行判斷位置與身分。候選做法是用 head 與落地 commit 的雙向 diff 對齊位置,再用 blame 認出哪一行是誰寫的,這樣 main 自己的同文字副本與相鄰改動都不會混進來。先在擴大後的小世界驗證,不直接採用。
  3. idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6 是內在歧義(只看 tree 分不出),要寫進封閉偏差清單;idd-close 的「abort + land orphans」選項要提醒 cherry-pick 前先比對 main 目前的值。
  4. 需要 maintainer 決定的三件事:idd-all Phase 5: PR body heredoc 改 quoted (預防未來 RCE) #7(git 2.38/2.39 退回計數還是 tip)、idd-all Phase 0.2: GITHUB_REPO post-derive shape assertion #8(partial clone 要不要保留 lazy fetch)、[refactor] idd-list PR body parsing: ignore #N inside fenced code blocks (sister concern from #13) #14(3.5.1 還是 3.6.0)。
  5. LOW 一併處理;push 之後跑 CI。

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315687653 ### Sister Bugs Filed (mid-impl, v2.44.0+ #526) 不是 none surfaced:Step 1.55 修的未加大括號 $CLAUDE_PLUGIN_ROOT 屬於 #401,同檔還有其他處,應記錄並連到 #401 /idd-edit comment:6315687653 --correct --section '### Sister Bugs Filed (mid-impl, v2.44.0+ #526)' --body-file '$HOME/.cache/idd/corrections/correction-399-6.md'

(與第 10 輪報告的同一項,尚未執行。#399 正文的 Current Status 由這次的 /idd-update 重建。)

kiki830621 pushed a commit that referenced this pull request Oct 26, 2026
Version left to the merge: PR #426 and #424 both claim 3.6.0, PR #418
claims 3.5.1.

Refs #411, #412, #413

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJZJ8sbcVVqqLngBfkGNUq
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 14 輪,本機 commit e9e83d2,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...e9e83d2,含本機的第 8–14 輪。

Aggregate

FAIL — 4 HIGH、11 MEDIUM,另有 10 LOW、4 INFO(合併前 29 則)。Codex 的 rename 那一則(原評 MEDIUM)我重現後是假的 clean,而且是相對第 13 輪的退步,併入 HIGH #1。

第 13 輪的四個 HIGH 都修好了(NFD 檔名、R1 把分支自己的 squash 當成 main 的副本、重複行刪一行找不到落地 commit、submodule 回 exit 4),reviewer 也確認過。這一輪的 HIGH 有兩個是第 14 輪新加的規則造成的退步:tip 檢查偵測 rename 之後,被後來搬走的檔案裡的修正看不見了(假的 clean);other_copies 讓複製函式的乾淨 squash 被列出。另外兩個是第 13 輪就有、這輪沒涵蓋到的形狀。

第 12、13、14 輪各有 4 個 HIGH,數量沒有下降,而且幾乎都出在同一處:重複文字與 rename 的特例規則。第 15 輪改的方向是減少特例:把「L^1 的副本是誰的」改成逐段文字判斷,取代現在兩個全域的檢查。

HIGH 的重現(我在凍結的 e9e83d2 上重跑):

重現 第 14 輪 第 13 輪 3.5.0 正確答案
修正之後分支搬了檔案,main 只拿到搬移(logic exp6、Codex 的歷史) 0 3 3 3
手動解衝突的 squash 保留搬移、丟了那一行(logic exp5) 0 3 3 3
複製函式兩次,main 改原函式,乾淨 squash(DA) 3 0 0 0
唯一的一行夾在重複區塊之間,乾淨 squash(DA) 3 3 0 0
兩個 commit 都碰同一段重複文字,乾淨 squash 加 follow-up(requirements) 3 3 3 0

#399

# Severity Finding Source Action
1 HIGH 假的 clean(退步):分支後來把檔案搬走,前面 commit 的修正沒落地也看不見。 c1 在 y.txt 加 X = fix,c2 把 y.txt 搬到 lib/y.txt,main 只拿到 c2(或手動解衝突時保留搬移、丟了這一行)。git diff feat main 有 -X = fix,第 14 輪回 0,第 13 輪與 3.5.0 都是 3。原因:tip 檢查用 c1 自己的檔名讀 head 與 tip,兩邊都沒有 y.txt;第 14 輪的 -M 又讓 c2 的 patch 只剩它自己的改動,沒有 commit 帶著 X。P0 也一樣用 c1 的舊檔名比對(Codex:A 加一行、B 純 rename、main 只 rename,P0 直接回 0) logic+codex Blocking
2 HIGH 退步:複製函式再多複製一次,乾淨 squash(沒有 follow-up)就被列出。 fixture 124 加一個 c3 再複製一份。main 自己的 .strip() 讓 L^1 的副本數和 merge-base 不同,other_copies 看到 c3 也改了副本數就保留 finding,分不出差額是 main 造成的。DA 的 fuzz:82 個乾淨 squash,第 14 輪列出 51 個、第 13 輪 30 個、3.5.0 0 個 DA Blocking
3 HIGH 唯一的一行因為旁邊的重複區塊被配錯位置,乾淨 squash 被列出。 c2 把 load 改名 load_text(唯一的一行),c3 在中間插入一份同樣函式體的 load_lines;diff(c2→squash) 把 def load_text 配到 def load_lines。confirm() 對唯一文字一律相信位置,finding 直接成立。第 13、14 輪都是 3,3.5.0 是 0;兩份偏差清單都沒有這一類。這正是 #399 的症狀,而且在複製函式這種常見形狀上比 3.5.0 差 DA Blocking
4 HIGH 兩個 commit 都碰同一段重複文字時,乾淨的 squash 找不到落地 commit,follow-up 讓 commit 被列出。 複本規則只接受「整個分支只有一個 commit 碰這段文字」,c1 刪一行 pass、c2 再加一行 pass 就被拒,退回 tip。fuzzdel3(fuzzdel2 再加一個 commit 複製被刪的文字):64 個乾淨 squash 錯 7 個,第 13 輪相同,3.5.0 全錯。SPEC 第 56 列、D1 的封閉清單、CHANGELOG 與 header 的 fall-back 清單、manifest 的描述都沒有這個限制 requirements Blocking
5 MEDIUM part_landed 是全域的:分支任何較早的部分到過 L^1(cherry-pick 一個無關的 hotfix,或分兩個 PR 落地),R1 對所有 finding 都停用。fixture 124 加一個早先被 cherry-pick 到 main 的 hot.txt commit:第 14 輪 rc=3,第 13 輪與 3.5.0 是 0(regression);fixture 142 的形狀先落地前兩個 commit:第 13、14 輪都是 3(logic)。SPEC 還寫「計數只會拿掉 finding,不會加」 logic+regression Blocking
6 MEDIUM part_landed 只看最新 20 個較早的 prefix。分支的第一個 PR 是多個 commit 的 squash、而且早於這 20 個時,偵測不到,R1 清掉一個從沒落地的 commit(rc=0)。不是退步(第 13 輪與 3.5.0 也是 0),但這個上限在 SPEC、CHANGELOG、header 都沒寫 requirements Blocking
7 MEDIUM 分多步落地時複本規則不適用:Rebase and merge(一串 cherry-pick)與 release merge(先 squash 進 develop,再 --no-ff merge)。複本規則只數 L 自己那一步,又一律拒絕 merge commit,有 follow-up 就誤報(rebase fuzz 85 例錯 5、release fuzz 69 例錯 8,都與第 13 輪相同)。SPEC 與 CHANGELOG 寫的涵蓋範圍比實際大 DA Blocking
8 MEDIUM 整棵樹模式(路徑超過 64 KB)會漏掉 update-index 拒收的路徑(.git 變形、NTFS/HFS 形式等),訊息被丟棄、exit 0,結果是假的 clean:同一個歷史在 pathspec 模式是 3,整棵樹模式是 0 security Blocking
9 MEDIUM 整棵樹模式會寫進 repo 的 .git:core.splitIndex=true 時 update-index 把 shared index 寫進 $GIT_DIR(實測從 1 個變 5 個 sharedindex.*),write-tree 也會更新既有 object 的 mtime。header 寫「the repository is never written」不成立。DA 另行重現,確認不是誤報 security+DA Blocking
10 MEDIUM awk -v 會解讀路徑裡的反斜線(a\nb.txt 變成含換行),old_of 與 holds_by_copies 的三個 awk 比對不到,複本規則的限制形同沒有。實測只有誤報與錯的落地 commit,沒有假的 clean;也違反本檔「路徑經檔案進 awk,不經 argv」的規則 security Blocking
11 MEDIUM 效能退步:single_changer 在拒絕唯一的失敗行之前就跑,而且每個 prefix 的淨改動都重走一次整個分支。從沒落地、檔案有重複的 ### Fixed 標題時:150 個 commit 從 17 s 變 245 s CPU,40 個 commit 從 7.4 s 變 70 s;idd-close 的 Bash 呼叫預設 120 s 逾時。答案不變 regression Blocking
12 MEDIUM 效能退步:路徑超過 64 KB 時,每個落地檢查列四次整棵樹並建 index;10 萬個檔案的 repo、3000 個檔案的分支、60 個 main commit:33 s 變 202 s CPU。答案不變 regression Blocking
13 MEDIUM bounded.py 對 tip 路由是循環驗證:D1+D2 類別不檢查送到 tip 的原因是否在 D1 列舉的五種裡,路由模型照抄 helper 的落地規則。加上小世界造不出重複區塊、Results 的 fuzz 只跑直接 squash,#2、#3、#7 都在證據之外 DA In-scope
14 LOW 複本規則會接受沒帶分支改動的 commit:淨改動只是搬一行重複文字時要求的副本變化是 0,任何不碰這段文字的 commit 都符合;「其他行在原位」也會被較早落地的行滿足。實測只造成錯的落地訊息,答案沒錯 logic In-scope
15 LOW part_landed 用的是落地搜尋共用的 200 次預算,用完時 R1 默默停用,finding 成立、輸出沒說明(需要約 190 個 main commit 碰同一檔) logic+regression In-scope
16 LOW 整棵樹模式的另外三處:repo 路徑含 : 時 alternates 被切錯;partial clone 下 write-tree 需要每個列出的 blob(git ≥ 2.45 失敗、較舊的會 fetch);TMPDIR 是相對路徑時 shell 與 git 解析的位置不同。三者都是往 tip 退,不會假的 clean security In-scope
17 LOW 文件:SPEC Results 前後對「跑的是哪個 helper」說法不一;CHANGELOG 與 SKILL.md 說 P0 比對「分支 commit 碰過的路徑」,實際是 candidate 的路徑;run-all-tests 寫 suite 要兩三分鐘,負載高時實測 4–10 分鐘,貼近 600 s 上限;3.5.1 的日期還是 10/21 requirements+regression In-scope
18 LOW #399 留言的 ### Sister Bugs Filed 仍寫 none surfaced(第 13 輪 #11,草稿 6 已含 printf 那一條) requirements+DA 推送後
19 INFO 第 8–14 輪都還沒推送,CI 沒跑過。reviewer 在 Docker(Ubuntu 24.04、git 2.43、mawk)跑 suite 289/0(fixture 74 在 2.45 以下跳過);Debian、git 2.30.2 跑 288/0,2.30 的下限成立 requirements+regression 推送後
20 INFO 確認過的:本 repo 99 個已合併 PR(含 #408)、Akashic-Library 235 個已合併 PR,第 13、14 輪答案完全相同,都是 0;issue 的最小重現 rc=0;PR #408 約 8–10 s;輸出清理、pparse 解碼、trace 檔、命令列上的 ref 與路徑都沒有注入或執行外部程式的途徑;輸入裡沒有 prompt injection requirements+security+regression+DA —

處理方式

第 15 輪的順序:

  1. rename 跟到 head(idd-all: 增加 HITL(attended)模式 — direct-commit + 允許 sub-skill AskUserQuestion #1):P0 與 tip 檢查讀一個 commit 的檔案時,先沿著分支的 rename 找到它在 head 的名字,再用那個名字讀 head 與 tip。exp5、exp6、Codex 的歷史都做成 fixture,要求 exit 3。
  2. L^1 的副本逐段文字判斷(idd-all: direct-commit mode 加 passive warning (dirty tree / on default branch) #2、idd-all Phase 0.5: fork detection 改 fail-closed (gh repo view 失敗時預設 PR mode) #5、idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6、[enhancement] idd-list cluster_leader config (lowest | primary) (sister concern from #13) #15):拿掉全域的 part_landed 與 other_copies。改成只扣掉「確定已在 L^1、而且改過這段文字副本數」的分支部分(成立的 prefix、git cherry 配到的 commit);跟這段文字無關的部分不影響 R1。搜尋沒做完(20 個 prefix 的上限、預算用完)時,只有另有分支 commit 改過這段文字的副本數才保留 finding,並在 exit 3 的輸出說明。這一條有自己的預算。
  3. 唯一的行也用計數確認(idd-all SKILL.md: pseudocode quality (goto bash / branch-exists no-op / stale Failure Modes table) #3):位置找到的 finding,不論文字是否重複都要副本數同意才成立;commit 自己搬移的那一行(同一段文字它既刪又加)仍以位置為準。先用 DA 的 fuzz 與 bounded 確認不會多出假的 clean;會的話改成在偏差清單列出這一類。
  4. 複本規則(idd-all Phase 4: ${MAX_ROUND} substitution missing in abort message #4、idd-all Phase 5: PR body heredoc 改 quoted (預防未來 RCE) #7、[refactor] idd-list PR body parsing: ignore #N inside fenced code blocks (sister concern from #13) #14):要求的副本變化是 0 的文字不用複本規則;「其他行在原位」只算 L 自己那一步帶進來的行。idd-all Phase 4: ${MAX_ROUND} substitution missing in abort message #4 與 idd-all Phase 5: PR body heredoc 改 quoted (預防未來 RCE) #7 先試能不能安全放寬;不行就列進 D1,並改正 SPEC 第 56 列、CHANGELOG、header 與 manifest 的說法。
  5. 效能([SOP] umbrella split flow should mandate commit log scan before filing sub-issues #11、Complexity routing: vagueness 應該是一階信號,但目前 4 層 gate 都沒衡量需求清晰度 #12):唯一的失敗行先拒絕,再跑 single_changer,每個 commit 碰過的文字快取起來;整棵樹模式每個 revision 的 tree 只建一次。
  6. 整棵樹模式(idd-all Phase 0.2: GITHUB_REPO post-derive shape assertion #8、idd-issue Step 2: 強制 sanitize incoming title (length cap / strip control chars) #9、idd-all Examples: 跨 repo invocation 段重複描述,合併 #10、[refactor] idd-list Step 5: needs-fix × PR state matrix 應展開,不該摺疊成單行 (verify follow-up from #13) #16):core.splitIndex=false 並清掉 GIT_TEST_SPLIT_INDEX;寫入 index 的筆數和輸入不符就 fail closed;awk 一律經 ENVIRON 拿路徑;alternates 用 C-quoted;write-tree --missing-ok;SCRATCH 先轉成絕對路徑;header 改成「不寫入內容,可能更新既有 object 的時間戳」。
  7. bounded.py(idd-list 應該顯示引用 issue 的 open PR 資訊 #13):D1+D2 類別要求模型找得到 D1 列舉的原因,否則算 unexplained;DA 的 fuzzcopies、fuzzrebase、fuzzrelease 與 fuzzdel3 加進 Results。
  8. [enhancement] idd-list Step 5: planning + has PR 應勸 user 處理 stale/zombie PR (verify follow-up from #13) #17 一併處理。需要 maintainer 決定:版本號(3.5.1 或 3.6.0)、partial clone 是否保留 exit 4。[test] idd-list cluster algorithm + footer rule + state matrix 沒被任何 production data 跑過 (verify follow-up from #13) #18 的更正與 CI([refactor] idd-list regex #(\d+) 應加 \d{1,7} 上限 + 過濾 #0 (verify follow-up from #13, related #14) #19)在推送後處理。

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315687653 ### Sister Bugs Filed (mid-impl, v2.44.0+ #526) 不是 none surfaced:Step 1.55 修的未加大括號 $CLAUDE_PLUGIN_ROOT 屬於 #401(同檔還有 6 處),echo "$OUT" 改成 printf 也是本範圍修掉的 idd-close 缺陷 /idd-edit comment:6315687653 --correct --section '### Sister Bugs Filed (mid-impl, v2.44.0+ #526)' --body-file '$HOME/.cache/idd/corrections/correction-399-6.md'

(第 10–13 輪報告列過的同一項,尚未執行。#399 正文的 Current Status 由這次的 /idd-update 重建。)

…ies per text (#399)

Round 14 verify found four HIGH problems: with renames detected at the tip, a
commit that later moved a file no longer carried an earlier commit's lines, and
the earlier commit was read under a name neither the head nor the tip has, so a
fix that never landed read as landed; the check of other commits' copies listed
a clean squash of a function copied twice; a unique line next to repeated
function bodies was paired with another function's and listed; and a repeated
text two commits touch has no landing commit.

- The tip reads each candidate's files under their names in the head too
  (followed by a rename-detecting diff from the commit to the head).
- Every finding at the landing commit needs the copies to agree, unique lines
  included; a unique line must also be in the landing commit as often as in
  what landed before main's own copies are counted, and one the commit moved
  is judged by position alone.
- L^1's copies are main's unless another commit that changes them reached L^1
  (its own change all in place there). This replaces round 14's global check
  of earlier parts and its check of other commits' copies; subtracting the
  copies of the newest prefix L^1 holds was tried and dropped (a replaced
  deletion makes L^1 hold a prefix whose state it does not have).
- The copy rule needs a count to compare (no move) and a line of the net
  change its own step brought.
- Whole-tree mode: every listed path must reach the scratch index (fail
  closed), no split index, the object directory C-quoted in the alternates,
  write-tree --missing-ok, trees cached per tree and list, SCRATCH absolute.
- awk gets repository paths through the environment, never -v.
- single_changer reads each commit's texts once per file; a unique failing
  line is refused before it runs.
- bounded.py: the model follows the new rules; its tip class needs a cause
  SPEC's D1 names, read from how the case was built (three causes the class
  had taken for granted are now named in D1).
- Fixtures 158-173 (each red on round 14 or on its mutant). Suite 322/322 with
  bash 5, /bin/bash 3.2 and the path threshold at 0; bounded.py: 18 modes, 0
  unexplained (plain length 2 identical to rounds 13 and 14); fuzz: 0 false
  listings in 82 copied-function and 258 deleted-line clean squashes (round
  14: 51 and 0); mutants: survivors in the test.sh header with the reason.
- SPEC (D1, R1, rows 56-73, results, history), CHANGELOG (timing), manifests,
  idd-close's P0 wording, run-all-tests timing; the suite's timeout is 1200 s.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 15 輪,本機 commit 1480ad8,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...1480ad8,含本機的第 8–15 輪。

DA 沒有跑完:執行時連不上 API(ENOTFOUND),對抗檢查這一支中斷,其他 reviewer 判為通過的項目這輪沒有被挑戰過(fail-closed,不當作通過)。第 16 輪的 verify 會重跑完整的 6 支。

Aggregate

FAIL — 4 HIGH、2 MEDIUM,另有 11 LOW、9 INFO(合併前 27 則,其中 1 則是 DA 未完成的完整性紀錄)。

第 14 輪的 HIGH 有三個確認修好:被搬走的檔案在 tip 用 head 的名字讀(exp5、exp6、Codex 的歷史都回 3);複製函式兩次、唯一的行被重複區塊配錯位置,都不再誤報(fuzzcopies 0/82)。第四個(兩個 commit 碰同一段重複文字)照計畫寫進 D1。

這一輪的 4 個 HIGH 是同一類:分支自己 rename 了檔案之後,不同方向的 diff 用不同的檔名當紀錄的 key,於是對不上。pparse 把每筆紀錄都掛在 diff 新那一側的檔名上,但 C、D 紀錄的行號屬於舊那一側;兩邊檔名不同時,join 就失效。第 15 輪的 tip 修正只處理了 tip,落地處、in_l1、「這一步帶進來的行」三處都還有同樣的錯配。

HIGH 的重現(我在凍結的 1480ad8 上重跑):

重現 第 15 輪 第 14 輪 3.5.0 正確答案
fixture 153 加上分支在 b2 之後搬檔(logic rep153r) 0 3 0 3
fixture 146 改成插入行、乾淨 squash 加 follow-up(logic rep146j) 3 0 3 0
fixture 153 加上分支最後搬檔(logic rep153m) 0 0 3 3
改一行、改回來、搬檔,squash 丟掉改回來那個(Codex) 未重跑(與上一列同類) — — 3

#399

# Severity Finding Source Action
1 HIGH 假的 clean:落地處沒有沿著分支後來的 rename 讀檔。 land_orphan_p 的五個 diff 都只限 candidate 自己的路徑;分支後來把檔案搬走時,c→head 把整個檔案讀成刪除,c 的每一行都像被後來的 commit 取代,所以從沒落地的 commit 不會被列出。第 15 輪只在 tip 加了 head 的名字。重現:fixture 153 最後加一個搬檔 commit,第 14、15 輪都回 0,3.5.0 回 3(列出 b4)。Codex 給的歷史(改一行、改回來、純 rename,squash 丟掉改回來那個)是同一個機制 logic+codex Blocking
2 HIGH 假的 clean(退步):in_l1 用兩個不同的檔名 join。 b 自己的紀錄掛在 b 的檔名,b→L^1、b^→L^1 兩個 diff 的紀錄掛在 L^1 的檔名;分支在 b 之後搬檔、而且這次搬檔和 b 一起進了 L^1 時,b 的刪除永遠找不到,讀成「沒到過 L^1」,L^1 的副本被當成 main 的,從沒落地的 restore 被放行。fixture 153 在 b2 之後加搬檔:第 15 輪 0,第 14 輪 3 logic Blocking
3 HIGH 誤報(退步,#399 的症狀):「這一步帶進來的行」檢查在分支搬過檔時一律不成立。 比對 L^1 的兩個 diff 掛在 L^1 的舊檔名,淨改動掛在 head 的新檔名;搬檔那一步的新增行永遠算不上「帶進來」,乾淨的 squash 被拒,退回 tip,follow-up 讓 commit 被列出。fixture 146 把 pad 5 的取代改成插入:第 15 輪 3,第 14 輪 0。fixture 146 本身會過,是因為它的刪除紀錄靠同一個錯配往另一個方向通過 logic Blocking
4 HIGH DA 未完成(API 連線錯誤),對抗檢查沒有發生 — 第 16 輪重跑
5 MEDIUM bounded 的 D1 原因檢查對 own、merge、two 三種模式一律放行,所以「tip 類別需要 D1 列出的原因」在這三種模式裡仍是循環驗證。把那一條拿掉後,--touch --landing own 有 38 例、--touch --landing merge 有 24 例 unexplained,其中一例是 header 列了、SPEC D1 沒列的「git pull merge 的另一邊在 squash 之後改了分支的行」 requirements Blocking
6 MEDIUM 整棵樹模式會執行 repo 的 post-index-change hook:update-index、write-tree 寫 scratch index 時照樣觸發,core.hooksPath 指向工作樹時,跑什麼由 repo 內容決定;hook 卡住也會卡住 idd-close。第 14 輪就有。修法已驗證:-c core.hooksPath=/dev/null security Blocking
7 LOW SPEC D1 是封閉清單,卻少了 header 與 CHANGELOG 列出的四個 tip 退路:超過 200 次檢查、prefix 之後超過 20 個沒落地的 commit、git pull merge、stacked branch requirements In-scope
8 LOW 文件:plugin README 的 v3.5.1 列日期仍是 10/21、follow-up 的說法沒加限定、副本數仍寫「只有重複文字」;SPEC 的第 15 輪歷史仍描述已拿掉的 prefix 扣除,第 68 列仍寫「20 個 commit 的搜尋」,第 56b 列應寫「找不到整個分支的落地 commit」;idd-close 的 TaskCreate 說只有「什麼都沒落地」才用 tip requirements+regression In-scope
9 LOW 第 56b 列(兩個 commit 碰同一段重複文字、Rebase and merge)只有 fuzz 佐證,沒有釘住路由的 fixture;而這兩種是 #399 第二種形狀,仍會列出 commit,要先由 maintainer 確認接受,再寫進 Key Decisions requirements 待決定
10 LOW 淨改動是純 rename(相似度 100%)時,pparse 讀不到 ---/+++,沒有 N/O 紀錄,confirm() 找不到 merge-base 的檔名,main 自己加的副本不會被扣掉 logic In-scope
11 LOW head_names 與 tip 的 patch 都用 -l1000 並丟掉 stderr;一次搬動並修改超過約 1000 個檔案時,rename 偵測被跳過,第 15 輪的對應安靜地失效 logic In-scope
12 LOW TMPDIR 是相對路徑且有 export CDPATH 時,新的 cd 把兩行輸出當成 SCRATCH,每次都 exit 4、暫存目錄留下來;TMPDIR 含反斜線時,scratch 路徑經 awk -v 被解讀錯,也每次 exit 4。都是 fail closed security In-scope
13 LOW other_copies 對每個 finding、每個分支 commit 重跑 in_l1,沒有快取:150 個 commit、8 行的複製函式 squash 要 136 s CPU(第 13 輪 25 s,第 14 輪更慢而且答錯) regression In-scope
14 LOW 在本 repo 自己的部分 merge 上,fc4842e(已落地)仍被列出:commit 新建的檔案裡,唯一的行被兩個 diff 配錯,而 confirm() 對新建的檔案一律保留 finding、不計數。verdict 仍是對的 regression In-scope
15 INFO 帶到下一輪的事:#399 的 Sister Bugs 更正、CI、版本號、partial clone requirements 推送後/待決定
16 INFO 確認過的:#399 的最小重現、PR #408(約 8.5 s CPU)、第 14 輪 HIGH 的重現;本 repo 33 個 PR 與 Akashic-Library 235 個 PR 的答案與第 14 輪逐字相同;Linux Docker(git 2.30.2、mawk)319/0;clean()、pparse 解碼、ENVIRON 路徑、single_changer 快取、want=0 的拒絕都沒有問題;輸入裡沒有 prompt injection 全部 —
17 INFO 第 14 → 15 輪唯一不是修正的答案變化:logic exp3 從 3 變 0,屬於 SPEC 第 69 列記載的 tip 限制(3.5.0 也是 0) regression —

處理方式

第 16 輪的順序:

  1. 一個檔名空間(idd-all: 增加 HITL(attended)模式 — direct-commit + 允許 sub-skill AskUserQuestion #1、idd-all: direct-commit mode 加 passive warning (dirty tree / on default branch) #2、idd-all SKILL.md: pseudocode quality (goto bash / branch-exists no-op / stale Failure Modes table) #3、idd-all Examples: 跨 repo invocation 段重複描述,合併 #10):pparse 改成依行號所屬的那一側取檔名:A 紀錄用新那一側的檔名,C、D 紀錄用舊那一側的檔名;rename from/rename to 也產生 N/O 紀錄。讀檔的地方改用紀錄所在那一側的名字。落地處與 in_l1 的 diff 把 head 的檔名一起放進路徑清單,讓 rename 配得起來。三個重現與 Codex 的歷史都做成 fixture。
  2. bounded 的 D1 原因(idd-all Phase 0.5: fork detection 改 fail-closed (gh repo view 失敗時預設 PR mode) #5、idd-all Phase 5: PR body heredoc 改 quoted (預防未來 RCE) #7):own/merge 只在 main 自己的修改碰到分支改過的單元時算原因,two 只在有重複文字時算;跑出來的 unexplained 逐一處理;SPEC D1 補上四個 tip 退路。
  3. hook(idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6):GIT 一律加 -c core.hooksPath=/dev/null,加 fixture。
  4. 其餘 LOW(idd-all Phase 0.2: GITHUB_REPO post-derive shape assertion #8、[SOP] umbrella split flow should mandate commit log scan before filing sub-issues #11–[refactor] idd-list PR body parsing: ignore #N inside fenced code blocks (sister concern from #13) #14):文件修正;rename 偵測被跳過時當成錯誤;CDPATH=、SCRATCH 含反斜線時直接跳過並說明;in_l1 的結果快取;新建檔案的 A 紀錄也計數。
  5. 需要 maintainer 決定:idd-issue Step 2: 強制 sanitize incoming title (length cap / strip control chars) #9(兩個 commit 碰同一段重複文字、Rebase and merge 這兩種仍退回 tip,是否接受)、版本號、partial clone。

Proposed Corrections

# Target Section 錯在哪 Command
1 comment:6315687653 ### Sister Bugs Filed (mid-impl, v2.44.0+ #526) 不是 none surfaced:Step 1.55 修的未加大括號 $CLAUDE_PLUGIN_ROOT 屬於 #401(同檔還有 6 處),echo "$OUT" 改成 printf 也是本範圍修掉的 idd-close 缺陷 /idd-edit comment:6315687653 --correct --section '### Sister Bugs Filed (mid-impl, v2.44.0+ #526)' --body-file '$HOME/.cache/idd/corrections/correction-399-6.md'

(第 10–14 輪報告列過的同一項,尚未執行。#399 正文的 Current Status 由這次的 /idd-update 重建。)

…name (#399)

Round 15 verify found three HIGH problems of one kind: every diff record
carried the new side's name of its file, also for the old side's lines, so
once the branch had moved a file, records of two diffs about the same line
never met. The check at the landing commit read a candidate under its own
names and missed a commit whose file a later commit moved; in_l1 missed a
commit that reached L^1 before the move, clearing a restore that never
landed; and the copy rule never found a line the squash's own step brought
in a moved file, sending a clean squash to the tip.

- pparse: A records carry the new side's name, C and D records the old
  side's; a move with no edit gets its N/O records from "rename from"/
  "rename to"; a file is kept when either name is listed.
- holds_by_copies, single_changer and confirm read each revision's file
  under the name it has there (pair_of, new_of, head_name_of); the diffs at
  the landing commit list the head's names of a candidate's files too.
- confirm counts lines of a file the commit created as well (only the
  side the line comes from must be a file).
- Every git call runs with core.hooksPath=/dev/null (writing the scratch
  index ran post-index-change); skipped rename detection is an error (exit
  4); no CDPATH when resolving the temp dir, and a temp dir with a backslash
  is refused; in_l1's answer is kept per commit and names.
- bounded.py: a landing mode is no longer a tip cause by itself (a hand-
  edited squash or a merge counts when main's own edit in that step touches
  a branch unit, a merge or two squashes when some text repeats).
- Maintainer decision (2026-10-31): a repeated text two commits touch and a
  several-step landing failing on repeated text stay at the tip, accepted as
  known limits; fixtures 179 and 180 pin that route. Version stays 3.5.1;
  partial clones keep exit 4.
- Fixtures 174-180 (174-178 red on round 15). Suite 335/335 with bash 5,
  /bin/bash 3.2 and the path threshold at 0; bounded.py: 18 modes, 0
  unexplained under the narrower tip causes (plain length 2 identical to
  rounds 13-15); fuzz: identical to round 15 in every run; this repository's
  own partial merge now lists only the commit that did not land; round-16
  mutants: survivors in the test.sh header with the reason.
- SPEC (D1 with the four missing fall-backs, rows 56b, 68, 74-78, results,
  history), CHANGELOG, plugin README row, idd-close TaskCreate wording.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 16 輪,本機 commit 79a0b9e,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...79a0b9e,含本機的第 8–16 輪。這一輪六支都跑完(DA 正常完成)。

Aggregate

FAIL — 2 HIGH、7 MEDIUM,另有 11 LOW、5 INFO(合併後 25 則)。

第 15 輪的三個 HIGH 確認修好:rep153r、rep153m 從 0 變 3,rep146j 從 3 變 0,Codex 的「改一行、改回來、搬檔」回 3 且只列 c2;fixture 175、178 在第 15 輪是紅的、第 16 輪是綠的。本 repo 自己的部分 merge 只列 e9e83d2。本 repo 198 次與 Akashic-Library 235 次已 merge PR 的答案與第 15 輪逐字相同;Linux Docker(git 2.30.2、mawk)332/0(3 個依版本跳過)。

這一輪的兩個 HIGH 是同一類,也是第 16 輪主改動照不到的地方:判斷「同一個檔案」只靠一次兩棵樹的 rename 偵測(commit → head 的 diff-tree -M),或只認得 commit、parent、head、merge-base 四個名字。檔案在分支上搬兩次、搬走後在原名放一個新檔、搬走後大幅改寫,這三種形狀都認不出來,從沒落地的 commit 被判成已落地。DA 指出 regression 的「相對第 15 輪的答案變化都是修正」不成立:搬兩次的例子是 3→0;而 bounded.py 的小世界不建 rename,fuzz 也只有單次 rename,所以這些證據量不到第 16 輪的主改動。

HIGH 的重現(我在凍結的 79a0b9e 上重跑):

重現 第 16 輪 第 15 輪 3.5.0 正確答案
fixture 153 的形狀,檔案搬兩次(f → mid/f → lib/f),只改中間名的 b2 先被 pick 到 main,b4 沒落地(logic probeD) 0 3 3 3
A 加一行;B 搬檔;C 在原名放一個 shim;main 只 pick B、C(DA R1) 0 0 3 3
A 加一行;B 搬檔;C 把新檔大部分改寫;main pick B,C 解衝突後沒有 A 的行(DA R2) 0 0 3 3
R1 的刪除版:A 刪一行,main 的新檔仍有那一行(DA R3,落地處) 0 0 0 3

#399

# Severity Finding Source Action
1 HIGH 假的 clean(相對第 15 輪與 3.5.0 的退步):檔案搬兩次時,只改過中間名的 commit 看不見。 other_copies 只用 head、commit、parent、merge-base 四個名字列分支的 commit;f → mid/f → lib/f 時,只改 mid/f 的 commit 不在清單裡,它被 pick 到 main 之後,L^1 的副本被當成 main 自己的,從沒落地的 b4 被放行。single_changer 和 prefix 模式的計數有同樣的限制。第 15 輪答對只是因為另一個錯配剛好抵銷 logic, DA Blocking
2 HIGH 假的 clean:檔案的身分只靠一次兩棵樹的 rename 偵測。 兩棵樹的 rename 偵測要求來源路徑消失、相似度至少 50%;搬走後在原名放 shim、或搬走後大幅改寫,commit → head 就配不起來,commit 的行用舊名去讀,讀到的是另一個檔。tip(R1、R2:3.5.0 為 3,第 15、16 輪為 0)和落地處(R3)都受影響 DA Blocking
3 MEDIUM count_of_line() 用 awk 的 == 比較兩行,兩邊都像數字時比的是數值:1、01、1.0 被算成同一行的副本,計數可能把沒落地的 commit 放行。已在 macOS awk 確認,改成字串比較即可 codex Blocking
4 MEDIUM partial clone 的決定(exit 4、不 lazy fetch)只在 git ≥ 2.45 成立;支援的最低版本是 2.30,2.30–2.44 會從網路抓 blob 並寫進 repo。CHANGELOG 的寫法沒有加版本限定 requirements Blocking
5 MEDIUM 「rename 偵測被跳過就是錯誤」讓沒有搬任何檔案的分支 exit 4:git 只要新增 × 刪除的檔案數超過 1000×1000 就跳過,與有沒有搬檔無關;第 16 輪又對每個 candidate 做整棵樹的 commit → head diff。分支後來重產上千個檔案(vendored、generated)、或 merge 過做了這件事的 main,之前每個 candidate 都 exit 4(3→4、0→4,git 2.55 與 2.30 都重現) logic, regression, DA Blocking
6 MEDIUM 設定檔定義的 hook 仍會執行:core.hooksPath=/dev/null 擋不到 hook.<name>.event=post-index-change,整棵樹模式寫 scratch index 時照樣觸發(git 2.55 實測 6 次)。要另加 -c hook.post-index-change.enabled=false security, DA Blocking
7 MEDIUM regression 的「每個答案變化都是修正」被推翻(見 #1):sweep 用的 suite、real PR、fuzz、bounded 都不建兩次搬移 DA Blocking(證據缺口)
8 LOW SPEC D1 少列 bounded.py 最常用的 tip 原因:「已落地的 prefix 之後的 commit」 requirements In-scope
9 LOW bounded.py 的 d1_cause 仍比 SPEC D1 寬:mop 是 landing 之前的 main commit,不是 landing 那一步的修改;「沒有淨改動」一律放行;「多步落地」與「重複文字」沒有要求 landing commit 真的在那段文字上失敗。拿掉前兩條後仍是 0 unexplained requirements, DA In-scope
10 LOW idd-close Step 1.55 與 plugin README 仍寫成「找不到落地處時」才用 tip;部分 merge 時 prefix 之後的 commit 也在 tip 檢查 requirements In-scope
11 LOW exit 4 的清單只寫暫存目錄含反斜線,程式也拒絕換行;CHANGELOG、header、test.sh 對「rename 偵測被跳過」的觸發條件寫錯,印出的理由也沒有提到它 requirements, regression In-scope
12 LOW no_rename_skip 在 git 的整段 stderr 裡找一個片語,repository 的文字(例如目錄名出現在 .gitattributes 的錯誤訊息)可以造出它,乾淨的 squash 變成 exit 4 logic, security In-scope
13 LOW pparse 對沒列在清單那一側的檔名不檢查換行:一個含 LF 的檔名能偽造 C/A/D/N/O 紀錄。要先過 has_lf 的檢查,實際可達的路徑很窄,屬於 defense-in-depth security In-scope
14 LOW 每個 candidate 都跑 head_names,150 個 commit 的分支 CPU 時間多約 44%;CHANGELOG 的時間數據是更早幾輪的 regression In-scope
15 LOW #399 的 Sister Bugs 更正(comment 6315687653)從第 10 輪延到現在還沒套用 requirements 推送後
16 INFO 第 15 輪 #10、#11、#12 的處理沒有 fixture 釘住 requirements In-scope
17 INFO 確認過的:#399 的最小重現、PR #408(約 9.7 s CPU)、第 15 輪 HIGH 的重現、fixture 179/180 的路由、版本號一致、外部寫入與 diff 一致、輸入裡沒有 prompt injection 全部 —

處理方式

第 17 輪的順序:

  1. 檔案身分改成逐 commit 追蹤(idd-all: 增加 HITL(attended)模式 — direct-commit + 允許 sub-skill AskUserQuestion #1、idd-all: direct-commit mode 加 passive warning (dirty tree / on default branch) #2、idd-all Phase 0.5: fork detection 改 fail-closed (gh repo view 失敗時預設 PR mode) #5、idd-all Phase 5: PR body heredoc 改 quoted (預防未來 RCE) #7):分支每個 commit、以及 main 從 merge-base 到 landing 的每一步,各自記錄自己的 rename;任何一個 revision 裡的檔名,都從 candidate 的檔名沿這些步驟一步步推過去(像 git log --follow),不再靠一次兩棵樹的偵測、也不再從四個名字裡挑第一個存在的。比對兩個 revision 時,同一個檔案的兩個名字直接配對,不交給 git 猜。rename 偵測只在單一 commit 內做;那個 commit 的偵測被跳過時,只有我們追的檔名正好在它沒配對的檔案裡才算錯誤。bounded.py 加入搬兩次、原名放 shim、搬走後改寫的形狀,讓這一輪的主改動有證據。
  2. 計數改成字串比較(idd-all SKILL.md: pseudocode quality (goto bash / branch-exists no-op / stale Failure Modes table) #3)、設定檔 hook 一併關掉(idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6)、git < 2.45 的 partial clone 開頭就 exit 4(idd-all Phase 4: ${MAX_ROUND} substitution missing in abort message #4),各加 fixture。
  3. pparse 拒絕含換行的檔名(idd-list 應該顯示引用 issue 的 open PR 資訊 #13);rename 偵測是否被跳過改成從檔案數判斷,不再搜 stderr(Complexity routing: vagueness 應該是一階信號,但目前 4 層 gate 都沒衡量需求清晰度 #12)。
  4. bounded 的 D1 原因與 SPEC 對齊(idd-all Phase 0.2: GITHUB_REPO post-derive shape assertion #8、idd-issue Step 2: 強制 sanitize incoming title (length cap / strip control chars) #9),文件修正(idd-all Examples: 跨 repo invocation 段重複描述,合併 #10、[SOP] umbrella split flow should mandate commit log scan before filing sub-issues #11、[refactor] idd-list PR body parsing: ignore #N inside fenced code blocks (sister concern from #13) #14)。
  5. [enhancement] idd-list cluster_leader config (lowest | primary) (sister concern from #13) #15 推送之後套用。

Round 16 verify found two HIGH problems of one kind: a file's identity
came from one rename detection between two trees, or from the first of
four fixed names a revision had. A file moved twice (a commit that edited
it only under the middle name was never listed), a file moved away with
a new file put at its old name, and a file moved and then mostly
rewritten were paired wrongly, and commits that never landed read as
landed.

- Every file is followed commit by commit, as git log --follow does:
  one git log -M over the non-merge commits since the fork point gives
  each commit's own moves; a file's name in a revision is what those
  moves make of it, and every file is known by its name in the head. A
  move over two commits of the branch comes from one rename detection
  between the merge-base and the head, among the files added and deleted
  there. On main, a file the branch moved is the one main created at a
  name it had on the branch (a squash that moves a file and puts a new
  one at its old name records no move).
- Every diff compares trees that hold each file under its key (ctree in
  the scratch store when a moved file is involved), with no rename
  detection between two trees; pparse, confirm, other_copies, in_l1,
  single_changer, holds_by_copies, the tip check and the head-equals-tip
  shortcut read each file by its key. The commits that edited a file are
  listed by every name it had. head_names, pair_of and the per-side
  record names are gone; same_paths still compares the names themselves.
- A skipped rename detection (more than 1000 files added and deleted in
  one commit) is an error only where it deletes a file an earlier
  candidate touched, read after it (exit 4 with the reason); a branch
  that replaces a thousand generated files no longer exits 4.
- Lines compare as text in the copy count (awk compared "1", "01" and
  "1.0" as numbers); hook.post-index-change.enabled=false turns off a
  hook defined in the configuration; a partial clone with git older than
  2.45 stops before reading anything (exit 4).
- bounded.py: the tip causes follow SPEC D1's wording (main's commit
  before the squash is no cause, nor a branch with no net change by
  itself; each repeated-text cause needs the text at a unit the net
  change changes). SPEC D1 lists the commits after a landed prefix.
- Fixtures 181-195 (red on round 16 or on round 17's own drafts; the
  test.sh header says which). Suite 371/371 with bash 5, /bin/bash 3.2
  and the path threshold at 0; Linux (git 2.30.2, mawk) 368/368;
  bounded.py: 18 modes, 0 unexplained under the narrower tip causes,
  every number as in round 16; old fuzzers identical to rounds 15-16;
  new rename fuzzer (fuzzrename.py): 251 cases, 0 false clean, 0 false
  listing, 8 D1+D2 (round 16: 19 false clean, 13 false listing); round 16's and 15's repro scripts: same answers as
  round 16 (21 scripts), round 16's HIGH repros fixed.
- SPEC (D1, rows 79-92, history, results), CHANGELOG, plugin README row,
  idd-close wording of the tip route, test.sh header.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 17 輪,本機 commit 1cfb2de,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...1cfb2de(da47674 起),含本機的第 8–17 輪。六支都跑完。

Aggregate

FAIL — 6 HIGH、7 MEDIUM,另有 11 LOW、6 INFO(合併後 30 則)。

第 16 輪的兩個 HIGH 確認修好:搬兩次、原名放新檔、搬走後大幅改寫三種形狀都回 3、列對 commit;awk 數值比較、只換掉上千個檔案而沒搬檔就 exit 4 這兩個 MEDIUM 也修好。本 repo 198 次與 Akashic-Library 235 次已 merge PR 的答案與第 16 輪相同;/bin/bash 3.2 與 Linux(git 2.30)的 suite 全過。

但這一輪的新做法(逐 commit 追蹤檔案,所有比對以檔案在 head 的名字為準)在 main 那一側 對應錯檔案,造成相對第 16 輪的退步。main 的歷史只看得到 main 自己的 commit 怎麼記錄,而 squash 一步做完整個分支:先複製再刪舊檔,squash 記成一次 rename;兩個檔互換名字,squash 什麼 rename 都不記;分支 merge 過 main 時,同一路徑從兩條歷史線各來一個檔。這些情況下,「依 main 的歷史」對到的不是分支的那個檔。另外兩個 HIGH 與檔名無關:SIGPIPE 在寬分支上讓判斷函式回錯碼;partial clone 只認得一個 git 自己不寫的設定。

HIGH 的重現(reviewer 實跑,我抽查過前兩列):

重現 第 17 輪 第 16 輪 3.5.0 正確答案
分支先加 b(a 的複本)、再刪 a,squash 後 head 與 tip 的 tree 完全相同(DA copydel) 3 0 0 0
同上再加 main 的 follow-up(#399 的第二種形狀) 3 0 3 0
兩檔分三個 commit 互換名字後 squash,之後一個刪行的 commit 沒落地(DA swap2 del) 0 3 — 3
分支 merge 過 main(main 已 squash 過分支的一部分),之後一個刪行 commit 沒落地(logic mk_fc2) 0 3 — 3
寬分支(上千個路徑)裡有搬過的檔、名字排在前面,squash 落地(regression mkwide) 3 或 4 0 — 0

#399

# Severity Finding Source Action
1 HIGH 假的 clean(退步):分支 merge 過 main 時,merge-base 的檔案對不到 key。 同一路徑從兩條歷史線各來一個檔,重放時誰佔住路徑取決於 commit 日期;merge 進分支的 main commit 被當成分支的 revision,main 那一側的規則都不套用。淨改動因此少了刪除,沒落地的 commit 被判為 clean logic Blocking
2 HIGH 誤報(退步):squash 自己的 rename 偵測與分支逐 commit 的歷史配對不同時(先複製再刪、搬移後再複製、搬到中間名再刪再加),main 依歷史把名字給了另一個檔,head 的檔在 main 上找不到;P0 也因此把 head 與 tip 完全相同的分支判成有 orphan,違反 #399 的 Expected logic, DA Blocking
3 HIGH 假的 clean(退步):兩檔互換名字(或三個以上輪轉)後 squash,git 只記成兩次修改,main 依歷史把 key 對到錯的檔,沒落地的刪行被判為已落地 DA Blocking
4 HIGH SIGPIPE:has_tracked、diff_by_key 的 awk 一找到就提早結束,set -o pipefail 下整條 pipeline 回 141。路徑清單超過 pipe buffer 且被追蹤的名字排在前面時,比對失敗,退回 tip 誤報或 exit 4;P0 則把 141 當成「沒有被追蹤的名字」 regression, DA Blocking
5 HIGH partial clone 只認 extensions.partialClone:git clone --filter 只寫 remote.<name>.promisor=true,所以 git 2.30–2.44 的真 partial clone 仍會從網路抓 blob、寫進 repo codex, requirements, security, DA Blocking
6 MEDIUM 路徑含位元組 \034(awk 的 SUBSEP)時,entries_of、ctree 把它當分隔符拆開,tip 檢查可能把沒落地的修改判為 clean codex Blocking
7 MEDIUM hook.post-index-change.enabled=false 這個 event 開關是 git 2.55 才有;2.54 已有設定檔 hook,卻只能逐一以名字關掉,寫 scratch index 時照樣執行 security Blocking
8 MEDIUM 沒有 merge-base(淺 clone、不相關的歷史)時,identity 層在 P0 之前就 exit 4;第 15、16 輪會照常回答 regression Blocking
9 MEDIUM 只要 fork point 之後有任何 rename(包括 main 自己不相關的),每個 diff 都重放一次 fork 以來的歷史:main 很長時 CPU 2–4.6 倍 regression Blocking
10 MEDIUM 「rename fuzz 0 false clean」的涵蓋範圍說得太大:產生器不會移回原名、互換、輪轉、跨 commit 搬移、main 自己搬檔、分支 merge;bounded 也不建 move DA Blocking(證據缺口)
11 LOW blob:none 的 partial clone 在 main 的 git log -M 需要 blob,exit 4 的理由沒有說是 partial clone requirements In-scope
12 LOW 文件:rename 偵測被跳過的條件應寫「新增 × 刪除 > 1,000,000」;「main 搬走的檔裡的刪除」已不再是盲點(現在回 3);test.sh 開頭的 exit 4 清單沒補新原因;SPEC/CHANGELOG 對 main 第 1 步的描述比程式窄;SPEC 說「不再有兩棵樹的 rename 偵測」但 links 就是一次;CHANGELOG 把未發布的內部輪次當比較基準 requirements, regression In-scope
13 LOW unc_check 的 awk 讀檔失敗被當成「沒有」;P0 的 key 檢查在 has_tracked/cand_keys 出錯時落到 exit 0 logic In-scope
14 LOW \001 同時是名字裡 LF 的代用字元與檔名歷史的分隔符 security In-scope
15 LOW 第 16 輪說要在 bounded.py 加 rename 形狀,實際改用 repo 外的 fuzzer requirements In-scope
16 INFO 確認過的:#399 最小重現與 follow-up、PR #408、第 16 輪 HIGH 的重現、433 個已 merge PR、bash 3.2 與 Linux 的 suite、外部寫入與 diff 一致、輸入沒有 prompt injection 全部 —

處理方式

第 18 輪:

  1. main 那一側改成「落地處之後看名字、之前看歷史」(idd-all: 增加 HITL(attended)模式 — direct-commit + 允許 sub-skill AskUserQuestion #1–idd-all SKILL.md: pseudocode quality (goto bash / branch-exists no-op / stale Failure Modes table) #3):被當成落地處的 commit 及其後(含 tip)以名字配對,用分支在落地那一點的檔名;落地處之前(L^1 等)才依 main 的歷史。分支這一側的歷史改成同一路徑從兩條歷史線來時視為同一個檔,與重放順序無關;merge 進分支的 main commit 也走 main 那一側的規則。P0 只比名字。互換、輪轉、先複製再刪、分支 merge main 都做成 fixture,fuzzer 也加這些形狀。
  2. SIGPIPE(idd-all Phase 4: ${MAX_ROUND} substitution missing in abort message #4):判斷用的 awk 直接讀檔,不經 pipe;檢查其他提早結束的 pipeline。
  3. partial clone(idd-all Phase 0.5: fork detection 改 fail-closed (gh repo view 失敗時預設 PR mode) #5、[SOP] umbrella split flow should mandate commit log scan before filing sub-issues #11):同時認 remote.*.promisor,理由寫明是 partial clone。
  4. \034、設定檔 hook、沒有 merge-base、效能(idd-all SLUG derivation: BSD/GNU sed alternation (\| not portable) #6–idd-issue Step 2: 強制 sanitize incoming title (length cap / strip control chars) #9):不用 SUBSEP 拼接路徑;逐一以名字關掉綁在 post-index-change 的 hook;沒有 merge-base 時不追蹤檔名、照舊回答;沒有被追蹤的名字時不重放歷史,ops 只留有動作的 commit。
  5. LOW 與文件(Complexity routing: vagueness 應該是一階信號,但目前 4 層 gate 都沒衡量需求清晰度 #12–[enhancement] idd-list cluster_leader config (lowest | primary) (sister concern from #13) #15)。

Round 17 verify found six HIGH problems. Three of one kind: main's
files were read by main's history, and a squash records the branch's
moves as one rename detection, which pairs files otherwise. A copy
and then the original deleted read as a move (a branch whose head and
tip were the same tree was listed); two files that swap names read as
two edits (a deletion that never landed read as landed); a branch that
merged main's squash had one path reached by two lines of history,
kept by whichever the log listed first (a deletion that never landed
read as landed). Two checks stopped reading a pipe at the first match
(SIGPIPE under pipefail on a wide branch), and a partial clone made by
git clone --filter was not recognized.

- Readings of a revision of main: "<rev>@<ref>" holds the branch's
  files at the names they had in <ref> where it has a file there (the
  landing commit and the tip after a landing); "<rev>@@<ref>" does the
  same except where main's history puts another of the branch's files
  at that name: history's when main never changed the file there, else
  the head file it shares more lines with (each commit the landing
  search tries; the tip when nothing landed); a revision in its
  reference's history is read by history. The landing search also asks that every file the
  net change moved be at the reference's name (same_files).
- At the tip and at the landing commit a path a commit added or
  deleted is compared by file, not by whatever is at the name.
- One file per path two lines of history bring a file to, whatever
  the order; a link's old name paired with the merge-base's file; a
  copy and the original deleted later are one file, link by link
  (each branch deletion paired with the copies made before it); of a
  copy and its original in one revision the copy stands for the file,
  and the original's name reads back to it.
- P0 compares names. No check stops reading a pipe early; awk never
  joins two paths in one string; promisor remotes count as a partial
  clone; configured hooks are also turned off by name (git 2.54); with
  no merge-base no file is followed; a revision replays only the
  commits that move, add or delete a followed name.
- Fixtures 196-212 (each red on round 17 or on round 18's own drafts;
  the test.sh header says which). Suite 408/408 with bash 5, /bin/bash 3.2 and the
  path threshold at 0; Linux (git 2.30.2, mawk) 405/405; bounded.py:
  18 modes, every number as in round 17, 0 unexplained; the older
  fuzzers identical to round 17; fuzzrename2 (seeds 1-8, 353 cases):
  false clean/false listing 0/1 (D3) where the landed commits are a
  prefix and 2/10 where they are not (round 17: 6/44 and 11/15;
  round 16: 6/17 and 14/14); fuzzrename (251 cases): 0 false
  clean, 2 false listings, both where the landed commits are not a
  prefix; round 16's and 17's repro scripts: the same 30 answers as
  round 16; mutants M800-M823: 19 caught, M805's full variant caught,
  four unpinned backstops (test.sh header). CPU time on PR #408 about
  20 s.
- SPEC (D1, rows 93-109, history, results), CHANGELOG, plugin README
  row, helper header, test.sh header.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 18 輪,本機 commit 2376f6e,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...2376f6e(da47674 起),含本機的第 8–18 輪。六支都跑完。

Aggregate

FAIL — 1 CRITICAL、3 HIGH、10 MEDIUM,另有 15 LOW、8 INFO(合併後 37 則)。

第 17 輪的 HIGH 確認修好:先複製再刪的完整 squash、兩檔互換後的刪行、分支 merge 過 main 的刪行、寬分支的 SIGPIPE、git clone --filter 的 partial clone。本 repo 198 次與 Akashic-Library 235 次已 merge PR 的答案都沒變;/bin/bash 3.2 與 Linux(git 2.30)的 suite 全過;run-all-tests 63/0。

但第 18 輪為 main 那一側加的幾條讀法(落地處依名字、弱讀法讓歷史優先、先複製再刪的配對),在常見的形狀上弄壞了第 16、17 輪原本對的答案,其中兩個就是 #399 本身那一類誤報(整個 squash 落地、之後 main 又改了它)。

CRITICAL 與 HIGH 的重現(reviewer 實跑,我抽查過第 1、2 列):

# 重現 第 18 輪 第 17 輪 第 16 輪 3.5.0 正確答案
1 分支 merge 過 main(main 已 squash 並把檔搬走),之後分支改舊路徑上的檔,沒落地 0 3(但列錯 commit) 3 3 3
2 兩檔互換、再複製並刪原檔;main 只 squash 了一部分(不是前段),互換的最後一步沒落地 0 3 — — 3
3 單次「複製後刪原檔」、搬走後原名放新檔,main 只拿到一部分:第 18 輪多列已落地的 commit 多列 對 — — —
4 分支「搬走再放回原檔」(或複製、刪除、再 revert),整個 squash 落地,之後 main 改了它 3 0 0 0 0

#399

# Severity Finding Source Action
1 CRITICAL 假的 clean(退步):同一路徑從兩條歷史線各來一個檔時,第 18 輪把兩者併成同一個檔;之後其中一條線把檔搬走,另一條線的檔也跟著失去身分,分支之後在舊路徑上的修改讀成空的。結果取決於 commit 的日期順序 logic Blocking
2 HIGH 假的 clean(退步):落地的 commit 不是前段時,整個分支被當成在 squash 落地;分支中途建立又移除的名字(main 還留著檔)不在淨改動裡,於是在落地處漏掉。不在任何已記錄的偏差裡 requirements Blocking
3 HIGH 新的 D1 限制寫得比實測窄:單次複製後刪除、搬走後原名放新檔(決策表第 80、82、88、92 列宣稱處理的形狀)也會出錯,且是相對第 17 輪的退步;Results 只給總數,看不出這些是新的 requirements Blocking
4 HIGH 假的 listing(退步,#399 本身的形狀):搬走再放回原檔、或複製刪除再 revert,整個 squash 落地後 main 又改了它:弱讀法把原名保留給已經搬走的檔,放回來的檔在 main 上找不到 DA Blocking
5 MEDIUM 弱讀法:搬走後在原名放一份一模一樣的檔,main 沒動過那個路徑,就把名字留給歷史,真的落地處被拒絕(退步,同屬 #399 的形狀) logic Blocking
6 MEDIUM 純搬移落在 main 自己已有檔的名字上(另一個 PR 先加了同名檔),各版本都回 0;不在已知盲點清單 DA Blocking(文件或修)
7 MEDIUM 複製配對:pathspec 不限長度(寬分支從 3 變成 exit 4)、O(刪除 × 新增)、依 awk 陣列順序(macOS 與 Linux 答案不同) security, regression, logic Blocking
8 MEDIUM 環境裡的 GIT_CONFIG 會讓兩個讀設定的防線失效(2.54 的 hook、2.45 以前的 lazy fetch) security Blocking
9 MEDIUM 新的已知限制沒有經過你同意就進了封閉的偏差清單 requirements 需要你決定
10 MEDIUM 兩個 rename fuzzer 不在 repo 裡,無法重現 codex In-scope
11 LOW nm_decide 平手時給名字,兩個幾乎相同的小檔互換可以讓 main 自己的 commit 被當成落地處(各版本都回 0) DA In-scope
12 LOW CPU 時間比第 16、17 輪多 40–70%,落地處搜尋加上搬檔時是第 16 輪的 3.7 倍;suite 在 macOS 接近 1200 秒上限 regression In-scope
13 LOW 文件:holds 實際用弱讀法;決策表第 101、106 列與 helper 不一致;remote.<name>.partialclonefilter;GIT_CONFIG_COUNT 前導零與 GIT_CONFIG_PARAMETERS;cand_keys 的讀取錯誤;計時的負載範圍;複製偵測上限 200 沒列進限制 requirements, security, regression In-scope
14 INFO 確認過的:第 17 輪 HIGH 的重現、433 個已 merge PR、跨平台 suite、diff 以外的寫入與 diff 一致、輸入沒有 prompt injection;DA 認為 #1 應降為 HIGH(第 17 輪在另一種日期順序下也錯) 全部 —

處理方式

第 16 到 18 輪的走向是:每一輪為檔案身分加的規則,修好上一輪的形狀,卻在別處弄壞原本對的答案。第 19 輪開始前需要你決定範圍(見 dashboard)。第 19 輪的草稿已經修好 #1(每個 view 與該 revision 實際的樹對帳)與 #2(被刪的名字上若有沒歸屬的檔,算沒落地),fixture 181–212 全綠。

Round 18 verify found one CRITICAL and three HIGH problems, most of
them round 18's own rules breaking shapes earlier rounds got right: a
path two lines of history brought a file to was one file, and a move on
one line took the other line's file with it (an edit at the old path
read as no change); a name the branch made and removed again was in no
net change, and the commit that left it was missed; a move and the
original put back, squashed and edited later by main (#399's own
shape), listed both commits. The maintainer chose to converge
(2026-11-01): fix the common shapes and #399's own, and accept the
rarer move combinations as known limits.

- Each view of the files is checked against its revision's tree: a
  name the revision holds and the view lost is a file of its own, one
  the view holds and the revision does not is gone.
- Readings of main: a file history puts at a name keeps it only when
  it has no place of its own at its name in the reference; history's
  exception applies only when the head has another file at the
  reference name; a name the reference gives another file is skipped
  only while that file has no place elsewhere (always for the other
  name of a copy); a tie in the content decision goes to history.
- At the tip and the landing commit a deleted name holding a file the
  reading gives no file of the head counts as not landed; at the tip a
  deleted line is kept while any name of the file has it.
- Copy pairing newest copy first in a fixed order, bounded (200
  detections, 100,000 pairs, 64 KB of paths, beyond which a deletion
  stays a deletion). GIT_CONFIG and an inherited GIT_CONFIG_PARAMETERS
  are unset; a remote with a partial clone filter is a partial clone;
  GIT_CONFIG_COUNT is read in base 10; cand_keys fails on a read
  error; same_files runs only when the net change touches a followed
  file.
- SPEC M1 lists the accepted limits (only these, verdict wrong either
  way): landed commits not a prefix with a moved, swapped or copied
  file; a copy and its original both landed with a line deleted before
  the copy; a pure move onto main's own file; nearly identical files
  swapped; beyond the copy-pairing bounds.
- The rename fuzzers (fuzzrename.py, fuzzrename2.py) are in the test
  directory; the suite asks for 2400 s.
- Fixtures 213-220. Suite 420/420 with bash 5, /bin/bash 3.2 and the path
  threshold at 0; Linux (git 2.30.2, mawk) 417/417; bounded.py:
  plain length 2 in full and four sampled modes, every number as in
  round 17, 0 unexplained; fuzzrename2 (353 cases), false clean/false
  listing: 0/1 (D3) where the landed commits are a prefix and 1/8
  where they are not (round 18: 0/1 and 2/10; round 17: 6/44 and
  11/15); fuzzrename (251 cases): 0 false clean, 2 false listings,
  both off the prefix route (M1); round 16's and 17's repro scripts:
  the same 30 answers as round 16; the round-18 reviewers' repros of
  the CRITICAL and HIGH findings fixed; mutants M900-M913: 7 caught,
  5 unpinned (test.sh header). CPU time on PR #408 about 17 s.
- SPEC (M1, rows 101, 106, 110-117, history, results), CHANGELOG,
  helper header, test.sh header.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 19 輪,本機 commit eedd008,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...eedd008(da47674 起),含本機的第 8–19 輪。六支都跑完。

Aggregate

FAIL — 2 CRITICAL(同一件事,兩個 lens 各自找到)、3 HIGH、6 MEDIUM,另有 9 LOW、8 INFO(合併後 28 則)。

確認修好的:第 18 輪的 move-and-restore、同名同內容的檔、awk 順序、GIT_CONFIG、partial clone filter。本機 suite(bash 5、/bin/bash 3.2、路徑門檻 0)420/420,Linux(git 2.30)417/417,run-all-tests 63/0。安全面沒有新問題;diff 以外的寫入與 diff 一致;Codex 盲審沒有新發現。

但第 19 輪的規則又弄壞了幾個先前對的答案,DA 另外找到兩個從 3.5.0 起就錯、屬於 #399 本身症狀的常見流程。

重現(reviewer 實跑):

# 重現 第 19 輪 第 18 輪 第 17 輪 第 16 輪 3.5.0 正確答案
1 分支先改檔、之後才 merge main(main 已 squash 第一個 commit 並搬走檔);那次修改沒落地 0 0 3 3 3 3
2 main 搬走檔並在原名放新檔;分支刪掉的一行沒落地(前段落地,分支沒搬檔) 0 3 3 — — 3
3 分支建立暫時的名字再搬走,main 在那個名字有自己的檔,整個 squash 3 0 0 — — 0
4 第一個 PR squash 後按 Update branch 繼續做,main 改了第一個 PR 的行,第二個 PR squash 3 3 3 3 3 0
5 main 在 PR 開著時改檔名,squash 整個分支後 follow-up 改同一行 3 3 3 3 — 0

#399

# Severity Finding Source Action
1 CRITICAL 假的 clean(退步):第 18 輪 CRITICAL 只修了一半。對帳只救回 head 仍持有的那個名字;分支在 sync merge 之前做的修改,仍歸到 main 搬走的那個檔,於是讀成已落地(上表第 1 列)。main 以 cherry-pick 或自己加同樣內容取得第一個 commit 時也一樣 requirements, regression Blocking
2 HIGH 假的 clean(退步):收窄後的歷史例外只在 head 另有檔時才跳過參照名字;main 把檔搬走、在原名放無關的新檔時,tip 拿分支的檔去比 main 的新檔(上表第 2 列) logic Blocking
3 HIGH 假的 listing(退步):「被刪的名字上若有沒歸屬的檔,算沒落地」這條規則在整個 squash 落地時也生效,分不出那是分支的檔還是 main 自己的檔(上表第 3 列;搬檔、複製後刪除、改名都會) logic Blocking
4 HIGH 假的 listing(#399 本身的症狀,3.5.0 起都錯):上表第 4 列。sync 之後 merge-base 已含第一個 PR,但 git cherry 仍把第一個 commit 當候選,拿到第二個 PR 檢查,而那裡的行已被 main 改掉。決策表沒有這一列,bounded.py 也不曾在兩次 squash 之間 sync。D4 的理由在這裡不成立 DA Blocking
5 MEDIUM 假的 listing(第 16 輪起都錯):上表第 5 列。不在 M1 也不在 10/31 的接受清單;兩個 rename fuzzer 都不會在落地後做 follow-up,也不會在落地後讓 main 搬檔再 sync,所以「前段路線 0 個假 clean」沒量到這兩類 DA Blocking
6 MEDIUM 互換再換回、只 squash 第一次互換(前段):各版本都回 0,正確是 3;不在 M1 requirements Blocking
7 MEDIUM 決策表第 9 列(暫存檔、main 有同名檔)中間加一次搬移,第 16–19 輪都多列那次搬移 logic Blocking
8 MEDIUM 複製配對的上限反而製造假的搬移:一次換掉上千個產生檔再 squash,第 17、18 輪回 0,第 19 輪回 4;上限算的是配對數不是迴圈次數,仍是平方複雜度 regression, requirements, security Blocking
9 MEDIUM lr18/b1、b2 的清單在第 19 輪多了 b1;DA 指出那是整個沒落地的情況,exit 3 正確、清單差異在已記錄的範圍內,可降為 LOW regression, DA In-scope
10 LOW 文件:M1 第 5 種、CHANGELOG、helper 開頭仍寫 64 KB 上限(最終版已改用過濾後的樹);fixture 217 沒釘住它被引用的複製順序;Linux suite 時間、fixture 220 的限制引用、CHANGELOG 漏了刪除名字那條規則;2400 秒 timeout 沒有餘裕;fuzzer 的「not a prefix」標籤沒檢查分支有沒有搬檔 requirements, regression, logic In-scope
11 INFO 確認過的:#399 的原始形狀、第 18 輪的重現、逐案表、fuzz 計數、版本、suite;安全面;diff 以外的寫入;GIT_CONFIG_COUNT 的 +N、空值的 extensions.partialClone 比 git 嚴格(保守方向) 全部 —

處理方式

第 16 到 19 輪,每一輪為「檔案搬移」加的規則都修好上一輪的形狀,又在別處弄壞原本對的答案;這一輪 DA 還指出,沒有搬移的常見流程(上表第 4 列)從 3.5.0 起就一直誤報。沒有搬移的部分由 bounded.py 逐一比對過,答案自第 17 輪起沒變;不穩定的是搬移那一層。接下來的方向需要維護者決定(見 dashboard)。

…#399)

Round 19 verify failed again (two CRITICAL, one finding: an edit the
branch made before merging main, whose file main had moved, read as
landed), and the devil's advocate found #399's own symptom in a common
flow every version since 3.5.0 got wrong: a first PR, GitHub's Update
branch, main's edit of the first PR's line, a second PR listed the
first PR's commit. Rounds 16 to 19 each fixed the move shapes the round
before broke and broke others. The maintainer decided (2026-11-01) to
stop following files across moves.

- A file is its path. The file identities, readings, copy pairing and
  reconciliation of rounds 17 to 19 are gone (about 740 lines).
- A move counts when git's rename detection inside a non-merge commit
  since the fork point finds one in a commit of the branch the baseline
  lacks, or one anywhere whose old or new name is a path such a commit
  touched. Then: 0 when the tip has the head's content at every path a
  candidate touched and both names of every such move; judged as usual
  when every such move is main's own, after every landing commit used,
  with nothing checked at the tip; exit 4 otherwise, naming the commit
  (never a file name).
- A commit listed at the landing commit is checked again where the
  branch up to a first-parent merge of main into it landed (oldest
  first; the first part that contains it decides). Caches keyed by the
  landing commit.
- bounded_sync.py builds the first PR / Update branch / second PR shape
  (round 19: 238 of 3,192 length-2 cases unexplained).
- Fixtures 221-229; 42 move assertions now expect exit 4 and their list
  assertions are gone; 206 moved to the wide path.
- Suite 397/397 (bash 5; fixtures 224 and 227-229, changed or
  added after it, run one by one on the same helper); bounded_sync.py
  length 2, all 3,192 cases: 0 unexplained (round 19: 238);
  bounded.py plain length 2 in full,
  --landing own and --landing two: every number as in rounds 17-19,
  0 unexplained; fuzzrename2 (353 cases): 147
  exit 4, 205 agree, 1 list that differs by path (exit code right);
  fuzzrename (251): 111 exit 4, 138 agree, 2 D1+D2; mutants
  M1000-M1012: 7 caught, 6 unpinned (test.sh header). CPU time on PR
  #408 about 11 s (round 16: 12 s).
- SPEC (Definition: merges of main, Moves; M1 removed; rows 11-117
  updated where a move counts, rows 118-127; history), CHANGELOG,
  README row, helper header, test.sh header.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 20 輪,本機 commit 7efdc27,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...7efdc27(da47674 起),含本機的第 8–20 輪。六支都跑完。

Aggregate

FAIL — 1 HIGH、6 MEDIUM,另有 15 LOW、9 INFO(合併後 31 則)。沒有退步:每一則 HIGH/MEDIUM 第 19 輪也一樣錯。

確認的:

  • 沒有搬移的歷史答案都沒變,包括 bounded 全部 7,058 個案例,以及本 repo 與 Akashic-Library 的 337 個已 merge PR。
  • 有搬移時,沒有找到錯的 0 或 3,這部分由 git 記成搬移的情況構成,下面 HIGH 的形狀除外。
  • 補回比對沒有把沒落地的 commit 清掉。
  • 第 19 輪的 CRITICAL 形狀現在回 4。
  • 安全面沒有新問題;git 2.30 加 mawk 的 Linux 與 /bin/bash 3.2 都通過。
  • 本 repo 與 Akashic-Library 的 337 個已 merge PR 裡,有 4 個從 0 變成 exit 4(都是分支上的 Spectra archive 搬移;在 merge 當下結案時 P0 仍回 0)。
  • CPU 時間比第 19 輪少。

#399

# Severity Finding Source Action
1 HIGH 假的 clean(第 19 輪一樣):git 在單一 commit 裡沒配成對的搬移不算數。一種是搬走後在舊名放新檔(shim),另一種是 git mv -f 搬到已存在的名字。分支刪掉的行在舊路徑上不見了,就讀成已落地,實際上 main 搬過去的檔裡還有那一行 DA Blocking
2 MEDIUM 第一個 PR 合併後、按 Update branch 前又推了一個 commit(GitHub 上常見的順序),第一個 PR 的 commit 仍被列出:補回比對要求「分支到 merge 的第一個 parent」整段在某處落地 logic, regression Blocking
3 MEDIUM 同一個原因的另一種情況:第一個 PR 本身只收了一部分,完整收下的那個 commit 仍被列出 DA Blocking
4 MEDIUM main 改目錄名,merge 的目錄改名偵測把分支新增的檔帶到新目錄;這個搬移不算數,結果是錯的 3 DA Blocking
5 MEDIUM plugin.json 與 marketplace.json 的 3.5.1 描述仍寫「follow renames」 requirements, codex Blocking
6 MEDIUM 決策寫「搬移或複製」回 exit 4,實作只把 git 記成搬移的算進去,複製照路徑讀 requirements 需要確認範圍
7 LOW 文件:CHANGELOG 仍說沒落地的改名會列出;Results 的 bounded_sync 表把 bounded.py 的表切開;部分量測說法暗示這輪沒跑的 run;exit 4 的列舉、test.sh 開頭、helper 註解仍有身分層的描述;README 漏了「沒有東西在 tip 檢查」;「跨兩個 commit 的搬移照路徑讀」在 squash 後不成立(squash 記成一次搬移,回 4);suite timeout 2400 秒與這輪量到的不符 requirements, logic, regression In-scope
8 LOW 補回比對無法執行時(預算用完、錯誤),清單照列且沒有說明;git 跳過改名偵測時的 exit 4 不在 Moves 的定義裡;main 在落地前搬了又搬回的子句沒有 fixture;身分層的殘留(寫了沒人讀的檔、恆真條件);fixture-128 一個不涉及搬移的清單斷言在批次修改時被刪 requirements, logic, regression In-scope
9 LOW idd-close Step 1.55 的程式片段有一行寫成兩個反斜線,照原樣執行會解析失敗(base 就有,但本 PR 改的正是這一段) DA In-scope
10 INFO 補回比對共用 200 次檢查預算、一律先建好;partial clone 的不 fetch 在 git ≥ 2.45 沒有測試(人工確認正確);bounded_sync.py 只比答案、第一段一律完整落地 logic, security, requirements —

處理方式

第 21 輪:

  • 搬移偵測加上 git 的改寫拆分(-B),讓 shim 與搬到既有名字也記成搬移;main 搬走分支檔案所在的目錄時,那次搬移也算數。
  • 補回比對改成逐 commit 判斷:對每次 merge main,看這個 commit 自己的效果是否已在被 merge 進來的 main commit 上就位,不再要求整段落地。
  • 複製維持照路徑讀,在 SPEC 寫明:沒有找到錯的結束碼,只有清單不同。
  • 文件與 LOW 一併處理。

Round 20 verify found one HIGH and six MEDIUM, none a regression: a
move git pairs only once a rewrite is broken (a file moved with a new
file put at its old name, or moved onto a name that exists) did not
count, and a deletion that never landed read as landed; a directory
main renamed carried the branch's new file along and listed it; the
Update-branch check needed the branch up to the merge to land as a
whole, which fails when a commit was pushed before Update branch or
the first PR left part out; the plugin description still said the gate
follows renames.

- Moves are looked for with git log -B -M; a copy git reports from a
  broken rewrite counts as a move (git breaks only a rewrite of a file
  over 400 bytes, documented). A move that takes a file out of a
  directory where the branch added one counts.
- A commit listed at the landing commit is checked again in place at
  the main commit each first-parent merge of main brought in (any of
  them decides), not where the branch up to the merge landed.
- bounded_sync.py: first PRs that take part of the first part
  (commits pushed before the merge, squashes that left some out).
- Fixtures 230-237; fixture-128's list assertion restored.
- Suite 408/408 (bash 5; fixtures 235-237 and the larger 237 run
  one by one on the same helper); bounded_sync.py length 2, all 6,264
  cases: 0 unexplained (round 20 on the 3,072 added cases: 472);
  bounded.py plain length 2 in full, --landing own and --landing two:
  every number as in rounds 17-20; fuzzrename2 (353): 147 exit 4, 205
  agree, the same 1 list that differs by path; fuzzrename (251): 115
  exit 4, 134 agree, 2 D1+D2, 0 false; mutants: 13 caught, 4 unpinned
  (test.sh header). CPU time (load 7-27): PR #408 7.7 s (round 16:
  8.5 s), a squash-landed 150-commit branch 16.9 s (round 16: 20.7 s).
- Docs: plugin.json and marketplace.json 3.5.1 description, CHANGELOG
  (moves, Update branch, deletions bullet, suite timing), README row,
  SPEC (Moves, rows 128-135, Results, history), helper header and
  identity leftovers removed, test.sh header; idd-close Step 1.55's
  pipeline continuation had two backslashes; suite timeout 3600 s.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 21 輪,本機 commit 84baa09,未推送)

Engine

pai-ensemble 2.25.1 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-6.1-sol, effort medium), model: opus。範圍是 origin/main...84baa09,含本機的第 8–21 輪。六支都跑完。

Aggregate

FAIL — 5 HIGH、13 MEDIUM,另有 14 LOW、5 INFO(合併後 37 則)。有退步:3 個 HIGH 是第 21 輪自己加的 -B 與目錄規則造成的,第 20 輪答對。

確認的:

#399

# Severity Finding Source Action
1 HIGH 退步:-B 把改寫過的檔也算成 rename 的來源,大的 commit 因此超過 rename 上限,git 跳過偵測。helper 判斷「git 有沒有跳過」只算新增×刪除,沒有算到這些檔,結果第 20 輪的 exit 4 變成錯的 0 或 3 requirements, regression, logic, security Blocking
2 HIGH 假的 clean:小於約 400 bytes 的檔,搬走後在舊名放一行 shim,-B 不會拆開它,分支刪掉的行讀成已落地 requirements Blocking
3 HIGH 退步:目錄規則把「main 從分支新增檔案的目錄搬走一個兄弟檔、目錄還在」也算成搬移。完整 squash 變成 exit 4,真的 orphan 也被 exit 4 蓋掉(第 20 輪分別是 0 與 3) logic, requirements, regression Blocking
4 HIGH 假的 clean:Update branch 時 merge 把分支的新檔帶到 main 改名後的目錄,P0 只比舊名,新檔沒落地也回 0(第 19、20 輪一樣) logic(DA:預設設定下這是人解衝突,應為 MEDIUM) Blocking
5 MEDIUM 目錄規則只看直接的上層目錄;merge-ort 也會從子目錄裡的搬移推出上層目錄改名(例如目錄裡只有子目錄),結果是錯的 3,Update branch 後是錯的 0 Codex, requirements, logic Blocking
6 MEDIUM 退步:第二次比對只看 merge 進來的 main commit。main 在 Update branch 前改了第一個 PR 的某一行、merge 保留分支那邊時,第 20 輪回 0,第 21 輪列出 logic Blocking
7 MEDIUM git 跳過 rename 偵測、Update branch 又把分支的修改帶到新檔名時,P0 在 unc 檢查之前就回 0(第 19–21 輪一樣) DA Blocking
8 MEDIUM 退步(效能):-B 讓每次執行都讀過 fork 以來所有改過的 blob。Akashic-Library 的 PR 平均約慢一倍,常改大檔的 repo 線性變慢;blobless partial clone 的 exit 4 幾乎加倍 regression, security Blocking
9 MEDIUM 決定寫「搬移或複製」,實作把複製照路徑讀,沒有記錄確認 requirements 需要確認範圍
10 MEDIUM 目錄規則的前提:git 預設設定下 merge 停在 CONFLICT (file location),要人接受建議才會搬;fixture 231 把衝突訊息丟掉 DA Blocking
11 MEDIUM -B 把分支自己的拆檔(複製後把原檔改成 shim)算成搬移,exit 4(第 20 輪 0 且正確) logic(DA:在決定範圍內) 見處理方式
12 LOW 文件:400 bytes 的門檻差一、實際是 max(舊, 新);CHANGELOG「只有這些」的封閉清單沒跟上;test.sh 開頭新舊兩段互相矛盾;SPEC 的 fixture 範圍與 bounded_sync.py 說明過時;第 133 列的 S 欄與定義矛盾;exit 4 的說明句不符目錄規則與複製;方法說明殘留身分層的用語 requirements, logic, regression In-scope
13 LOW main 自己在落地後的搬移照常判斷,超出決定「落地前後」的字面,沒有記錄確認 requirements 需要確認範圍
14 LOW 第二次比對把「main 後來也 merge 進去的別的分支」當成 merge main;一個 commit 分兩次落地仍被列出 logic 已知限制
15 LOW 實際歷史:-B 多一個 0→4(Akashic-Library #736,內容拆分) regression 見處理方式
16 INFO exit 4 的 fixture 只斷言結束碼,設定錯誤也會通過 regression In-scope

處理方式(第 22 輪)

  • 拿掉 -B,搬移偵測分三段:
    • 每個非 merge commit 跑 -M,和第 20 輪相同。另外用 blob id 找「刪掉的檔,內容和同一 commit 改過的另一個檔完全相同」,也就是搬到既有的名字。
    • 只對「新增檔案、又改了檔案,且屬於分支或碰到分支路徑」的 commit 跑 -C。從同一 commit 改過的檔複製出去的檔(shim、拆檔)算數。
    • 分支 first-parent 上的每個 merge,對它的第一個 parent 跑 rename 偵測。分支的檔在 merge 裡被搬走就算數。
  • 「git 跳過偵測」照 git 自己的條件算:複製的來源也包含改過的檔。git 警告了跳過、helper 卻算不出是哪個 commit 時,直接 exit 4。有這種 commit 刪掉(或改了)candidate 碰過的路徑時,不走 P0。
  • 目錄規則只在搬移後原目錄已經不存在時才算,並照 merge-ort 的方式往上層推。用目錄規則找到的搬移不放進 P0 比對的名單。
  • Update branch 的第二次比對兩處都看:「分支到 merge 為止」落地的地方(第 20 輪的比對),以及 merge 進來的 main commit(第 21 輪)。任一處就位就算落地。
  • 複製:原檔在同一 commit 有改動的複製算數。原檔沒變的複製照路徑讀,因為原檔還在原名。這是對決定裡「複製」的解讀;這一點與第 13 項會請維護者確認。
  • LOW 的文件問題一併處理,第 14 項記為已知限制。

…at is read (#399)

Round 21 verify found five HIGH and thirteen MEDIUM, three of the HIGH
regressions of round 21's own: -B made every rewritten file a rename
source, so a wide commit passed git's rename limit and the skip went
uncounted (a move read as a deletion: 0 or 3); the directory rule
counted a sibling moved out of a directory that stays; -B read every
changed blob since the fork point. Also a shim at a small file's old
name read as clean; a merge of main that moved the branch's file read
as clean through P0; a commit whose line main edited before Update
branch, the merge keeping the branch's side, was listed again.

- Renames come from -M in every non-merge commit (round 20's pass);
  copies from -C, run only on a commit that adds a file and changes
  one, of the branch or touching a path it touched, and counted when
  that commit replaced the original's content (more than half its
  lines gone); a move onto a name that exists from the blobs.
- Renames inside the merges on the branch's first-parent line count
  when they move a path the branch touched.
- The directory rule counts only a directory taken away, walking up as
  merge-ort does, and its names stay out of P0.
- git's skipped detection is counted with git's own sources; a warning
  the count cannot place stops the check; P0 is not taken past a commit
  git did not look in that deleted (or replaced) a candidate's file.
- The Update-branch check also looks where the branch up to the merge
  landed (round 20's check) besides the merged main commit.
- bounded_sync.py --pre-edit: main edits the first PR's line before the
  merge; B2 covers main's own edit taken as the landing commit.
- Fixtures 238-248; 231 asserts the squash's file-location conflict;
  rc_why checks the exit-4 reason.
- Suite on the round's helper (fixtures 1-247) 433/433, fixture 248 run
  alone; bounded.py plain length 2 in full, --landing own and --landing
  two: every number as in rounds 17-21; bounded_sync.py 6,264: as round
  21; --pre-edit 20,352: 17,772 agree, tip 2,264, B2 316, 0 unexplained;
  fuzzrename2 (353): 147 exit 4, 205 agree, the same 1 list; fuzzrename
  (251): 114 exit 4, 135 agree, 2 D1+D2, 0 false; mutants: 21 caught, 3
  unpinned. The 337 merged PRs of this repository and Akashic-Library:
  all 0 at close time; against today's main 5 exit 4 (round 20: 4),
  about 1 % and 7 % more CPU than round 20; a blobless clone: 60 of 102
  exit 4 at close time (round 20: 37, round 21: 71).
- Docs: helper header, SPEC (Moves, rows 128-144, small world, history,
  results), CHANGELOG, README row, plugin.json and marketplace.json,
  test.sh header.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 22 輪,本機 commit 28e6cb1,未推送)

Engine

pai-ensemble 2.25.1 — 4 IDD lenses + DA + Codex。DA 沒跑完(帳號週額度用完),所以這輪沒有對抗審查。run-all-tests 在這個 commit 通過。

Aggregate

FAIL — 4 個實質 HIGH(另 1 個 HIGH 是 DA 未完成)、9 MEDIUM、11 LOW。第 22 輪加的規則又造成沒有搬移的歷史改變答案。

# Severity Finding Source
1 HIGH 退步:git 跳過偵測的 commit 碰到 candidate 的路徑時不走 P0,head 與 tip 整棵樹相同、沒有任何搬移也回 exit 4(第 20、21 輪回 0) requirements, logic
2 HIGH 目錄分兩個 commit 清空時目錄規則不算數,回錯的 3(第 21 輪回 4) logic
3 HIGH 分支的檔由非 first-parent 的 merge 帶進改名目錄時,P0 回錯的 0(第 20、21 輪相同) logic
4 HIGH 退步:複製規則把非搬移算進去(留一份舊檔再改寫原檔的 squash、小檔範本),回 exit 4 並蓋掉真的 orphan logic
5 MEDIUM 退步:格式化大 commit、內容相同的小檔被當成搬移;目錄之後又重建仍算數;replaced() 沒固定 diff 選項;git 2.30 上跳過偵測的計數不符、fixture 246 失敗 requirements, logic, security, regression
6 MEDIUM SPEC 的封閉偏差清單漏列 Update branch 檢查的兩個限制 requirements
7 LOW 文件細節、replaced() 差一、binary 複製、--load 用 pickle、blobless 的說明 多個

判斷

第 20 輪之後,第 21、22 輪為抓 shim、搬到既有名字、目錄改名而加的偵測,都在沒有搬移的歷史上造成新的錯誤。範圍問題交由維護者決定。

Round 22 verify found four HIGH, and again answers changed for histories
without moves. The maintainer decided on 2026-11-02 to shrink back.

- Moves count from -M in non-merge commits and inside the branch's
  first-parent merges. Copy detection, blob pairing, the directory rule,
  their counts, the warning check and the P0 guard are gone.
- Copies, shims, moves onto existing names and renamed directories are
  known limits in SPEC; fixtures pin them.
- Both Update-branch checks stay. bounded_sync.py drops --save/--load.
- Suite 430/430. bounded.py and bounded_sync.py (6,264; --pre-edit
  20,352): 0 unexplained. Fuzzers: no wrong exit code. 337 merged PRs:
  every answer as round 20, same CPU.

Refs #399
…isted (#399)

Round 23 verify (devil's advocate): the second check cleared a commit
that sets a line back to its merge-base value and never landed, after
a merge of main; the merged main commit never touched the line. It now
counts only when the commit is not already in place at that merge-base.
Also: P0's list keeps a newline inside a name; the docs' closed list of
limits, and leftovers of rounds 21-22. Fixtures 249, 250. Suite 435/435.

Refs #399
)

Round 24 verify: the guard missed the check where the branch up to the
merge landed, and a set-back hunk mixed with a real edit. The second
check now skips any commit with a hunk that only sets lines back to the
merge-base's. Repeated text can leave a landed commit listed, the safe
direction. Fixture 251. Suite 437/437.

Refs #399
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #418(第 23–25 輪,本機,未推送)

  • 第 23 輪:1 HIGH。第二道檢查放行把一行還原成 merge-base 值、從未落地的 commit。
  • 第 24 輪:3 HIGH,同一根因,防護漏了步驟 (a) 與混合 commit。
  • 第 25 輪:FAIL。sets_back 讀到被引號包住的檔名(中文檔名時防護失效)、把空白行算進去,這兩項已修,fixture 252。未解:還原與相鄰的真實修改落在同一 hunk 時仍誤清;新增一行重複文字(如 })時第一個 PR 的 commit 又被列出。

第 25 輪的 hunk 問題沒有兩全的判準:逐行判斷會把分支改寫自己的行誤認為還原,那正是 #399 的症狀。需要維護者決定範圍。

337 個 PR 的重放在第 23 輪與第 20 輪完全相同;第 24 輪之後的重放沒有跑完。

No criterion separates a set-back from a branch rewriting its own line.
Pending the maintainer's acceptance of the limit.

Refs #399
… limits (#399)

Round 26 verify. Fixtures 253, 254.

Refs #399
…olving (#399)

Round 27 verify. Closed lists of limits brought in line.

Refs #399
@kiki830621
kiki830621 marked this pull request as ready for review November 3, 2026 04:18
@kiki830621
kiki830621 merged commit e4a656d into main Nov 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant