Skip to content

idd-edit --correct:未關閉的 <details> 能把後面的內容收進摺疊區 #409

Open

Description

@kiki830621

問題

/idd-edit --correct 的 compose-correction 會檢查「更正範圍以外的解析結果不變」,但它用的是 CommonMark parser:fence 與 HTML 註解留著沒關會被擋下,<details> 卻不會。GitHub 渲染時,一個沒關閉的 <details> 會把它後面的內容(包括 ## Current Status)收進摺疊區;同一手法也能把 Original text 引文藏進摺疊區,而逐字比對照樣通過。

預期

更正後的內文,在 GitHub 的渲染下,被取代區段以外的內容可見性不變;Original text 引文不被收進摺疊或隱藏的區塊。可能的做法:替換內容的 <details> 必須成對;或以 GitHub 的渲染結果(API 的 HTML)比對。

來源:#351 verify 第 4 輪(表 #14、#10 的 <details> 部分)。

Refs #351


Current Status

Phase: diagnosed
Last updated: 2026-10-26 by idd-diagnose(雲端 session,批次診斷;body 經 REST 更新)

Key Decisions

  • Complexity = Plan → 下一步 /idd-plan(Plan 核准後再實作)(Diagnosis)
  • Conflict Class = C_shared_module_coord

Scope Changes

Blocking

  • (none)

Commits

  • (none)

Generated by Claude Code

Activity

  1. kiki830621 commented on Oct 26, 2026

    @kiki830621
    MemberAuthor

    Diagnosis

    Type

    bug

    Root Cause / Analysis

    根因:兩道檢查都以 CommonMark 的 parse 結果定義「沒有改變」,而 <details> 改的是渲染、不是 parse。

    1. compose-correction 的不變量(plugins/issue-driven-dev/scripts/idd-edit-helper.py:1441-1468)只比對替換範圍之後每一行是否仍在 code/HTML block 裡(opaque)、是否仍是標題(headings),以及標記行是否仍是獨立的 HTML block。_Blocks(:785-834)只記錄 fence/code_block/html_block 的範圍、頂層標題與分隔線,沒有任何 HTML 元素巢狀的資訊。

    2. CommonMark 裡,以 <details> 開頭的行開始一個 type 6 HTML block,遇到第一個空行就結束。所以沒關閉的 <details> 只讓它自己那一小塊變成 opaque,後面每一行 parse 結果完全不變,不變量照樣通過。瀏覽器(以及 GitHub 處理後的 HTML)則依 HTML 的解析規則,讓沒關閉的 <details> 元素包住其後到正文結尾的全部內容,而且預設是收合的。

    3. check-correction 的第 (6) 條(:1333-1339)把「藏起來」定義為 verbatim block 有一行落在 code 或 HTML block 裡(_verbatim_runs,:1134-1141)。<details> 後空一行再接 Original text 引文時,引文是頂層 blockquote,不算藏起來。這在 <details> 有正確關閉時也成立:用一對 <details>…</details> 包住原樣保留的引文,逐字比對與 hidden 檢查都通過,引文卻被收進摺疊區。所以 issue 提到的「<details> 必須成對」只能涵蓋第一個預期(範圍外的可見性),涵蓋不到第二個(引文不被收合)。

    4. spec 與實作一致。openspec/specs/in-place-correction/spec.md:103:

      compose-correction SHALL refuse with exit 6 when the composed body would parse differently outside the replaced region

      :45 的第 (6) 條:

      the replacement places a verbatim block inside a code block or an HTML block where the original did not

      缺口在不變量的定義(parse 層),不在實作偏離 spec。

    重現(throwaway probe:直接呼叫 main 上 helper 的 check-correction 與 compose-correction;正文為 ## Problem(含 Original text 引文)/## Expected/---/## Current Status。「渲染」欄是用 markdown-it-py 4.0.0 render 後交給 lxml(libxml2 HTML parser)建 DOM 的結果,作為瀏覽器的替代,沒有呼叫 GitHub 的 render API 驗證):

    替換內容 目標段落 check compose ## Current Status 在 <details> 裡 引文在 <details> 裡
    new expected+<details><summary>…+內文,沒有 </details> ## Expected 0 0 是 否
    同上,有 </details> ## Expected 0 0 否 否
    一對 <details> 包住原樣的引文,後接新解讀 ## Problem 0 0 否 是
    新解讀後接沒關閉的 <details> 與原樣的引文 ## Problem 0 0 是 是

    四種都被接受。對照組(原文)兩欄都是「否」。

    寫入後會一直留著:idd-update Branch A 只重寫 ## Current Status 以下,沒關閉的 <details> 在它上面那一節,所以之後每次同步 Current Status 仍是收合的。自動化不受影響(idd-all 以逐行比對讀 Current Status,skills/idd-all/SKILL.md:1240),受影響的是人類讀者。--scope whole-comment 之後沒有其他內容,只剩「把引文收合」這一種情形;comment 的 --section 則會收合之後的各節。

    Existing work

    沒有處理本 issue 的 PR 或 branch(Step 1.6:clear)。相關的進行中工作:本機實作、尚未 push 的 #424(branch idd/424-living-issue-write-back)讓 --correct 不再確認、偵測 skill 直接套用更正,並改了同一份 in-place-correction spec(不同的 requirement)。

    Impact

    • 影響的檔案:plugins/issue-driven-dev/scripts/idd-edit-helper.py(_Blocks、_compose 的不變量、_verbatim_runs/check 第 (6) 條)、openspec/specs/in-place-correction/spec.md(:45 第 (6) 條、:103 不變量)、skills/idd-edit/SKILL.md:84(閘門表)、scripts/tests/in-place-correction/test.sh
    • 影響的使用者流程:/idd-edit --correct --section(issue 正文與 comment);/idd-comment --type errata 委派給 --correct 的路徑

    Strategy

    • 先寫測試(scripts/tests/in-place-correction/test.sh,比照 H10、G1):(a) --section 替換留下沒關閉的 <details>、其後有 --- 與 ## Current Status → compose exit 6,prepare 記為 REASON=unclosed-block;(b) 成對 <details> 只包新文字 → 接受;(c) 成對 <details> 包住原樣的 Original text 引文 → check exit 6;(d) 原文裡引文本來就在 <details> 裡、替換照樣保留 → 接受;(e) 替換拿掉一個用來關閉範圍外 <details> 的 </details> → 拒絕;(f) 大小寫與屬性(<DETAILS open>)、段落內的行內 <details>
    • _Blocks 加上每一行的 <details> 深度:掃 html_block 與段落內 html_inline token 的內容,比對 <details\b[^>]*> 與 </details\s*>(不分大小寫)
    • compose 不變量:範圍外每一行的 <details> 深度,替換前後要相同(併進 :1457-1461 的迴圈),沿用 exit 6/REASON=unclosed-block
    • check 第 (6) 條:verbatim block 有一行的深度 > 0、而原文沒有時,視同藏起來
    • 更新 spec(:45 第 (6) 條與 :103 的不變量文字)與 skills/idd-edit/SKILL.md:84 的閘門表;CHANGELOG
    • 另一種做法(issue 也提到):以 GitHub render API 的 HTML 比對前後。prepare 與 apply 的重新檢查都要多一次網路呼叫、失敗時要 fail closed、fake gh 要支援 /markdown,仍需另做 DOM 分析。建議採本機 parser 的深度不變量,在 Plan 審核時定案

    Conflict Class

    C_shared_module_coord
    共享資源:plugins/issue-driven-dev/scripts/idd-edit-helper.py(_Blocks、_compose、_verbatim_runs)與 openspec/specs/in-place-correction/spec.md 的 "Protected regions" requirement;#410 改同一個 requirement 與同一組 verbatim 函式,#407、#406 改同一支 helper,#424 改同一份 spec,要相鄰序列化。

    Complexity

    Plan — Layer 2 不成立:這是收緊拒絕條件,遵守契約的呼叫端不受影響(Plan-Spectra line 表的 "Tighten input validation that callers already conform to"),spec 裡對應的一條在同一個改動中補上。Layer P 命中:decision-heavy(issue 自己列了兩種做法,選哪一種決定 prepare/apply 是否多一個網路依賴);risk-sensitive(這是保護原話引文與受管區的防竄改閘門)。
    Hard-gate: not triggered — 同一概念涉及 helper、spec、skills/idd-edit/SKILL.md、scripts/tests/in-place-correction/test.sh 四個檔案(另有 CHANGELOG),未達 5;_Blocks 只在 scripts/idd-edit-helper.py 內部使用,沒有其他檔案引用

    Risks

    Residue

    GitHub 的渲染才是真正的判準,本機 parser 無法對所有 HTML 構造證明等價;issue 說的「隱藏的區塊」在這裡落實為 <details>(code block 與 HTML 註解已有檢查)。

    Open questions(交給 /idd-plan)

    • Approach (can also be settled at the Plan approval gate): (a) local
      Details-depth invariant from the pinned markdown-it-py parser — tail depth unchanged + a verbatim block at depth>0 counts as hidden (recommended: offline, deterministic, matches the existing parser-based design) / (b) compare GitHub's rendered HTML (POST /markdown) before and after — ground truth, but adds a network call to prepare and to apply's re-check, must fail closed, needs fake-gh support and DOM analysis.
    • Note for the maintainer: a balanced
      Details pair wrapping an unchanged 'Original text' quote also passes today, so 'require
      to be paired' alone does not meet the issue's second expectation.

    Sister Concerns Filed (mid-diagnose, v2.47.0+ #528)

    Source: surfaced during /idd-diagnose #409 sister concern surfacing (Step 3.6)

    Vagueness Pre-check

    • V1: 2 — 預期寫成兩個可檢查的不變量(替換範圍以外的可見性不變;Original text 引文不被收進摺疊或隱藏的區塊),也列出兩種可能做法。
    • V4: 3 — 可以用 fixture 驗證,但「隱藏的區塊」除了 <details> 之外還包括哪些機制,沒有封閉列出。
    • Triggered: no — both axes ≤ 3

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions