Skip to content

test(review): pin the fold invariant the per-line escape rests on - #11533

Merged
wenshao merged 3 commits into
mainfrom
fix/review-fold-invariant
Sep 10, 2026
Merged

test(review): pin the fold invariant the per-line escape rests on#11533
wenshao merged 3 commits into
mainfrom
fix/review-fold-invariant

Conversation

@wenshao

@wenshao wenshao commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

The case added here drives every model-written channel of composeReview with a multi-line entry — LF, CRLF and a lone CR — and asserts what a reader ends up with: the entry's head and tail on the SAME posted line, no line break left inside it, and its tag inert. It replaces a first version whose assertions could not fail, and it rewrites the three comments beside it so that each sentence is one an experiment can settle. Outside the tests the diff is comments only; no executable line changes.

Why it's needed

escapeTagOpeners models CommonMark INLINE structure only. That is correct exactly while every channel folds its text to one line before posting it — and nothing in the code enforced that. The fold is a convention held at several sites, and scriptLintGate already proved a channel can appear and be missed: it joins bodyCriticals AFTER ingestEntryList has run, and for a while it did not fold, so a fenced block in a side file the review agent can rewrite reached the per-line escape as several lines and rendered a literal < to a reviewer (fixed in #9940).

Review then showed the guard added for that was itself decorative. Two of its three assertions could not fail: body.split('\n') does not split on a bare \r, and the filter keeps the entry's first physical line, so the line count read 1 whatever happened to the rest; and < can only appear if a model wrote those seven characters itself, since the escape emits < and nothing in the source emits &. Four reverse-audit rounds then found the same class of defect four more times, each in a sentence written as MEASURED that had not been — a comment blaming cost where the cost was measurably the other way, a miscounted row, a producer the case never reached, and a fold credited to the wrong holders. This PR is the correction, and every claim it now makes is attached to a mutation that reddens it.

Reviewer Test Plan

How to verify

Run the suite: cd packages/cli && npx vitest run src/commands/review119 files, 6132 passed, 19 skipped.

The point of the change is that the case is load-bearing, so the evidence is mutation, not a green run. Break one fold at a time and the case reddens — each of these was run at this head:

collapseEntry's fold removed (compose-review.ts)
  AssertionError: expected [ 'bodyCriticals', false ] to deeply equal [ 'bodyCriticals', true ]
  Tests  1 failed | 738 skipped (739)

the relocation exit's fold removed (compose-review.ts, the `mdField(boundDeferredLine(...))` wrap)
  AssertionError: expected [ …(2) ] to deeply equal [ …(2) ]     ← deferredSuggestions (relocated)
  Tests  1 failed | 738 skipped (739)

the downgrade reasons' `\s+` pass removed (compose-review.ts)
  AssertionError: expected [ 'downgradeReasons', false ] to deeply equal [ 'downgradeReasons', true ]
  Tests  1 failed | 738 skipped (739)

Three of the eight rows are held by more than one fold and so do NOT redden on a single-site break — the deferral list, the cannot-tell leg, and the relocation exit, which is held three times over (collapseToLine at toDeferredEntries and again inside boundDeferredLine, then mdField). Breaking any two of those three leaves the row folded; breaking all three reddens it. That is stated in the comment rather than left for a maintainer to discover, because a double-held row must not be read as a sentinel for its own folds.

Against the version this replaces, the old assertions stayed green under a mutation that removed every fold in the module; the new ones do not.

Evidence (Before & After)

N/A — nothing here is user-visible. The only rendered difference this change protects against is one the tests now assert directly.

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux

Linux locally: the review suite above, npx vitest run scripts/tests/qwen-autofix-workflow.test.js (249 passed; the two failures are the known environmental pair — a heartbeat timing case and a chmod-as-root case that cannot fail as root), plus tsc, eslint and prettier --check over packages/cli/src/commands/review. macOS and Windows are covered by CI.

Environment (optional)

Unit tests only.

Risk & Scope

  • Main risk or tradeoff: none to behaviour — outside the tests the diff is comments. The risk this PR is spending is reviewer attention on comment text, in exchange for comments a maintainer can act on.
  • Not validated / out of scope: the second recorded boundary PINS behaviour the comment beside it calls wrong. rendersAsNothing('&nbsp;<?x>') is asserted true, while the note says those characters are VISIBLE to the renderer and calling the body empty is wrong. That is deliberate: splitting the <!/<? arms by position was tried and reverted, because being right about it needs a container's EXTENT, which scanLines discards. A future fix has to unpick that pin. The corpus numbers recorded with it (121 → 116 wrongly-empty against 1426 → 1730 wrongly-visible for the split alone; 121 → 3594 wrongly-empty once container prefixes are taught without their extent) are from a one-off corpus that was not kept — they are the shape of the trade, not a benchmark to re-run, and the comment says so. The gate's own push is the one channel the new case does not arm; it needs a report fixture beside the plan and stays pinned by folds its own entry.
  • Breaking changes / migration notes: none.

Linked Issues

Follow-up to #9940.

中文说明

这个 PR 做了什么

新增的用例用多行条目(LF、CRLF、单独 CR)驱动 composeReview 的每一条模型撰写渠道,断言的是读者最终拿到的东西:条目的头和尾在同一条已发布行上、行内不残留换行、其中的标签失效。它替换掉的第一版,断言根本不可能失败。同时重写了旁边三处注释,让每一句都能被实验判定。测试之外,改动全是注释,没有任何可执行行发生变化。

为什么需要

escapeTagOpeners 只建模 CommonMark 的行内结构。这在"每条渠道在发布前都已折成一行"的前提下才成立——而代码里没有任何东西强制它。折行只是散落在若干站点上的约定,scriptLintGate 已经证明渠道会出现并被漏掉:它在 ingestEntryList 之后才并入 bodyCriticals,且曾有一段时间没有折行,于是评审 agent 可改写的旁路文件里的围栏块以多行形态进入逐行转义,给审阅者渲染出字面的 &amp;lt;(已在 #9940 修复)。

随后评审指出,为此添加的守卫本身是装饰性的:三条断言里有两条不可能失败——body.split('\n') 不会在单独的 \r 处切分,而过滤器只保留条目的第一条物理行,所以无论后面发生什么行数都读作 1;而 &amp;lt; 只有在模型自己写下那七个字符时才会出现,因为转义只发射 &lt;,源码里没有任何地方发射 &amp;。之后四轮反向审计又在同一类问题上抓到四次,每次都是一句写成 MEASURED 却并未测过的话——把理由归为成本而成本实测方向相反、数错行数、有一条生产者用例根本走不到、把折行归给了错误的持有者。本 PR 就是这些的更正,且它现在做的每一个断言都配有能让它变红的变异。

复核方式

跑套件:cd packages/cli && npx vitest run src/commands/review119 files, 6132 passed, 19 skipped

本次改动的意义在于用例真的承重,所以证据是变异而不是一次绿色运行。逐个破坏折行,用例即变红(上面英文部分是在本 head 上实际跑出的输出)。

八行里有三行被多重折行持有,因此不会在单点破坏下变红——deferral 清单、cannot-tell 腿,以及被持有三次的 relocation 出口(toDeferredEntriesboundDeferredLine 内各一次 collapseToLine,再加 mdField)。任断其二仍是折好的,三个全断才红。这一点写进了注释而不是留给维护者去发现,因为双持行不能被当作它自己那些折行的哨兵。

与被替换的版本相比:旧断言在"移除模块内全部折行"的变异下依然全绿,新断言不会。

风险与范围

  • 主要风险/取舍:对行为没有风险——测试之外全是注释。本 PR 花掉的是审阅者读注释文本的注意力,换来的是维护者可据以行动的注释。
  • 未验证/超出范围:第二条被记录的边界钉住了它旁边注释自己称为错误的行为rendersAsNothing('&nbsp;<?x>') 被断言为 true,而注释说这些字符对渲染器而言是可见的、把该正文判为空是错的。这是刻意为之:按位置拆分 <!/<? 分支已试过并回退,因为要做对需要容器的 extent,而 scanLines 把它丢弃了。将来的修复必须解开这个 pin。与之一并记录的语料数字(仅拆分:误判为空 121 → 116,误判可见 1426 → 1730;教会容器前缀但没有 extent 时:误判为空 121 → 3594)来自一次性、未保留的语料——它们表示这笔取舍的形状,不是可重跑的基准,注释里已写明这一点。gate 自身的 push 是新用例唯一未武装的渠道,它需要计划旁的报告 fixture,仍由 folds its own entry 钉住。
  • 破坏性变更/迁移说明:无。

关联 issue

#9940 的后续。

`escapeTagOpeners` models CommonMark INLINE structure only. That is correct
exactly while every model-written channel folds its text to a line before it,
and the fold lives at five sites, not one — `ingestEntryList` for the entry
channels, the `\s+` normalisation for downgrade reasons, `collapseEntry` for
the `Not reviewed:` disclosures, `scriptLintGate`'s own push, and compose's
refusal of a line break in a ruling note's `by`. `scriptLintGate` is the proof
that a sixth channel can appear and be missed: it joins `bodyCriticals` after
`ingestEntryList` has run, and for a while it did not fold, so a fenced block
in a side file the review agent can rewrite reached the escape as several
lines and rendered a literal `&amp;lt;` to a reviewer.

Nothing in the code enforced that. This adds the guard: one case drives every
channel with a multi-line entry — LF, CRLF and a lone CR — and asserts the
CONSEQUENCE, so a new channel that skips the fold reddens here instead of
mangling a review. It also pins the two channels that REFUSE rather than fold
(a quoted fence on either Critical list, and a ruling note's `by`), and the
one that renders as a code span, where an escape would show the reader a
literal `&lt;` instead. Removing the fold from `scriptLintGate` or from the
disclosures reddens it.

Two boundaries that reverse audits keep rediscovering are recorded rather than
changed, both measured against cmark-gfm — the renderer GitHub runs — and an
HTML parser:

- A reference link's LABEL is ordinary inline text: a backtick inside
  `[a`b][c]`, `[x][a`b]`, `[a`b]` or a link reference definition's own label
  is a code-span delimiter like any other, and the tag it pairs over is code.
  Two audits reasoned the other way and neither could build a counterexample;
  the rows say so now.
- `rendersAsNothing` is fold-blind and position-blind on purpose. An arm that
  deleted a `<details>`'s content would delete a quoted `` `<details>` `` too,
  which is the shape a review of that file writes — it loses a real finding to
  catch a readable one. And splitting the `<?` arm by position was tried and
  REVERTED: on a prose-weighted corpus of 30000 bodies the split alone traded
  121 -> 116 bodies wrongly called empty for 1426 -> 1730 wrongly called
  visible, and teaching it the container prefixes then needs the container's
  EXTENT as well — without that, 121 -> 3594. Being right there needs the
  block structure the module deliberately does not model.
@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wenshao — this run stops at the template gate, so this is the only output you'll get from it. No code review yet.

The body doesn't carry the required headings from .github/pull_request_template.md. It has two headings of its own — ## What this adds and ## Two boundaries recorded rather than changed — and the bilingual 中文说明 block is present, but all eight required sections are missing:

  • ## What this PR does
  • ## Why it's needed
  • ## Reviewer Test Plan, with ### How to verify, ### Evidence (Before & After), ### Tested on
  • ## Risk & Scope
  • ## Linked Issues

The prose is already most of the way there, so this looks like a re-heading job rather than a rewrite — the opening paragraphs are the "why" and ## What this adds is the "what". Two of the missing sections are where this PR specifically owes content, though:

  • Reviewer Test Plan → How to verify. The entire change is tests, so mutation evidence is the only thing that shows they're load-bearing rather than decorative. The body already claims that removing the fold from scriptLintGate or from the disclosures reddens the new case — that belongs under How to verify with the actual failing output pasted in, not asserted in prose. ### Evidence (Before & After) can be N/A since nothing here is user-visible, and ### Tested on is just the platforms you ran the suite on.
  • Risk & Scope. The second recorded boundary pins behaviour your own comment calls wrong-but-deliberate: rendersAsNothing('&nbsp;<?x>') asserted true, while the note above it says those characters are VISIBLE in the renderer and calling the body empty is wrong. Pinning that is a defensible call, but it's a tradeoff a future fix has to unpick, and "Not validated / out of scope" is exactly where it should be on the record.

## Linked Issues can stay a non-closing reference — "Follow-up to #9940" reads right, and I confirmed it currently resolves to no closing reference, which is what you want for a follow-up.

Worth saying this is a lapse rather than a habit: #11524 and #11516 both carried the full template. Update the body and re-run /triage — the review picks back up at Stage 1.

中文说明

@wenshao — 本次运行停在模板关卡,所以这是它唯一的输出,还没有进入代码审查。

PR 正文没有带上 .github/pull_request_template.md 要求的标题。正文有两个自己的标题——## What this adds## Two boundaries recorded rather than changed——双语的 中文说明 块也在,但八个必需章节全部缺失:

  • ## What this PR does
  • ## Why it's needed
  • ## Reviewer Test Plan,含 ### How to verify### Evidence (Before & After)### Tested on
  • ## Risk & Scope
  • ## Linked Issues

正文的散文其实已经完成了大半,所以这更像是改标题而不是重写——开头几段就是"为什么",## What this adds 就是"做了什么"。但缺失的章节里,有两个是这个 PR 特别需要补内容的:

  • Reviewer Test Plan → How to verify。 整个改动都是测试,所以变异证据(mutation evidence)是唯一能说明这些用例真正起作用、而不是摆设的东西。正文已经声称:把 scriptLintGate 或披露渠道的折行去掉,新用例就会变红——这句话应该放到 How to verify 下面,并贴上真实的失败输出,而不是只在散文里断言。### Evidence (Before & After) 可以写 N/A,因为这里没有用户可见的改动;### Tested on 就写你实际跑过测试的平台。
  • Risk & Scope。 第二条被记录的边界钉住的是你自己的注释都称之为"错但故意"的行为:rendersAsNothing('&nbsp;<?x>') 断言为 true,而它上方的注释说这些字符在渲染器里是可见的,把正文判为空是错的。钉住它是一个可以辩护的选择,但这是一个将来修复时必须先解开的取舍,而"Not validated / out of scope"正是它该被记录在案的地方。

## Linked Issues 可以保持非关闭式引用——"Follow-up to #9940" 是合适的,我也确认了它目前不产生任何关闭引用,这对后续 PR 来说正是想要的效果。

需要说明的是这是一次疏漏而非习惯:#11524#11516 都带全了模板。更新正文后重新运行 /triage,审查会从 Stage 1 继续。

Qwen Code · qwen3.8-max-2026-09-02

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Blocking finding(s) follow.

Partially reviewed — gaps disclosed. Suggestions are inline.

Unresolved, please confirm:

  • [Critical] Live CHANGES_REQUESTED from the triage gate (review 5163484124, @qwen-code-ci-bot) — it asserts the PR body is missing all eight template sections and explicitly files no code defect ("No code review yet"), so it cannot be ruled on from the…

Not explored to full depth (tool budget reached): "agent 3c": grepping compose-review.test.ts and submit.test.ts for an existing rendersAsNothing / rendersAsNothingAtExit pin on a <details> -wrapped body..

Not reviewed: reverse audit — stopped before round 3 by the review time budget.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未决,请确认:共 1 条(原文未翻译,列表见上方英文部分)。

未探索到全部深度(达到工具调用预算):"agent 3c"grepping compose-review.test.ts and submit.test.ts for an existing rendersAsNothing / rendersAsNothingAtExit pin on a <details> -wrapped body.

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

— qwen3.8-max via Qwen Code /review (v0.23.2)

Comment thread packages/cli/src/commands/review/compose-review.test.ts Outdated
Comment thread packages/cli/src/commands/review/compose-review.test.ts Outdated
Comment thread packages/cli/src/commands/review/lib/review-footer.ts Outdated
@wenshao
wenshao requested a lite review from Copilot September 10, 2026 10:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Review found that the case added here, and the comments beside it, claimed
more than they delivered. Four reverse-audit rounds then found the same
class of defect four more times — each time in a sentence written as
MEASURED that had not been. Everything below is now checked by a mutation
that reddens it.

The helper had three assertions and two of them could not fail.
`body.split('\n')` does not split on a bare `\r`, and the filter keeps the
entry's first physical line, so the line count read 1 whatever happened to
the rest; and `&amp;lt;` can only appear if a model wrote those characters
itself, since the escape emits `&lt;` and nothing in the source emits
`&amp;`. Both are replaced by the invariant the fold actually promises: the
entry's TAIL on the same line as its head, and no `\r` or `\n` left inside
it. That is what a surviving break costs the reader — the per-line escape
decides each half on its own and can post a tag the renderer holds live.

The case now drives eight rows, not five. It gained the downgrade reasons,
which need no fixture at all, and a Critical deferral's RELOCATION exit —
a second producer that joins `bodyCriticals` after `ingestEntryList` has
run, which a `Suggestion` row never reaches. Removing the fold at either
site reddens the case; before, both were silently uncovered.

The comments now name what the experiment shows:

- Three rows are held by more than one fold — the deferral list, the
  cannot-tell leg, and the relocation exit, that last one three times over
  — so they cannot redden on a single-site regression, and the other five
  do. The earlier text said two and six, and credited the relocation exit
  to two holders that can both be broken with the row still folded.
- The deferral LIST line never reaches `escapeTagOpeners`; it posts as
  `- ${mdField(entry)}`. The relocation line does reach it and comes back
  unchanged. Both are asserted inert as a code span, for different
  reasons.
- `ingestEntryList`'s `\r\n?` normalisation folds nothing on its own;
  `collapseEntry` is what carries the entry and disclosure legs.
- `rendersAsNothing` stays position-blind because `scanLines` discards
  `token.map` and returns a block-quote-only depth — not because a parse
  would cost too much. That path already runs one:
  `canProjectFooterMarker` opens on a `<`, which every body these arms
  judge carries by construction. The earlier text blamed cost and put the
  figure at the parser's own inline-pass note, which is off by two orders
  of magnitude.

The corpus numbers behind the reverted `<?` split keep their conclusion and
lose their false precision: the axis of the third figure is named, and both
they and the parse comparison are marked directional rather than
reproducible, because the absolute values swing more than twentyfold with
the body's shape.

No behaviour change: outside the tests, only comments move.
@wenshao
wenshao requested a lite review from Copilot September 10, 2026 11:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wenshao

wenshao commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 114 passed · 4 failed · 118 total

Flakiness gate: ✅ 2 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:114 通过 · 4 失败 · 118 总计

抖动门:✅ 2 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR #11533 — deep verification

Verdict: findings — 114 pass / 4 fail / 118 scripted assertions.
Verified head d3d0086a7517dcbd8be1c5c7febd9735da891b48 (git rev-parse HEAD^2),
A/B base 76635386e42b08d41004ac1bb8d2eccdc1d1338b (HEAD^1, the merge-ref's base tip).

The 4 fails are my harness's mispredictions, not PR defects — see
Assertion accounting. The verdict is findings on the
strength of one measured finding: a sentence the new test comment marks
MEASURED that measurement falsifies.

Nothing here is a runtime defect. The PR's "no behaviour change" claim is
proven, not assumed: the minified transpile of both edited source files is
byte-identical to base.

中文摘要

结论 findings —— 114 通过 / 4 失败 / 118 条脚本断言。4 条失败全部是我的预测写错了,不是 PR 的缺陷,详见下方 Assertion accounting。

A/B 结论:本 PR 只有测试与注释,所以"载荷证明"是变异 A/B。中心主张(新用例真的钉住了折行不变量)成立:18 个单点变异中 8 个让用例变红,其余 10 个恰好就是注释所说"被多处折行共同守住"的那些单一持有者,加上它自己声明本用例不覆盖的 gate push,以及三个针对别的行的变异。"无行为变化"用 esbuild 压缩后逐字节比对证明(两个源文件与 base 完全一致)。见 Mutation matrixBehaviour neutrality 两表。

findings:一条 Suggestion —— 新测试注释里写着 MEASURED 的一句被实测推翻:延后清单那条腿的折行持有者是三个(与重定位出口完全相同),不是注释说的两个;注释断言"两个都破坏后裸 CR 才会出现在已发布的行上",而实测三对组合(C+D、C+F、D+F)都仍然折行,只有三个全破坏才不折。另有一条 nit(用例标题相对自己的两行过度声称)与一条 Correction(PR 描述里的 &amp;lt; 不可能由转义产生)。

未覆盖范围:逐 commit 归因(浅克隆,快照 3 个 commit 本地只有 1 个);快照里的 baseRefOid 本地不存在;review-footer 注释中的语料数字(注释自己声明语料未保留、数字只是方向性的);cmark-gfm 实测复核(容器内无该渲染器);仓库级 lint / prettier / typecheck 与 packages/cli 其余测试(PR 自己的 CI 已覆盖);向当前 main 的试合并。

Central claim

One case drives every channel with a multi-line entry (LF, CRLF and a
lone CR) and asserts the consequence, so a new channel that skips the fold
reddens here instead of mangling a review. … No behaviour change: outside the
tests, only comments move.

This is a test-only PR, so the load-bearing question is not "does it pass" but
"does the suite now hold down what it claims to". Two arms:

Behaviour neutrality (the "only comments move" arm)

01-source-edits-are-comment-only.png

arm oracle result
every changed line in compose-review.ts is a comment line 19 changed lines, count non-comment 0 non-comment
every changed line in review-footer.ts is a comment line 42 changed lines, count non-comment 0 non-comment
compose-review.ts executable content, base vs head esbuild --minify byte-compare identical, 86 224 B
review-footer.ts executable content, base vs head esbuild --minify byte-compare identical, 9 841 B
the minifier really strips comments (so equality is not trivial) source B ÷ minified B 5.4× and 5.8×

--minify drops comments and all optional whitespace, so identical bytes
means identical executable content. 6/6 assertions. Repro:
node tmp/pr11533-verify-20260910-121626/01-no-behaviour-change.mjs.

Mutation matrix (the "it reddens" arm)

02-fold-mutation-matrix.png — full text in logs-02b-matrix-condensed.txt.

Each mutant breaks one named fold site in a scratch copy of the production
source, runs the real it() plus three instrumented probes, then restores from
git. The probes exist because the real case aborts at its first failed
expect, so it alone cannot attribute a mutation to rows.

8 of 18 mutants redden the case. The 10 that do not are not survivors of
consequence — each is a single holder of a row the comment declares multiply
held, or a mutant aimed at a different row:

mutant (one site broken) real case rows that came back unfolded / refused
collapseEntry fold RED bodyCriticals; uncoverableChunks; unreviewedDimensions
downgrade \s+ pass RED downgradeReasons
collapseToLine (whole function) RED suggestionsDroppedAsDuplicates
boundDeferredLine's collapseToLine green nothing — another holder still folds
toDeferredEntries' collapseToLine green nothing
asListLine's collapseToLine green nothing
mdField's \r\n strip green nothing
ingestEntryList's \r\n? normalisation green nothing — confirms "folds nothing on its own"
scriptLintGate's push green and folds its own entry → RED, exactly as the comment cross-references
COMBO toDeferredEntries + mdField green nothing — see Finding 1: the comment predicts this pair unfolds the row
COMBO all three relocation holders RED deferredSuggestions; deferredSuggestions (relocated)
COMBO collapseEntry + asListLine RED bodyCriticals; uncoverableChunks; unreviewedDimensions (cannot-tell survives — its third holder holds)
COMBO all three cannot-tell holders RED + cannotTellCriticals + duplicates
by line-break refusal removed RED REFUSAL LOST: fixedFindings[].by
quoted-fence refusal disabled RED REFUSAL LOST: both Critical channels
reference-link label treated as raw green (fold case) all 4 new escape rows flip; the control row does not
<? arm split by position green (fold case) exactly the admitted-miscall footer row flips; the other 5 hold
<? span no longer ends at first > green (fold case) 2 footer rows flip

Every claim in the case's comment that I could turn into a mutation held, with
one exception (Finding 1). Specifically verified as stated:

  • "THREE rows cannot redden on a SINGLE-site regression … the other five do
    redden on theirs"
    true. Five rows fall to three function-level
    mutants; the three multiply-held rows survive every single-site mutant.
  • "Removing the fold from scriptLintGate … reddens it"true of the
    other test
    , and the comment says so: the gate mutant leaves the new case
    green and reddens folds its own entry. Both halves checked.
  • "the deferral LIST line never reaches escapeTagOpeners"true, and
    the relocation line does reach it. Confirmed by census below.
  • The two "recorded rather than changed" boundaries are real tripwires, not
    decoration: the reference-link rows die under the reading two audits argued
    (4/4 flip, control unaffected), and the &nbsp;<?x> row dies under exactly
    the position split the comment says was tried and reverted (1/6 flip).

Repro: node tmp/pr11533-verify-20260910-121626/02-mutation-matrix.mjs.

Census: does "every channel" mean every channel?

04-escape-census-ten-sites.png

The case is named "every model-written channel reaches the escape", so I
enumerated rather than read. There are exactly 10 production
escapeTagOpeners( call sites; all 10 are accounted for:

site covered by
compose-review.ts:2093/2099/2100 cannotTellCriticals row
compose-review.ts:5955 accounting probe (downgrade budget), not a render leg
compose-review.ts:7188 bodyCriticals + deferredSuggestions (relocated)
compose-review.ts:7215 suggestionsDroppedAsDuplicates
compose-review.ts:7596/7597 uncoverableChunks / unreviewedDimensions
compose-review.ts:7931 downgradeReasons
submit.ts:1847 upstream refusal — see below

submit.ts:1847 is the one site outside the module, so I traced it rather than
assuming: it escapes a ruling note's by, and its input is
composeReview's returned fixedFindings (r.fixedFindings, submit.ts:497),
destructured from compose() at :1520 — before planThreadActions at
:1770, which is before the escape at :1847. A multi-line by therefore throws
in compose first and the escape is never reached. The channel is protected by
the very refusal the new case pins. 24/24 assertions across the census and the
two corrected re-runs. Repro: …/04-census-and-corrections.mjs.

Corrections

These are corrections to descriptions, not requests to change code.

The PR description's &amp;lt; cannot be produced by this code. The
description says the historical scriptLintGate bug "rendered a literal
&amp;lt; to a reviewer"
. Measured (05-amp-lt-cannot-be-emitted.png):

  • no non-comment production line anywhere in
    packages/cli/src/commands/review emits the literal &amp; (17 occurrences
    total, every one in a comment or a test);
  • over a 20-input corpus — on which the escape genuinely fires, 11/20 inputs
    come back changed — escapeTagOpeners never introduces &amp;, applied
    once or twice, so a re-escape does not explain it either;
  • the only route to a posted &amp;lt; is an input that already contained
    those characters, which passes through untouched.

That is precisely the reasoning commit 3 used to delete the helper's &amp;lt;
assertion, and the reasoning is correct. What a reader would have seen is a
literal &lt; (the phrasing the new comment itself uses for the deferral
code-span row) or a live element — not &amp;lt;. The same stale phrasing sits
in the pre-existing comment at compose-review.ts:8607 and in the pre-existing
test comments at compose-review.test.ts:8342/8353; all three are outside
this diff
, so this is a note for whoever touches them next, not a change this
PR owes.

Findings

1. Suggestion — the deferral-list fold has three holders, not two, and the sentence saying otherwise is marked MEASURED

packages/cli/src/commands/review/compose-review.test.ts, the new case's
opening comment:

the deferral leg is held TWICE, by a collapse pass and by mdField's
line-ending strip (lib/md-field.ts), and either alone holds it — MEASURED:
break only one and the row is still folded; the bare CR reaches the POSTED
LINE only with both broken
.

The second half is false under either reading of "a collapse pass". The list
leg has the same three holders as the relocation exit, because
deferredShown runs .map(renderDeferredEntry).map(boundDeferredLine)
(compose-review.ts:7405) before posting as - ${mdField(entry)} (:7530):

  • C = collapseToLine inside boundDeferredLine (compose-review.ts:758)
  • D = collapseToLine inside toDeferredEntries (compose-review.ts:802)
  • F = mdField's /[` \r\n]+/ strip (lib/md-field.ts)

I ran the complete 2^3 lattice over them (03-deferral-lattice-three-holders.png):

broken holders real case LIST row RELOC row
C green folded folded
D green folded folded
F green folded folded
C+D green folded folded
C+F green folded folded
D+F green folded folded
C+D+F RED UNFOLDED UNFOLDED

No pair unfolds the row; only the triple does. The two legs behave
identically in all seven cells, which is the direct proof they have the same
holders. Repro: node tmp/pr11533-verify-20260910-121626/03-deferral-lattice.mjs
(17/17 assertions).

Why it matters. Nothing at runtime — the source edit is comment-only,
proven byte-identical above. It matters because this PR's entire second commit
is "say only what it measures", and because the same comment draws a contrast
that does not exist: "Two more legs are held twice: cannot-tell by
collapseEntry AND collapseToLine, and the RELOCATION exit THREE times."

At the granularity the comment uses for the relocation exit (invocation sites,
not functions), all three multiply-held legs have three holders — cannot-tell
is collapseEntry + boundDeferredLine's collapseToLine + asListLine's
collapseToLine (compose-review.ts:2050), which the AE mutant confirms:
breaking two of its three leaves it folded. A maintainer adding a fourth
deferral exit would size the guard at two holders instead of three.

Suggested fix (comment-only; backed by the lattice above, no new harness needed)

Replace the TWICE … only with both broken sentence with the same shape the
comment already uses for the relocation exit, e.g.:

// the deferral leg is held THREE times, exactly like the relocation exit —
// `collapseToLine` at `toDeferredEntries`, again inside `boundDeferredLine`
// (which `deferredShown` runs before the list posts), then `mdField`'s
// line-ending strip. MEASURED over the whole 2^3 lattice: break any ONE or
// any TWO and the row is still folded; only all three let the bare CR reach
// the POSTED LINE.

and change "Two more legs are held twice" to name three holders for the
cannot-tell leg too (collapseEntry, and collapseToLine twice — at
boundDeferredLine and at asListLine), so one granularity is used throughout.

This is a wording change whose every clause the lattice table above already
measures, so it needs no new fixture; the suite is green with and without it,
which is exactly the unpinned-axis signal — the row that would pin it is the
C+D+F cell.

2. Nit — the case's title claims more than two of its own rows deliver

The case is titled "every model-written channel reaches the escape as ONE
line"
, but two of its eight rows are asserted not to reach the escape: the
deferral list line posts as - ${mdField(entry)} with no escapeTagOpeners
call at all (verified by census), and the row is asserted as a code span
instead. The body comment states this accurately — "The deferral LIST line …
never reaches escapeTagOpeners at all … The fold is what all eight share"

so only the title is loose. Given that commit 3 exists to remove sentences
that claim more than they deliver, the title is the one left. A title naming
the shared property ("every model-written channel is folded to ONE line before
it posts"
) would match all eight rows.

3. Nit — the <details> blind spot is recorded as a decision but gets no row

The new review-footer.ts comment records two blind spots as decisions.
BLOCK CONTEXT gets a pinned row (&nbsp;<?x>), and I verified the pin is a
genuine tripwire — the position-split mutant reddens exactly that row and
leaves the other five. FOLDS gets prose only. I measured the behaviour it
describes (logs-07-fold-blindspot-probe.json):

body rendersAsNothing
<details><summary>s</summary>the auth check is missing</details> false — posts and counts, as the comment says
<details><summary>the auth check is missing</summary></details> false
nested <details> false
fold + trailing prose false
`<details>` quoted in a code span + prose false

No row in review-footer.test.ts pins any of these (the file's only details
hits are unrelated prose, for details). So a future arm that did delete a
fold's content — the change the comment argues against — would redden nothing.
One row would close it, and the expected value is measured above:
expect(rendersAsNothing('<details><summary>s</summary>the auth check is missing</details>')).toBe(false).
The comment does not claim a pin here, so this is completeness, not a wrong
statement.

Not a finding — the comment's other structural claims all check out

Recorded so the absence of a finding is not read as an absence of checking:

  • "scanLines … returns {line, kind, depth, content} — the extent is
    discarded, and depth counts block-quote markers only, so a list item reads
    0"
    — true (review-footer.ts:753-779; QUOTE_PREFIX_RE is the only depth
    source).
  • "this path already pays for a run of it: stripReviewFooter opens
    canProjectFooterMarker on a < … and reaches scanLines"
    — true:
    stripReviewFooter (:563) gates on canProjectFooterMarker (:564, which
    tests includes('<')) then calls blankQuotedCode (:635), whose first
    statement is scanLines(body) (:636). rendersAsNothing itself is a pure
    regex chain with no parse, so "the parse is already in the bill, the
    projection is what stops it" is the right diagnosis.
  • "the relocation exit THREE times — collapseToLine at toDeferredEntries
    and again inside boundDeferredLine, then mdField"
    — true, and the CDF
    cell confirms all three are needed.

Assertion accounting

assertions.json reports 114 pass / 4 fail / 118 total, counting every
scripted check that executed. The 4 fails are all in harness 02 and all are
my predictions, revised afterwards on independent code evidence:

fail what I predicted what happened resolution
CL unfold set duplicates + both deferral rows duplicates only I had not yet read deferredShown…map(boundDeferredLine); mdField holds the deferral rows. Re-run with the corrected prediction in harness 04 — confirmed
DF case reddens RED green Same root misprediction. Superseded by harness 03's full lattice — and this is the measurement behind Finding 1
DF unfold set deferredSuggestions none ditto
GT footer rows 1 row flips 2 flip My error about noGt poisoning the whole family. Re-run corrected in harness 04 — confirmed

Three of the four trace to trusting the PR's own holder count, which is how
Finding 1 surfaced; one (GT) was purely my reasoning error. Both corrected
re-runs pass (harness 04: 24/24), and the original logs are kept
(logs-02-mutation-matrix.txt) rather than overwritten. Under the contract a
nonzero fail bars merge-ready; the verdict here is findings on Finding 1
regardless.

Not covered

  • Per-commit attribution. The checkout is shallow (git rev-parse --is-shallow-repository → true). git rev-list --count HEAD^1..HEAD^2
    returns 1 while the snapshot lists 3 commits; 8f7141d8 and the merge
    00f82a72 are absent locally. I verified the aggregate HEAD^1..HEAD
    diff only. This is exactly the shallow-boundary trap where the bare count
    looks plausible instead of erroring.
  • Base OID drift. The snapshot's baseRefOid fe7cf686… is absent
    locally. The A/B base used is HEAD^1 = 76635386, the merge-ref's base tip,
    which is the correct control for this checkout shape.
  • Trial merge into current main. main is not reachable at depth 2, so
    I could not confirm the merge is conflict-free or re-run the suite on the
    merged tree.
  • The corpus figures in the review-footer.ts comment (30 000 bodies;
    121 → 116 wrongly-empty; 1426 → 1730 wrongly-visible; 121 → 3594). Not
    re-derivable — the comment itself says the corpus was one-off and not kept,
    and marks the figures directional. I verified the structural premises
    those numbers rest on instead (the three bullets at the end of Findings).
    No A/B was possible on the figures themselves.
  • cmark-gfm agreement. The rows and comments say "MEASURED against
    cmark-gfm and an HTML parser". This container has neither, so I did not
    re-verify that the renderer agrees. What I did verify is different and
    weaker: that the rows have teeth against a plausible wrong implementation
    (mutant R). "The rows catch the wrong reading" is not "cmark-gfm says the
    right reading".
  • Repo-wide gates. Not run: npm run lint, npm run format,
    npm run typecheck, and the rest of the packages/cli suite. The PR's own
    CI covers them; per the method I ran only the affected workspace's two
    changed test files.
  • The <details> fold arm's cost side. Finding 3 measures that a fold
    hiding prose posts. I did not measure the counterfactual the comment argues
    against — how often a review of this very file quotes `<details>` — so
    the trade itself is taken as the comment states it.
  • No live GitHub or wire harness. The PR posts nothing new; there is no
    network surface in the diff, and this job has no token.

Methodology

Environment: the CI verify container (node:22-bookworm, Node v22.23.2),
working tree at refs/pull/11533/merge, npm ci + npm run build already
complete at HEAD. All five harnesses are .mjs/.ts files in this directory so
a maintainer can rerun them; raw per-cell logs are logs-*.txt, structured
results results-*.json, images evidence/*.png.

Because the PR is test-only, the A/B is a mutation A/B rather than a
base-vs-head build comparison. Harness 02 applies each mutant to the real
production source in the working tree, runs, then restores with
git checkout -- and asserts the restore left the tree clean before the
next mutant; git status --porcelain is clean at the end and the two changed
test files were re-run green on the pristine tree (839/839, logs-06). The
base-vs-head comparison is instead done on executable content: esbuild --minify byte-equality of the two source files at HEAD^1 and HEAD.

The real case aborts at its first failed expect, so row attribution comes
from three instrumented probes appended to a scratch copy of
compose-review.test.ts (compose-review.mutprobe.test.ts, deleted after the
run — the real test file was never modified). The copy reuses the PR's own
base()/coveredPlan() fixtures, so the probes drive the identical inputs;
the fold probe re-asserts the identical three invariants but records all
29 rows instead of stopping at the first. Probes write JSON to files because
packages/cli/vitest.config.ts sets silent: true, which swallows
console.log. Every run asserts its collected-test count (5 for the matrix,
1 for the escape probe) so a -t filter that matched nothing could not read as
a green run; the unmutated control run is the matrix's first row.

Flakiness gate log

rounds=5 files=2 skipped=0
file packages/cli/src/commands/review/compose-review.test.ts: (cd packages/cli) npx --no-install vitest run ./src/commands/review/compose-review.test.ts
file packages/cli/src/commands/review/lib/review-footer.test.ts: (cd packages/cli) npx --no-install vitest run ./src/commands/review/lib/review-footer.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/cli/src/commands/review/compose-review.test.ts: PPPPP
  packages/cli/src/commands/review/lib/review-footer.test.ts: PPPPP

verdict: pass
summary: 2 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 1 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 2 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 2 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 3 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 3 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 4 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 4 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 5 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 5 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)

Evidence images

01-source-edits-are-comment-only

02-fold-mutation-matrix

03-deferral-lattice-three-holders

04-escape-census-ten-sites

05-amp-lt-cannot-be-emitted

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical-only review at head d3d0086a. Not approving, and the code is not the reason: the production diff is comment-only and I found nothing blocking in it. One process blocker from the repository's own triage gate is still live at this head.

The standing blocker

Review 5163484124 (triage stage 1a, 2026-09-10T06:32:44Z) is still in CHANGES_REQUESTED state at this head, and the round that followed recorded it as its single unresolved Critical. Its ground is verifiable and still true: the PR body carries only ## What this adds and ## Two boundaries recorded rather than changed — I checked the heading set at this head — and none of the headings .github/pull_request_template.md requires and AGENTS.md repeats (what the PR does, why it is needed, reviewer test plan, risk and scope, linked issues). The triage run stopped at that gate, so it never reviewed the code at all. This is a body edit, not a code change, and until it happens the branch has a live request-changes against it.

Critical-only scan — nothing blocking

Both production hunks are comments, read in full:

  • compose-review.ts:3673-3689 replaces the fold-site inventory above escapeTagOpeners. The old comment claimed four sites; the new one names the actual set — collapseEntry for the entry channels and the Not reviewed: disclosures (noting that the \r\n? normalisation ingestEntryList does first folds nothing on its own), the \s+ pass for downgrade reasons, collapseToLine for the duplicate-drop leg and again through toDeferredEntries / boundDeferredLine for a Critical deferral's relocation exit, and scriptLintGate's own push — and states which of those join bodyCriticals after ingestEntryList has run, so the shared fold never sees them. It then says exactly what the new test drives and which leg it does not: the gate push needs a report fixture and is pinned by folds its own entry. No executable line changes.
  • lib/review-footer.ts:902-946 adds a docblock recording two blind spots of rendersAsNothing as measured decisions rather than gaps: a fold whose content is a collapsed triangle posts and counts, because an arm deleting fold content would also delete a quoted `<details>` — the shape a review of that very file writes; and the <! / <? arms stay position-blind because splitting them by hand was tried and reverted, with the corpus numbers recorded (121 → 116 wrongly-empty against 1426 → 1730 wrongly-visible for the split alone, and 121 → 3594 wrongly-empty once container prefixes are taught without their extent). It also records why the parse is not the obstacle — BLOCK_PARSER already runs on this path via stripReviewFootercanProjectFooterMarkerscanLines — and what is: scanLines discards token.map extent and its depth counts block-quote markers only.

The two test files are additive: the fold-invariant case drives every model-written channel with LF, CRLF and a lone CR and asserts the rendered consequence, and the footer case pins the position-blind <? behaviour (&nbsp;<?x>, <?x>, <?php echo 1 ?> judge as rendering nothing; q <?x> does not) so the boundary stays a decision on the record. A test-only addition cannot regress behaviour, and Test (ubuntu-latest, Node 22.x) is green at this head, which is direct evidence the new cases pass as written.

The three Suggestion threads from the previous round — that the guard's comment claimed enforcement it did not have, that two of its three assertions could not detect a fold regression and the third missed a bare CR on four channels, and that the footer record named a blocker absent from its own file — are all resolved, and this head is the commit that answers them: its title is "give the fold guard teeth, and say only what it measures", the compose comment now scopes its own claim to what the tests drive, and the footer docblock states the measured trade instead of asserting a fix. I read the two comment changes first-hand; I did not read the 189 new test lines assertion by assertion, and for a test-only addition that is not a gate on this verdict.

CI at this head

Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), both Desktop Shell jobs, web-shell E2E Smoke, TUI parity and the OpenTUI gate all passed. The rollup reads FAILURE only because two route checks were cancelled, and review-pr was still queued; neither is evidence of a defect here.

Next step

Restructure the PR body onto the template's headings — the content is already written, it is a matter of section names and adding the risk/scope and linked-issue lines (this is a follow-up to #9940) — or have a maintainer dismiss the triage review explicitly. The code needs nothing.

@wenshao

wenshao commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

Body restructured onto the template — all eight required sections are present, and the two places the gate said this PR specifically owed content are filled rather than re-headed:

Reviewer Test Plan → How to verify now carries the actual mutation output rather than the prose claim. Each fold broken one at a time, run at this head:

collapseEntry's fold removed
  AssertionError: expected [ 'bodyCriticals', false ] to deeply equal [ 'bodyCriticals', true ]
the relocation exit's fold removed
  AssertionError: expected [ …(2) ] to deeply equal [ …(2) ]     ← deferredSuggestions (relocated)
the downgrade reasons' `\s+` pass removed
  AssertionError: expected [ 'downgradeReasons', false ] to deeply equal [ 'downgradeReasons', true ]

It also records which rows CANNOT redden on a single-site break and why: three of the eight are held by more than one fold — the deferral list, the cannot-tell leg, and the relocation exit, which is held three times over — so a maintainer does not read a double-held row as a sentinel for its own folds.

Risk & Scope → Not validated / out of scope now states the tradeoff you named: rendersAsNothing('&nbsp;<?x>') is asserted true while the note beside it says those characters are VISIBLE and calling the body empty is wrong. It is pinned deliberately — splitting the <!/<? arms by position was tried and reverted, because being right needs a container's EXTENT, which scanLines discards — and it is on the record as something a future fix has to unpick. The corpus numbers are marked one-off rather than reproducible, with the third figure's axis named.

## Linked Issues is a non-closing reference to #9940, as you suggested.

中文说明

正文已按模板重构——八个必需章节齐全,而且 gate 指出的"本 PR 特别欠内容"的两处是补写,不是改标题:

Reviewer Test Plan → How to verify 现在贴的是真实的变异输出,而不是散文断言(逐个破坏折行,在本 head 上实际跑出,见上)。同时写明了八行里哪三行不会在单点破坏下变红以及为什么——deferral 清单、cannot-tell 腿,以及被持有三次的 relocation 出口——这样维护者不会把双持行当成它自己那些折行的哨兵。

Risk & Scope → 未验证/超出范围 写明了你点出的取舍:rendersAsNothing('&nbsp;<?x>') 被断言为 true,而旁边的注释说这些字符是可见的、把该正文判为空是错的。这是刻意钉住的——按位置拆分 <!/<? 分支已试过并回退,因为要做对需要容器的 extent,而 scanLines 把它丢弃了——并作为"将来的修复必须解开的东西"记录在案。语料数字已标注为一次性、不可重跑,第三个数字的坐标轴也已点明。

## Linked Issues 按你的建议写成对 #9940 的非关闭式引用。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks — the body is on the template now, so this run picks back up where the last one stopped. ✓

Problem: real, and statically demonstrable rather than theoretical. The version of this guard in the PR's own first commit (8f7141d) asserted body.includes('&amp;lt;') === false and counted lines via body.split('\n'). escapeTagOpeners only ever emits &lt; — I grepped the whole review module and &amp; appears in comments only, never in emitted output — so that assertion could only trip on a body a model had written those seven characters into itself. And a \n-only split leaves a bare \r inside the matched line, so the line count read 1 whatever happened to the tail. Two of three assertions unfailable. The bug underneath is observed, not hypothetical: #9940 put a literal &amp;lt; in front of a reviewer because scriptLintGate joins bodyCriticals after the shared fold has already run. Worth recording that the decorative version lives in 8f7141d and not in main — so this PR replaces it rather than leaving a second, dead guard sitting next to the new one.

Direction: aligned. The /review compose path is this repo's own tooling, and "a guard that cannot fail" is precisely the failure mode that let #9940 through in the first place. No CHANGELOG reference — nothing here reaches a user.

Size: not applicable — packages/cli/src/commands/review/** is not a core path, and the change stays inside one package. For the record: 206 test lines, 61 lines across the two non-test files, and zero executable lines. Both non-test hunks are entirely comment text — a line-comment block above escapeTagOpeners, and JSDoc above the <!/<? arms of rendersAsNothing.

Approach: the shape is right — assert what the reader ends up with on the posted body, once per model-written channel, with \n, \r\n and a lone \r all driven. Documenting which legs are held more than once is the part I'd have missed, and it stops a maintainer misreading a double-held row as a sentinel for its own folds. Two things I'd push on, neither blocking:

  • The title says "the fold invariant", but the PR also pins a second, unrelated boundary in lib/review-footer.ts: rendersAsNothing's position-blindness on <?, with &nbsp;<?x> asserted empty against the new comment's own note that those characters are VISIBLE to the renderer. Both came out of the same round-12 audit so it hangs together, and it is disclosed under Risk & Scope where it belongs. Just naming that this is two decisions in one PR, and the second is the one a future fix has to unpick.
  • The 42 new comment lines carry corpus numbers (121 → 116 wrongly-empty, 1426 → 1730 wrongly-visible, 121 → 3594 once container prefixes are taught without their extent) from a corpus the comment itself says was not kept. That is the one claim in this diff no reader can re-measure. The comment does label it directional rather than a benchmark, which is the honest framing — but a PR whose thesis is "every claim is attached to a mutation that reddens it" is carrying numbers attached to nothing. Worth deciding whether the figures earn their lines, or whether the qualitative trade ("the half-measure cost more bodies than it saved") already says everything a future reader can act on.

Risk: no elevated risk signals — none of the four files match the high-risk paths, and nothing executable changes.

Moving on to code review. 🔍

中文说明

感谢——正文现在已经套上模板了,所以本次运行从上次停下的地方继续。✓

问题: 真实存在,而且是静态可验证的,不是理论性问题。本 PR 自己第一个提交(8f7141d)里的那版守卫,断言了 body.includes('&amp;lt;') === false,并用 body.split('\n') 数行数。但 escapeTagOpeners 只会发射 &lt;——我把整个 review 模块都 grep 了一遍,&amp; 只出现在注释里,从不出现在实际输出中——所以那条断言只有在模型自己写下那七个字符时才可能失败。而只按 \n 切分会让单独的 \r 留在被匹配到的那一行里,于是无论尾部发生了什么,行数都读作 1。三条断言里有两条不可能失败。底下的 bug 是观测到的,不是假设:#9940 就让审阅者看到了字面的 &amp;lt;,原因是 scriptLintGate 在共享折行跑完之后才并入 bodyCriticals。有一点值得记录:那版装饰性守卫存在于 8f7141d不在 main 里——所以本 PR 是替换掉它,而不是在新守卫旁边再留一份死的守卫。

方向: 对齐。/review 的 compose 路径是本仓库自己的工具链,而"一个不可能失败的守卫"恰恰是当初放 #9940 过去的那种失效模式。CHANGELOG 无相关记录——这里没有任何东西会到达用户。

规模: 不适用——packages/cli/src/commands/review/** 不是核心路径,且改动只在单个 package 内。作为记录:测试 206 行,两个非测试文件共 61 行,可执行行 0 行。两个非测试 hunk 全是注释文本——escapeTagOpeners 上方的行注释块,以及 rendersAsNothing<!/<? 分支上方的 JSDoc。

方案: 形态是对的——断言读者最终在已发布正文上看到的东西,每条模型撰写渠道各跑一次,并且把 \n\r\n 和单独的 \r 全部驱动到。把"哪些环节被多重持有"写下来,是我自己会漏掉的部分,它能阻止维护者把一行被双重持有的用例误读成它自己那些折行的哨兵。有两点我想提出来,都不构成阻塞:

  • 标题说的是"折行不变量",但 PR 还在 lib/review-footer.ts 里钉住了第二个不相关的边界:rendersAsNothing<? 上的位置盲区,其中 &nbsp;<?x> 被断言为空,而新增注释自己就说这些字符在渲染器里是可见的。两者都出自同一轮 round-12 反向审计,所以放在一起是讲得通的,而且它被披露在 Risk & Scope 里,位置正确。只是要点明:这是一个 PR 里的两个决定,而第二个是将来修复时必须先解开的那个。
  • 新增的 42 行注释带着语料数字(121 → 116 误判为空、1426 → 1730 误判为可见、教会容器前缀但不带容器范围后 121 → 3594),而注释自己说那份语料没有保留。这是整个 diff 里唯一一条读者无法重新测量的断言。注释确实把它标注为方向性测量而非基准,这是诚实的写法——但一个论点是"每个断言都配有能让它变红的变异"的 PR,却带着一组什么都没挂上的数字。值得决定一下:这些数字是否配得上它们占的行数,还是说那句定性的取舍("半成品方案损失的正比它救下的多")已经把未来读者能据此操作的信息全说完了。

风险: 无升级风险信号——四个文件都不匹配高风险路径,且没有任何可执行代码变化。

进入代码审查 🔍

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at d3d0086a7517dcbd8be1c5c7febd9735da891b48 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code review

Read at d3d0086. Nothing here would fail at runtime, and the central thing the PR claims — that the old guard could not fail and this one can — checks out. I verified that independently rather than taking the body's word:

  • The old assertion really was dead. &amp; appears in the review module's non-test source in exactly one place, a comment at compose-review.ts:8598; escapeLine only ever emits &lt;. So expect(body.includes('&amp;lt;')).toEqual(false) could trip only on a body a model had written those characters into itself.
  • The replacement really is falsifiable. I traced mdField (lib/md-field.ts:29 collapses every [`\r\n]+ run to one space and strips backticks), so the escapes=false regex pins exactly a two-backtick code span with the tag inside it. Drop the fold and the line count still reads 1 — the filter keeps the first physical line — but lines[0].includes('never clears it') goes false. The tail assertion is the load-bearing one, which is the point the helper's own comment makes.
  • It actually runs. No .skip/.only/xit/xdescribe anywhere in the review suites; the case sits inside the plain describe('composeReview — fixedFindings') at compose-review.test.ts:6457.
  • The ordering claim holds. ingestBodyCriticals (compose-review.ts:4811) precedes the relocation push (:4879), the gate push (:5496) and the non-convergence push (:5517), and nonConvergenceCritical (:2816-2839) is numeric-interpolation-only with no line break to fold.
  • The five new rendersAsNothing rows and the four reference-link rows are all correct against the real implementation — stripBangSpans (review-footer.ts:934-944) hides to the first >, not to ?>, and escapeLine has no link-label handling at all (destish builds only on ](), so those four inputs come back unchanged.
  • The three inline Suggestions from the previous round are all addressed at this head.

Findings are all non-blocking, and they are all in prose — which for this PR is the deliverable, so they are worth more attention than the severity labels suggest.

1. The counting basis switches mid-comment, and "THREE rows cannot redden" depends on which one you pick. The deferral leg is called "held TWICE, by a collapse pass and by mdField's line-ending strip"; the relocation exit is called "THREE times — collapseToLine at toDeferredEntries and again inside boundDeferredLine, then mdField". But the deferral LIST runs the identical chain: toDeferredEntries' collapseToLine (compose-review.ts:802) → boundDeferredLine's (:758) → mdField (:7521). Three invocations over two mechanisms, same as the relocation exit. One of the two counts is wrong under either basis.

It bites on the conclusion too. Counted by site — which is what "cannot redden on a SINGLE-site regression" says — the duplicate-drop leg is also double-held, since compose-review.ts:7206 runs boundDeferredLine (:758) and asListLine (:1936), so it is four rows, not three. Counted by mechanism, three is right and the relocation sentence's "THREE times" is wrong. Both readings appear within four lines of each other. A maintainer doing the mutation exercise the comment invites gets a different answer than the comment predicts, which is the one thing this block exists to prevent.

2. "The <! and <? arms hide the same span wherever the opener stands" is over-broad. review-footer.ts:1049-1052 is explicitly position-aware — it fires only at (?:^|\n) {0,3} and then deletes to end of document — and the comment five lines above it (:1044-1048) states the opposite outright: "MID-LINE the same opener is an inline construct the renderer refuses, so the text after it is visible". The sentence is true of the terminated path (stripBangSpans), which is the shape the pinned &nbsp;<?x> exercises, but as written it contradicts an arm in the same function. This one matters more than it looks: the paragraph's argument is that being position-correct "needs a container's EXTENT, which scanLines discards", yet the arm 100 lines below already gets line-start position from a plain regex with no extent at all. The argument may still survive — the hard case is a container prefix, not a bare line start — but the sentence as written understates what the function already models.

3. Some of the 189 lines restate coverage the file already has. The fence-refusal block re-asserts :1108-1125 (LF) and :2079-2092 (bare CR) on the same two channels with a longer regex, adding neither a channel nor a line ending. The by throw overlaps :6538-6561, though it does genuinely add \r and \r\n to a case that had \n, U+2028/2029, empty and non-string. And the uncoverableChunks / unreviewedDimensions rows overlap :6860-6901, which already loops all three line endings across both fields through composeReview with escape assertions — and with a harder three-backtick fixture. What earns the space is the part that is new: the reader's-eye fold (head and tail on one line, no break inside), the relocation exit's posted line with a multi-line title, and the code-span-vs-escape contrast. Those are not pinned anywhere today. Worth considering whether the duplicated blocks stay or get a pointer to the case that already holds them.

4. The paragraph above the one you rewrote still carries the old inventory. compose-review.ts:3665-3671 is untouched and says "ingestEntryList folds the entry channels", four lines above the new text crediting collapseEntry and saying ingestEntryList's normalisation "folds nothing on its own". To be clear — and I got this wrong on a first read, so it is worth stating precisely — these are not contradictory: ingestEntryList calls collapseEntry at :3587, so both sentences are true. But two adjacent paragraphs now give different inventories of the same fold without saying they are one call chain, and ¶1's list is the shorter, staler one the PR set out to correct. Either fold ¶1 into the rewrite or have it point down.

5. The uncheckable numbers. The 42 lines in review-footer.ts land five figures no reader can re-run — 121 → 116, 1426 → 1730, 121 → 3594, "30 000 bodies", and the cost comparison — from a corpus the comment itself says was not kept. Same shape in the test comment: "MEASURED: break only one and the row is still folded" and "(all eight measured)" record mutation results with no harness behind them. The labelling is honest ("the shape of the trade, not a benchmark to re-run"), and I am not asking for the numbers to be struck. But a PR whose thesis is that every claim is attached to a mutation that reddens it is carrying claims attached to nothing, and it is the one place a future reader has to take the author's word.

6. Nit: !l.includes('qwen-review-ledger') in the carries filter has no comment saying why. The ledger marker carries {v, round, findings: [{id, sev}], sha, model} — ids and severities, not model prose — so it excludes nothing in practice. One line would keep the helper self-explaining in a file whose whole value is that a reader can check it.

CI evidence

Quoting the PR's own checks on d3d0086, fetched via the API — I did not build or run anything from this tree.

The suite is green where it runs: Test (ubuntu-latest, Node 22.x) succeeded with every step green including Run tests and generate reports, and Lint & Static (ubuntu-latest, Node 22.x) succeeded, which covers the tsc / eslint / prettier claim in the body. Integration, TUI parity, Desktop Shell (both OSes), web-shell E2E and the OpenTUI gate all passed. No red checks, so no log excerpt to quote.

One correction to the body. "macOS and Windows are covered by CI" is not true at PR time. ci.yml gates both test_macos (:1556-1565) and test_windows (:1652-1662) to merge_group, schedule and workflow_dispatch, and both report skipped on this head. That gating is deliberate and documented in the file — a pull_request run executes the workflow YAML from the PR's own merge commit, so a PR this lane admitted could rewrite runs-on in the same diff that reached it. The coverage is real, it just lands in the post-approval merge queue rather than here. The ⚠️ marks in ### Tested on are the accurate signal; the prose overstates it. Materiality is nil for this diff — the new case manipulates \r and \n as escapes inside string literals, so .gitattributes' eol=lf and the Windows job's autocrlf belt-and-braces cannot touch them — but the sentence should say merge queue, not CI.

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Lint & Static (ubuntu-latest, Node 22.x) success
Integration Tests (no-AK, No Sandbox) success
Integration Tests (CLI, No Sandbox) skipped
Test (macos-latest, Node 22.x) skipped
Test (windows-latest, Node 22.x) skipped
TUI parity snapshots (ink vs opentui) success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
OpenTUI no-flicker gate success
Classify PR success
build-cli skipped

Sandboxed verification would settle the one claim none of the above can: that the new case reddens under mutation. Green CI proves the tests pass, not that they are load-bearing — a case that passed identically with every fold removed would look exactly like this one, and that is precisely the defect the PR says it is fixing. The mutation output in the body is the author's own run, not independently re-run here, and I was not able to re-run it (the gate never executes PR-derived code). A verify job is already in progress on this head as part of run 34474375821, and its report will land in the existing sandboxed-verification comment — read that when it completes rather than triggering a second run. If it dies with the sibling triage job (which failed), @qwen-code /verify re-triggers it; the author has write access, so no sponsored run is needed. /tmux does not apply — nothing here is user-visible.

中文说明

代码审查

d3d0086 上阅读。运行时不会有任何东西失败,而 PR 的核心主张——旧守卫不可能失败、新的可以——是成立的。这一点我是独立验证的,没有只采信正文的说法:

  • 旧断言确实是死的。 在 review 模块的非测试源码里,&amp; 只出现在一个地方,即 compose-review.ts:8598 的一行注释;escapeLine 只会发射 &lt;。所以 expect(body.includes('&amp;lt;')).toEqual(false) 只有在模型自己写下那七个字符时才可能失败。
  • 新的断言确实可以失败。 我跟进了 mdFieldlib/md-field.ts:29 把每一段 [`\r\n]+ 折叠成一个空格并剥掉反引号),所以 escapes=false 那条正则钉住的正是"两个反引号、标签在其中"的代码跨度形态。把折行去掉,行数仍然读作 1——过滤器保留了第一条物理行——但 lines[0].includes('never clears it') 会变成 false。真正承重的是尾部断言,这也正是 helper 自己注释里说的那一点。
  • 它确实会运行。 review 各套件里没有任何 .skip/.only/xit/xdescribe;新用例位于 compose-review.test.ts:6457 的普通 describe('composeReview — fixedFindings') 内。
  • 顺序主张成立。 ingestBodyCriticalscompose-review.ts:4811)在 relocation 推入(:4879)、gate 推入(:5496)和 non-convergence 推入(:5517)之前;nonConvergenceCritical:2816-2839)只有数字插值,没有可折的换行。
  • 五条新的 rendersAsNothing 行和四条引用链接行都与真实实现一致——stripBangSpansreview-footer.ts:934-944)隐藏到第一个 > 而不是 ?>,而 escapeLine 完全没有链接标签处理(destish 只在 ]( 时构建),所以那四条输入原样返回。
  • 上一轮的三条 inline Suggestion 在本 head 上都已处理。

发现的问题全部不构成阻塞,而且全部在文字里——但对这个 PR 来说文字就是交付物,所以它们值得比严重性标签更多的注意。

1. 计数口径在注释中途切换,而"三行不会变红"取决于你选哪一种。 折行环节被称为"held TWICE,由一个 collapse pass 和 mdField 的行尾剥离";relocation 出口被称为"THREE times——toDeferredEntries 处的 collapseToLineboundDeferredLine 内部再一次,然后 mdField"。但 deferral 清单走的是完全相同的链条:toDeferredEntriescollapseToLinecompose-review.ts:802)→ boundDeferredLine 的(:758)→ mdField:7521)。两种机制、三次调用,与 relocation 出口一模一样。两种口径下,两个计数里都必有一个是错的。

这还会影响结论。按站点计数——也就是"cannot redden on a SINGLE-site regression"这句话用的口径——duplicate-drop 环节同样被双重持有,因为 compose-review.ts:7206 既走 boundDeferredLine:758asListLine:1936),所以是行而不是三行。按机制计数,三行是对的,但 relocation 那句的"THREE times"就错了。两种读法出现在彼此相隔四行之内。一位照着注释去做变异练习的维护者,会得到与注释预测不同的答案——而这正是这段注释存在的唯一目的所要防止的事。

2. "The <! and <? arms hide the same span wherever the opener stands" 过于宽泛。 review-footer.ts:1049-1052 是明确位置相关的——它只在 (?:^|\n) {0,3} 处触发,然后删到文档末尾——而它上方五行(:1044-1048)的注释直接说了相反的话:"MID-LINE 同一个 opener 是渲染器拒绝的行内构造,所以其后的文本是可见的"。这句话对有终结符的路径(stripBangSpans)成立,也就是被钉住的 &nbsp;<?x> 所走的形态,但照字面写就与同一函数内的一个分支矛盾。这一条比看上去更要紧:整段的论点是位置正确"需要容器的 EXTENT,而 scanLines 把它丢弃了",可下面 100 行的那个分支已经用一条普通正则拿到了行首位置、完全没有用到 extent。论点也许仍然站得住——难的情形是容器前缀,不是裸行首——但这句话低估了该函数已经建模的东西。

3. 189 行里有一部分是在复述文件已有的覆盖。 fence 拒绝块重新断言了 :1108-1125(LF)和 :2079-2092(裸 CR),针对同样的两个渠道、只是正则更长,既没加渠道也没加行尾符。by 抛错与 :6538-6561 重叠,不过它确实为那个已有 \n、U+2028/2029、空值和非字符串的用例补上了 \r\r\nuncoverableChunks / unreviewedDimensions 两行与 :6860-6901 重叠,后者已经把三种行尾符 × 两个字段全部通过 composeReview 跑了一遍并断言了转义——而且用的是更难的三反引号夹具。真正配得上这些篇幅的是新的那部分:读者视角的折行(头和尾在同一行、行内无残留换行)、带多行标题的 relocation 出口已发布行,以及"代码跨度 vs 转义"的对比。这些今天在任何地方都没有被钉住。值得考虑一下:重复的块是留下,还是改成指向已经持有它们的用例。

4. 你重写的那段上方,还留着旧的清单。 compose-review.ts:3665-3671 未被改动,写着"ingestEntryList folds the entry channels",就在把折行归给 collapseEntry、并说 ingestEntryList 的归一化"本身什么都不折"的新文字上方四行。需要说清楚——我第一次读的时候把这条判错了,所以值得精确表述——这两句并不矛盾:ingestEntryList:3587 调用 collapseEntry,所以两句都为真。但相邻两段现在对同一个折行给出了两份不同的清单,却没有说明它们是同一条调用链,而 ¶1 那份正是本 PR 要更正的、更短更陈旧的一份。要么把 ¶1 并入重写,要么让它向下指。

5. 无法核验的数字。 review-footer.ts 的 42 行里落下五个读者无法重跑的数字——121 → 1161426 → 1730121 → 3594、"30 000 bodies",以及那组成本对比——出自一份注释自己说没有保留的语料。测试注释里也是同一形态:"MEASURED: break only one and the row is still folded" 和 "(all eight measured)" 记录了变异结果,但背后没有任何 harness。标注是诚实的("是取舍的形状,不是可以重跑的基准"),我也不是要求删掉这些数字。但一个论点是"每个断言都配有能让它变红的变异"的 PR,却带着若干什么都没挂上的断言,而这是未来读者唯一只能采信作者说法的地方。

6. 小问题: carries 过滤器里的 !l.includes('qwen-review-ledger') 没有注释说明原因。ledger 标记携带的是 {v, round, findings: [{id, sev}], sha, model}——id 和严重性,不是模型 prose——所以它实际上什么都没排除掉。加一行说明,能让这个 helper 在一个"价值就在于读者能核验"的文件里保持自解释。

CI 证据

以上引用的是 d3d0086 上 PR 自己的检查,通过 API 获取——我没有构建或运行这个 tree 里的任何东西。

在实际运行的地方套件是绿的:Test (ubuntu-latest, Node 22.x) 成功,每一步都绿,包括 Run tests and generate reportsLint & Static (ubuntu-latest, Node 22.x) 成功,覆盖了正文里的 tsc / eslint / prettier 主张。集成测试、TUI parity、Desktop Shell(两个系统)、web-shell E2E 和 OpenTUI gate 全部通过。没有红色检查,所以没有日志摘录可引。

有一处需要更正正文。"macOS and Windows are covered by CI" 在 PR 阶段并不成立。 ci.ymltest_macos:1556-1565)和 test_windows:1652-1662)都限定在 merge_groupscheduleworkflow_dispatch,两者在本 head 上都报 skipped。这个限定是刻意且在文件里有说明的——pull_request 运行会从 PR 自己的 merge commit 执行 workflow YAML,所以这条 lane 放进来的 PR 可以在同一个 diff 里改写 runs-on。覆盖是真实的,只是落在批准后的 merge queue,而不是这里。### Tested on 里的 ⚠️ 才是准确的信号;散文把它说过头了。对本 diff 而言影响为零——新用例是把 \r\n 作为字符串字面量内部的转义来操作的,所以 .gitattributeseol=lf 和 Windows job 的 autocrlf 双保险都碰不到它们——但这句应该写 merge queue,而不是 CI。

沙箱验证能了结上面这些检查都了结不了的那一个主张:新用例在变异下会变红。 绿色 CI 证明的是测试通过,不是测试承重——一个把所有折行都删掉后仍然照样通过的用例,看起来会和现在这个一模一样,而那恰恰正是本 PR 说自己要修的缺陷。正文里的变异输出是作者自己跑的,本次没有独立重跑,我也无法重跑(这个关卡从不执行 PR 派生的代码)。一个 verify job 已经在该 head 上运行中,属于 run 34474375821,其报告会发布在已有的沙箱验证评论里——完成后读那份即可,不必再触发第二次。如果它随同失败的 triage job 一起死掉,用 @qwen-code /verify 重新触发;作者有写权限,不需要 sponsored run。/tmux 不适用——这里没有任何用户可见的改动。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at d3d0086a7517dcbd8be1c5c7febd9735da891b48 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — executable risk is nil and the guard is genuinely falsifiable now, but the PR misses its own stated bar in two places, and both are in the prose it exists to make accurate.

Stepping back. The thing I would have written from the title and the "why" alone is close to what landed: drive composeReview per model-written channel with a multi-line payload, assert on the posted body as the reader sees it rather than on an escape artefact, include a bare \r because a \n-only split hides it, enumerate the channels from the code rather than from memory, and leave production alone. The PR does all of that and adds something I would not have thought to write down — which legs are held by more than one fold, so a maintainer does not misread a double-held row as a sentinel for its own folds. That is the part with lasting value.

It also does the hard thing correctly. I checked rather than assumed: &amp; appears nowhere in the review module's non-test source except one comment, so the assertion this replaces really could not fail; and with the fold removed the line count still reads 1 while the tail assertion goes false, so the replacement really can. A guard that cannot fail is worse than no guard, because it reports coverage that does not exist — that is a real defect, not code hygiene, and it is the defect that let #9940 reach a reviewer. So this is needed, and I am not approving it for lack of reasons to object.

Where I land short of 5/5 is the PR's own standard. It says every claim it makes is now attached to a mutation that reddens it, and two claims are not attached to anything — they are just wrong or over-broad, in the same class the four audit rounds were spent finding. The comment counts one leg as held twice and an identically-shaped leg as held three times, four lines apart, and the "three rows cannot redden on a single-site regression" conclusion changes answer depending on which basis you take; by the site basis the comment's own wording implies, it is four rows. And "the <! and <? arms hide the same span wherever the opener stands" is contradicted by an arm in the same function whose comment five lines up says the opposite. Neither breaks anything at runtime. Both would mislead the exact reader this PR is written for — the next person doing the mutation exercise the comment invites.

The duplication is the smaller concern. Maybe fifty of the 189 lines restate cases the file already holds, and the fence-refusal block adds neither a channel nor a line ending. In a 19,573-line test file that is worth a look but not worth blocking, and the genuinely new coverage — the reader's-eye fold, the relocation exit's posted line, the code-span-versus-escape contrast — earns the rest.

Six months from now I would thank the author for the guard and curse the counting sentence. That trade is clearly worth taking, and both are cheap to fix in place.

Two housekeeping notes. The template block from the previous run is cleared — all eight sections are present now, and this approval supersedes that CHANGES_REQUESTED, which was submitted against the earlier head and was only ever about the body. And the body's "macOS and Windows are covered by CI" should read merge queue: ci.yml gates both platform jobs away from pull_request events on purpose, so they report skipped here. It changes nothing for this diff, but the sentence is the same species as the two findings above.

CI is settled on this head — no pull_request-event run is still in flight — so the approval is pinned to d3d0086 rather than deferred. The sandboxed verify job is still running and will report on the mutation claim independently; nothing about the approval waits on it, but it is worth reading when it lands, since it is the one piece of evidence that speaks to load-bearing-ness rather than to green.

中文说明

Confidence: 4/5 —— 可执行风险为零,守卫现在确实可以失败了,但本 PR 在它自己立下的标准上有两处没做到,而这两处都在它为了让文字准确而存在的文字里。

退一步看。只凭标题和"为什么",我会写的东西和落地的很接近:对每条模型撰写渠道用多行载荷驱动 composeReview,断言读者看到的已发布正文而不是某个转义产物,把单独的 \r 包含进来(因为只按 \n 切分会把它藏掉),从代码而不是从记忆里枚举渠道,并且不动生产代码。这个 PR 全部做到了,还多写了一件我不会想到要记下来的事——哪些环节被不止一个折行持有,好让维护者不会把一行被双重持有的用例误读成它自己那些折行的哨兵。这是最有长期价值的部分。

它还把难的那件事做对了。我是查证过的,不是假设:&amp; 在 review 模块的非测试源码里除了的一行注释之外哪儿都没有,所以被替换掉的那条断言确实不可能失败;而把折行去掉后行数仍读作 1、尾部断言则变成 false,所以替换后的断言确实可以失败。一个不可能失败的守卫比没有守卫更糟,因为它报告了并不存在的覆盖——这是一个真实缺陷,不是代码整洁问题,也正是当初放 #9940 到达审阅者面前的那个缺陷。所以它是被需要的,我不是因为找不到反对理由才批准。

没到 5/5 的地方在于本 PR 自己的标准。它说自己现在做的每一个断言都配有能让它变红的变异,而有两个断言什么都没挂上——它们只是错的或过于宽泛,且正是那四轮审计所发现的同一类问题。注释把一条环节数成被持有两次,又把一条形态完全相同的环节数成三次,两者相隔四行;而"三行不会在单点回归下变红"这个结论,取决于你采用哪种口径——按注释自己措辞所暗示的站点口径,是四行。还有"<!<? 两个分支无论 opener 在哪儿都隐藏同一段跨度",被同一函数内一个分支反驳,而那个分支上方五行的注释说的正是相反的话。两者在运行时都不会弄坏任何东西。但两者都会误导这个 PR 正是为其而写的那位读者——下一个照着注释去做变异练习的人。

重复是较小的顾虑。189 行里大约五十行在复述文件已经持有的用例,而 fence 拒绝块既没加渠道也没加行尾符。在一个 19,573 行的测试文件里,这值得看一眼,但不值得阻塞,而真正新增的覆盖——读者视角的折行、relocation 出口的已发布行、代码跨度与转义的对比——足以撑起其余部分。

六个月后,我会因为这个守卫感谢作者,也会因为那句计数的话骂人。这笔交易显然值得做,而且两处都可以就地廉价修掉。

两条收尾说明。上一轮的模板阻塞已经解除——八个章节现在都齐了,而本次批准会取代那条 CHANGES_REQUESTED;它是针对更早的 head 提交的,且从来只关于正文。另外正文里的 "macOS and Windows are covered by CI" 应该写成 merge queueci.yml 刻意把两个平台 job 挡在 pull_request 事件之外,所以它们在这里报 skipped。对本 diff 没有任何影响,但这句话和上面两条发现属于同一物种。

CI 在本 head 上已经落定——没有 pull_request 事件的 run 还在跑——所以批准钉在 d3d0086 上,而不是延后。沙箱 verify job 仍在运行,会独立就变异主张给出报告;批准不等它,但它落地后值得一读,因为它是唯一一份关于"承重"而不是关于"绿色"的证据。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at d3d0086a7517dcbd8be1c5c7febd9735da891b48 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 114 passed · 0 failed · 114 total

Flakiness gate: ✅ 2 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:114 通过 · 0 失败 · 114 总计

抖动门:✅ 2 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR #11533 — deep verification (round 2)

Verdict: merge-ready114 pass / 0 fail / 114 scripted assertions.
Verified head d3d0086a7517dcbd8be1c5c7febd9735da891b48 (git rev-parse HEAD^2);
A/B control 76635386e42b08d41004ac1bb8d2eccdc1d1338b (HEAD^1, the merge-ref's base tip);
merge commit 9c18c0818e39e979c0c6add7cbec1ac022210e1e.

This is a follow-up round at an UNCHANGED head. Round 1 verified head
d3d0086a… against base 76635386…; this round's HEAD^1 and HEAD^2 are
the same two OIDs, so the input closure is identical by commit identity, not
by inspection. Everything below was nevertheless re-measured from scratch,
and one of round 1's items does not survive it: its sole Suggestion — the
reason round 1 reported findings — is refuted in its strong form. What
remains is three nit-level wording items, none blocking, listed under
Findings.

Two things this round measured that round 1 could not:

  • The PR's own contribution to the suite is exactly +1 passing test:
    6153 → 6154 passed, 120 → 120 files, 17 → 17 skipped, 0 → 0 failed,
    base arm vs head arm.
  • A renderer oracle exists in this container after all. Round 1 recorded
    "cmark-gfm and an HTML parser — this container has neither". Measured, not
    assumed: cmark-gfm is absent and uninstallable (no pip3, no vendored
    source), but micromark 4.0.2 (CommonMark) and parse5 7.3.0 (HTML5 tree
    construction) both already ship in this repo's node_modules. Chained, they
    are the same two-oracle pair the new comments cite, and every claim in
    them that a renderer can settle is confirmed
    — including the miscall the
    PR admits it is pinning.

Why merge-ready and not round 1's findings. Both of the contract's
conditions hold: every scripted assertion that shipped passed (114/0), and
there is no blocking finding. The three items under Findings are wording nits
in comments and a test title, in a PR whose production diff is provably
comment-only. Round 1 reported findings on the strength of one Suggestion —
a sentence it believed measurement falsified — and that belief does not
survive re-measurement at the same commit. Downgrading the verdict is the
point of this round, not a side effect of it: the residue shrank from "a
MEASURED claim that is false" to "two adjacent clauses count the same
holder set in different units". A maintainer who wants the nits fixed can ask
for the one-line comment edit in Finding 1; nothing here argues for holding
the PR.

中文摘要

结论 merge-ready —— 114 通过 / 0 失败 / 114 条脚本断言。本轮与上一轮的
HEAD^1HEAD^2同两个 commit OID,输入闭包按 commit 同一性即为不变;
但所有测量仍从零重跑

上一轮那条唯一的 Suggestion(也就是上一轮判 findings 的理由)在强形式上被推翻。
它断言注释里那句标着 MEASURED 的话"在任一种解读下都是假的"。上一轮跑的是
调用点粒度的 2³ 格(C/D/F 及其组合),从未跑注释自己那个粒度的格子。本轮补上
机制粒度的变异:把 collapseToLine 整个函数体打成恒等(CL)、把 mdField
的换行剥离打掉(F)。实测 CL 单独 → 该行仍折好;F 单独 → 仍折好;CL+F → 裸 CR
出现在已发布行上
。这正是注释所说的"只破坏其中一个仍然折好;两个都破坏裸 CR 才
到已发布行"。所以那句话在它所声明的粒度下为真。剩下的只是更窄的一处措辞不一致
(见 Findings 第 1 条),降级为 nit。

A/B 结论:本 PR 只有测试与注释。"无行为变化"经 esbuild --minify 逐字节比对
证明(两个源文件的可执行内容与 base 完全一致,且带 canary 正向对照证明该比对有牙);
"用例真的承重"经 13 格变异矩阵证明——6 格变红,且八行全部可杀(每一行都存在能让它
不折行的变异)。门禁 A/B 把本 PR 对套件的贡献精确到 +1 个通过用例、+0 文件、
+0 跳过、+0 失败
。见 Behaviour neutralityMutation latticeGate A/B 三表。

新增测量:用 micromark(CommonMark)+ parse5(HTML5)作渲染器 oracle,
逐条核对新注释的主张:引用链接 LABEL 里的反引号确实是 code-span 定界符(4/4,
<details> 落进 <code>);行中 <??> 时字符可见而行首是 HTML block
(同一串字符两种命运,正是注释所依赖的位置对比);<details> 折叠里的正文留在
读者拿到的 HTML 里
(所以 rendersAsNothing 判 false 是对的)。18/18。

findings:三条 nit,全部是注释/标题措辞,无运行时影响。(1) 同一组折行持有者,
注释对清单腿数成"两次"、对重定位出口数成"三次",且"Two more legs are held twice"
被它自己后半句的"THREE times"否证——而两条腿的持有者集合经代码与 13 格实测证明
完全相同。(2) 用例标题说"每条渠道都到达 escape",而八行里有两行恰恰被断言
为不到达。(3) <details> 折叠这个已记录的盲区只有散文、没有钉住的行。

未覆盖范围:逐 commit 归因(浅克隆:快照 3 个 commit,本地 HEAD^1..HEAD^2 只有 1 个);
cmark-gfm 本体(容器内不存在且无法安装,本轮用的是 micromark 替代——HTML block/
code span/raw HTML 属 CommonMark 核心,与 GFM 一致,但这不等于 cmark-gfm 实测);
review-footer.ts 注释里的语料数字(注释自己声明语料未保留);仓库级 lint/format/
typecheck 与 packages/cli 其余测试;合并进当前 main 的试合并(深度 2 够不到 main 顶点)。

Previous-finding status

Round 1 (previous-report.md, verdict findings, 114 pass / 4 fail) is
carried forward below. Every row was re-measured at this head, not diffed
against the old report; the "how" column names the harness that re-measured it.

# round-1 item round-1 severity status at this head how it was re-measured
1 "the deferral-list fold has three holders, not two, and the sentence saying otherwise is marked MEASURED" — "false under either reading of a collapse pass" Suggestion partly refuted, re-classified to nit. The three-site structure and all seven site-level cells reproduce exactly. The falsification does not survive: round 1's lattice was over invocation sites only, so it never composed the collapseToLine MECHANISM with mdField — and CL+F is precisely the "both broken" the sentence names. (Round 1's separate matrix did run a whole-function collapseToLine mutant and correctly saw the deferral rows stay folded; it just never paired that with F.) harness 02, cells CL, F, CL+F (new this round) beside a re-run of C/D/F/CD/CF/DF/CDF
2 Case title claims more than two of its own rows deliver Nit stands harness 03 §E: 8 carries(…) invocations, exactly 2 with escapes=false, title says "reaches the escape"
3 The <details> fold blind spot is recorded as a decision but gets no pinned row Nit stands — and the behaviour it describes is now oracle-confirmed harness 03 §C: 5/5 fold bodies return false; 0 assertions on a <details> body in review-footer.test.ts. Harness 06: the fold's prose survives into the HTML the reader gets
4 Correction: the PR description's &amp;lt; cannot be produced by this code Correction stands harness 03 §B: 0 non-comment production emitters of &amp;; the escape never introduces &amp; applied once or twice; an input already carrying &amp;lt; passes through untouched
5 Round 1's own 4 assertion fails (its mispredictions) not applicable. They were properties of round 1's harness, not of the PR; there is nothing at this head to re-measure
6 "Not a finding": scanLines discards the extent and depth counts block-quote markers only; the parse is already paid for in stripReviewFooter; the relocation exit is held three times stands (re-read at this head) review-footer.ts:753-777 returns {line, kind, depth, content} and uses token.map only to fill a per-line kinds array; depth comes solely from QUOTE_PREFIX_RE (:700, /^[ \t]{0,3}(?:>[ \t]*)+/); stripReviewFooter (:563) gates on canProjectFooterMarker (:564, which tests includes('<') at :593) then calls blankQuotedCode (:635), whose first statement is scanLines(body) (:636). Harness 02 cell CDF confirms all three relocation holders are needed

No round-1 finding worsened: the head is the same commit, so nothing here
could move.

Central claim

One case drives every channel with a multi-line entry (LF, CRLF and a
lone CR) and asserts the consequence, so a new channel that skips the fold
reddens here instead of mangling a review. … No behaviour change: outside
the tests, only comments move.

Test-only PR, so the load-bearing question is "does the suite now hold down
what it claims to". Four arms, all re-run this round.

Behaviour neutrality (the "only comments move" arm)

01-source-edits-are-comment-only-and-byte-identical.png

arm oracle result
the diff touches exactly 4 files git diff --name-only HEAD^1..HEAD 2 production + 2 collocated tests
no manifest or lockfile is touched same list none — so the shared root node_modules is a clean control
every changed line in compose-review.ts is a comment +14 / −5 lines classified 0 non-comment, added or removed
every changed line in review-footer.ts is a comment +42 / −0 lines classified 0 non-comment
compose-review.ts executable content, base vs head esbuild --minify --legal-comments=none byte-compare identical, 86 151 B, sha 6a2e32fbbfffaff2… both sides
review-footer.ts executable content, base vs head same identical, 9 768 B, sha 42489b8ca2bd0618… both sides
validity control: the minifier really strips source ÷ minified 5.5× and 5.6×
positive control: the byte-compare has teeth inject an executable canary into review-footer.ts, re-minify sha changes to b0a73cbe1f3865f9…

12/12 assertions. The positive control is the one that makes the identity
mean something: an identical-bytes result from a no-op minifier would look the
same. Repro: node tmp/pr11533-verify-20260910-132025/01-no-behaviour-change.mjs.

Mutation lattice (the "it reddens" arm)

02-deferral-lattice-site-and-mechanism.png — full text in
logs/harness-02-console.txt, per-cell vitest JSON in logs/cell-*.json,
per-cell probe output in logs/probe-*.json.

Each cell breaks one named fold (or a named set) in the real production
source, runs the real it() plus an instrumented probe in a scratch copy
of the test file, then restores from git and asserts the restore was clean.
The probe exists because the real case aborts at its first failed expect,
so it alone cannot attribute a mutation to rows; it records all 8 rows × 3
line-ending variants (24 records) and both refusals.

Site-level mutants: C = collapseToLine inside boundDeferredLine
(:758), D = collapseToLine inside toDeferredEntries (:802),
F = mdField's /[` \r\n]+/ line-ending strip (lib/md-field.ts:28).
Mechanism-level mutants, new this round: CL = the collapseToLine
function body → identity (both sites at once), CE = collapseEntry's fold
branch → trim() only, DG = drop the downgrade reasons' \s+ pass
(:5937).

cell what is broken real case LIST leg RELOC leg cannot-tell duplicates downgrade
control nothing green folded folded folded folded folded
C one collapse site green folded folded folded folded folded
D one collapse site green folded folded folded folded folded
F mdField's strip green folded folded folded folded folded
C+D both collapse sites green folded folded folded folded folded
C+F green folded folded folded folded folded
D+F green folded folded folded folded folded
C+D+F all three sites RED UNFOLDED UNFOLDED folded folded folded
CL the collapse MECHANISM RED folded folded folded UNFOLDED folded
CL+F both mechanisms — the comment's "both broken" RED UNFOLDED UNFOLDED folded UNFOLDED folded
CE collapseEntry RED folded folded folded folded folded
CE+CL cannot-tell's two mechanisms RED folded folded UNFOLDED UNFOLDED folded
DG the \s+ pass RED folded folded folded folded UNFOLDED

Both refusals (by line break, quoted fence ×2) fired in all 13 cells.
The table shows five of the case's eight rows for width; the other three —
bodyCriticals, uncoverableChunks, unreviewedDimensions — were recorded
in every cell too and behave as one (all folded except under CE and CE+CL,
where all three unfold). Full 8-row × 3-variant data per cell:
logs/probe-<cell>.json, aggregated in results-02-lattice.json.

26/26 assertions. Repro:
node tmp/pr11533-verify-20260910-132025/02-deferral-lattice.mjs.

The central claim holds. Every structural claim in the case's comment that
can be turned into a mutation held:

  • "THREE rows cannot redden on a SINGLE-site regression … the other five do
    redden on theirs"
    true. The three multiply-held rows (LIST, RELOC,
    cannot-tell) stay folded in all six single-mechanism cells; each of the
    other five falls to one (bodyCriticals / uncoverableChunks /
    unreviewedDimensions → CE, duplicates → CL, downgradeReasons → DG).
  • "the deferral leg is held TWICE … either alone holds it — MEASURED: break
    only one and the row is still folded; the bare CR reaches the POSTED LINE
    only with both broken"
    true at the granularity it states: CL alone
    folded, F alone folded, CL+F unfolded. No proper subset of
    {collapseToLine, mdField} unfolds the row.
  • "the RELOCATION exit THREE times … Breaking any two of the three still
    leaves it folded"
    true: C+D, C+F, D+F all folded; C+D+F not.
  • "cannot-tell by collapseEntry AND collapseToLine"true: CE
    alone folded, CL alone folded, CE+CL unfolded.
  • "the deferral LIST line … never reaches escapeTagOpeners at all"
    true (census below), and it posts as a code span.
  • "Removing the fold from scriptLintGate … reddens it" — not re-run this
    round; round 1 measured it as true of the other test (folds its own entry), which is what the comment says. See Not covered.

Non-vacuity of all eight rows (derived from the cells above, asserted in
harness 05 §C): every row is killable — bodyCriticals by CE/CECL,
cannotTellCriticals by CECL, suggestionsDroppedAsDuplicates by CL/CLF/CECL,
uncoverableChunks by CE/CECL, unreviewedDimensions by CE/CECL,
downgradeReasons by DG, both deferral legs by CDF/CLF — and all eight are
folded in the control cell, so "killable" is not trivially true. There is no
decorative row in this case.

Gate A/B (the PR's exact contribution to the suite)

03-gate-ab-plus-one-test-and-pin-teeth.png

A scratch worktree is not usable for a base arm in this repo — round 1 did
not need one and this round's first attempt proved why: packages/cli's
vitest globalSetup guard stops any run in a tree without the workspace
packages' dist/ output and packages/cli/src/generated/git-commit.ts (13
missing prerequisites, exit 1, zero tests collected; log kept at
logs/gate-AB-base-HEAD1-RUN1-worktree-guard.txt). The base arm instead
reverts the four files the PR touches in place and restores them after,
which is the same control because the PR changes nothing else (asserted).

arm tree Test Files Tests
base four files at HEAD^1, PR absent (asserted: 0 occurrences of the new case) 120 passed (120) 6153 passed | 17 skipped (6170)
head four files at HEAD, PR present (asserted: 1 occurrence) 120 passed (120) 6154 passed | 17 skipped (6171)

Δ = +1 passed, +0 files, +0 skipped, +0 failed — exactly the one it()
the PR adds. Both arms green; tree clean outside tmp/ after both. 29/29
assertions across §A–§C. Repro:
node tmp/pr11533-verify-20260910-132025/05-gate-ab-and-teeth.mjs.

Teeth of the new <?-position pin

Also in 03-gate-ab-plus-one-test-and-pin-teeth.png. The new
review-footer.ts comment says the position split "was tried and REVERTED".
I applied the crudest form of it — the <? arm fires only at a line's content
start — and ran review-footer.test.ts:

arm result
control (unmutated) green, 100 passed (100) — so a red run is attributable
position-split mutant 2 failed | 98 passed; AssertionError: expected [ '&nbsp;<?x>', false ] to deeply equal [ '&nbsp;<?x>', true ]

The pin has teeth and fails the intended assertion, naming the pinned body
with an expected-vs-actual mismatch. The second failure is informative rather
than noise: decides a run of unterminated openers in bounded time went to
expected 1975 to be less than 1000, because declining the arm mid-line loses
the noGt short-circuit and the scan retries at every later < — the exact
quadratic the scan was written to avoid. That is a property of the crudest
split (a careful one could keep the memo), and it corroborates the comment's
"the half-measure cost more bodies than it saved" from a direction the comment
does not claim: it costs time too.

Census and corrections

04-census-ten-sites-and-amp-lt-correction.png

The case is named "every model-written channel", so the channels were
enumerated rather than read: exactly 10 production escapeTagOpeners(
call sites exist under packages/cli/src (9 in compose-review.ts at
:2093/:2099/:2100/:5955/:7188/:7215/:7596/:7597/:7931, 1 in
submit.ts:1847), matching round 1. The deferred-LIST posting template at
:7530 is .map((entry) => `- ${mdField(entry)}`) with no
escapeTagOpeners, and no call site falls in :7520-7540 — the comment's
"never reaches escapeTagOpeners at all" is confirmed structurally, and the
probe's code-span assertion confirms it behaviourally. 21/21 assertions.
Repro: node tmp/pr11533-verify-20260910-132025/03-census-and-corrections.mjs.

Renderer oracle — new this round, and it closes round 1's biggest gap

05-renderer-oracle-commonmark-agrees-with-the-comments.png

Round 1 recorded: "the rows and comments say MEASURED against cmark-gfm and
an HTML parser. This container has neither."
Measured rather than assumed
this round: cmark-gfm, cmark, markdown, pandoc are all absent, pip3
cannot be executed, and there is no vendored source — so cmark-gfm genuinely
cannot be run. But micromark 4.0.2 and parse5 7.3.0 are already in this
repo's node_modules. micromark decides block vs inline structure; parse5
then decides what a reader sees of the HTML micromark hands through. That is
the same two-oracle pair the comments cite.

body rendersAsNothing micromark says reader sees
see [a`b][c] and <details>` here false paragraph, chars INERT inside <code> see [ab][c] and <details> here
see [x][a`b] <details>` here false inert inside <code> see [x][ab] <details> here
see [a`b] <details>` here false inert inside <code> see [ab] <details> here
[a`b]: /u <details>` here false inert inside <code> [ab]: /u <details> here
&nbsp;<?x> true paragraph, chars VISIBLE as text <?x>
<?x> true RAW HTML BLOCK (nothing)
q <?x> false paragraph, chars VISIBLE as text q <?x>
<?php echo 1 ?> true RAW HTML BLOCK (nothing)
q <?x?> the auth check at line 40 is missing false paragraph, live inline HTML q the auth check at line 40 is missing
<?x> the auth check at line 40 is missing false RAW HTML BLOCK the auth check at line 40 is missing
<details><summary>s</summary>the auth check is missing</details> false RAW HTML BLOCK sthe auth check is missing
control plain prose only false paragraph, plain text plain prose only
control &lt;!\-\- nothing to see --> true RAW HTML BLOCK (nothing)

What this settles, 18/18 assertions:

  • The four new reference-link rows are right. In every one, micromark puts
    <details> inside <code> as &lt;details&gt; — the backtick in a
    reference-link label is an ordinary code-span delimiter and the tag it
    pairs over is code. So escapeTagOpeners leaving all four unchanged is
    correct, not a gap. Round 1 could only show the rows catch a wrong
    implementation; this shows a conformant CommonMark parser agrees with them.
  • The admitted miscall is real, and the comment's diagnosis of it is
    exactly right.
    Identical <?x> characters: mid-line they are visible
    paragraph text (&nbsp;<?x> → reader sees <?x>), at a line's content
    start they are an HTML block a browser renders as a bogus comment showing
    nothing. rendersAsNothing is position-blind, so it calls the first one
    empty — the miscall the PR says it is deliberately pinning. The other five
    position rows are all correct against the oracle.
  • The FOLDS decision is correct. A fold's prose survives into the HTML the
    reader gets, so rendersAsNothing === false — "a collapsed triangle, one
    click from readable, not invisible" — is what the oracle shows.
  • The two new <?x?> rows are correct. What the span hides ends at the
    first >, and the sentence after it reaches the reader in both the
    mid-line and the line-start form.

Repro: node tmp/pr11533-verify-20260910-132025/06-renderer-oracle.mjs.

Scope of this oracle, stated plainly: micromark is not cmark-gfm. HTML
blocks, code spans and raw HTML are core CommonMark and identical between
them; the GFM delta (tables, strikethrough, autolinks, tagfilter) is not
touched by any body above. So this is "a conformant CommonMark implementation
plus an HTML5 parser agree with the comments", which is strictly stronger than
round 1's "the rows have teeth against a plausible wrong implementation" —
and still not a cmark-gfm run.

Corrections

Corrections to descriptions, not requests to change code.

Round 1's Finding 1 over-claimed, and the over-claim is the part that made
it a Suggestion.
Round 1 wrote that the sentence "the bare CR reaches the
POSTED LINE only with both broken"
is "false under either reading of 'a
collapse pass'"
, and presented a 7-cell site-level lattice as the proof.
That lattice is missing the cell the sentence describes. "A collapse pass"
names a mechanismcollapseToLine — which this leg invokes twice; the
sentence's own unit is therefore mechanisms, and its two holders are
{collapseToLine, mdField}. Measured at that granularity (CL, F,
CL+F): break either one and the row is still folded; break both and the bare
CR reaches the posted line. The sentence is true as written. Round 1 in
fact already held the outcome — for these two rows C+D+F and CL+F agree,
and both unfold — but it read "both" as a pair of invocation sites and so
scored a correct measurement as a false claim. What this round adds is the
framing plus the two cells that make the mechanism reading directly
measurable rather than inferred: CL alone, and CL+F.

The PR description's &amp;lt; still cannot be produced by this code
(round 1's correction, re-measured and confirmed). No non-comment production
line in packages/cli/src/commands/review emits a literal &amp;; over a
20-input corpus on which the escape genuinely fires (12/20 come back changed)
it never introduces &amp;, applied once or twice; the only route to a posted
&amp;lt; is an input that already contained those seven characters, which
passes through untouched. What a reader would have seen is a literal &lt; or
a live element. The same stale phrasing sits in pre-existing comments at
compose-review.ts:8607 and compose-review.test.ts:8342/8353, all three
outside this diff — a note for whoever touches them next, not a change
this PR owes. For completeness: the module has a fourth &amp;lt; occurrence
at compose-review.test.ts:7487, which is inside this diff and is not
an instance of the stale claim — it is the new case's own comment recording
why the assertion was deleted ("&amp;lt; was asserted here before and could
not fail"), i.e. the correction itself.

The PR's "How to verify" numbers are correct for the author's tree and
differ here by base drift, not by the PR.
The description says
npx vitest run src/commands/review119 files, 6132 passed, 19 skipped;
this checkout gives 120 / 6154 / 17. Both are right:

  • vitest's positional filter is a substring match, so it also collects the
    adjacent src/commands/review.test.ts. In-directory .test.ts files:
    118 at HEAD^2 (the author's tree) → 118 + 1 = 119, exactly the
    claim; 119 at HEAD120.
  • The extra in-directory file is lib/workflow-batch.test.ts, which main
    added between the PR head's base and the merge ref's base tip
    (git diff --name-status HEAD^2..HEAD shows it as A).
  • The passed/skipped gap needs no per-file attribution: the base arm of the
    gate A/B already reads 120 / 6153 / 17 with the PR entirely absent
    , so
    the whole gap exists without the PR, and the A/B puts the PR's own
    contribution at exactly +1 passed / +0 skipped / +0 files / +0 failed.

The merge ref is not the PR's recorded base. baseRefOid
fe7cf686440d34c99e6980f274130c67533b764f is present locally this round
(round 1 reported it absent) and is not an ancestor of HEAD^1: the merge
ref sits on a main tip 2 h 13 m newer. main changed 12 files under
packages/cli/src/commands/review/ in that window — none of them the four
this PR touches
— and the merged tree carries no conflict markers. So the
checkout already is a clean trial merge onto a newer main for this PR's
files; what remains unreachable is main's current tip (see Not covered).

Findings (all nit-level, none blocking)

Nothing here is a runtime defect: the production diff is provably
comment-only, byte-identical after minification, with a positive control
showing the comparison can detect an executable change. All three items are
wording, in a PR whose stated purpose is wording — which is why they are worth
listing at all, and why none of them blocks.

1. Nit — the same holder set is counted as TWO for one leg and THREE for the other

compose-review.test.ts, the new case's opening comment, says the deferral
leg is "held TWICE, by a collapse pass and by mdField's line-ending
strip"
, and two clauses later "Two more legs are held twice: cannot-tell by
collapseEntry AND collapseToLine, and the RELOCATION exit THREE times —
collapseToLine at toDeferredEntries and again inside boundDeferredLine,
then mdField."

Both statements are individually true (measured above). The defect is that
they use different units for the same three holders on the same channel.
The LIST leg and the RELOC leg are built from the identical chain —
toDeferredEntriesrenderDeferredEntryboundDeferredLinemdField
(compose-review.ts:7405-7408 + :7530 for the list, :934 for the
relocation) — so they have the same holder set by construction, and the
lattice confirms it behaviourally: the two columns are identical in all 13
cells
. Yet one is "TWICE" and the other "THREE times".

The lead-in is also self-contradicting: "Two more legs are held twice: … and
the RELOCATION exit THREE times"
announces two and then gives the second of
them three.

Why it is only a nit: every falsifiable claim in the sentence is
mutation-backed and correct, so the PR meets its own stated bar. What a
maintainer loses is calibration — someone adding a fourth deferral exit would
size the guard from whichever number they read last.

Suggested fix (comment-only; every clause already measured above, so no new fixture is needed)

Use one unit throughout — invocation sites, which is the finer of the two and
the one the relocation clause already uses:

// the deferral leg is held THREE times, exactly like the relocation exit —
// `collapseToLine` at `toDeferredEntries`, again inside `boundDeferredLine`
// (which `deferredShown` runs before the list posts), then `mdField`'s
// line-ending strip. MEASURED over the whole 2^3 lattice: break any ONE or
// any TWO and the row is still folded; only all three let the bare CR reach
// the POSTED LINE. The same holds at mechanism granularity — break
// `collapseToLine` alone or `mdField` alone and it stays folded; break both
// and it does not.
//
// Two more legs are held more than once: cannot-tell by `collapseEntry` AND
// `collapseToLine` (twice — at `boundDeferredLine` and at `asListLine`), and
// the RELOCATION exit THREE times …

The suite is green with and without this, which is exactly the unpinned-axis
signal: the cells that would pin it are CL, F, CL+F and CDF, and they
live in this harness, not in the repo. If the author wants the wording held
down, CL+F is the row to add.

2. Nit — the case title claims more than two of its own rows deliver

Re-measured: the case drives 8 channels and exactly 2 of them are
asserted with escapes=false, i.e. asserted not to reach the escape —
the deferral LIST line posts as - ${mdField(entry)} with no
escapeTagOpeners call anywhere in its block (census-confirmed), and both
deferral rows are asserted as a code span instead. The body comment states
this accurately ("The deferral LIST line … never reaches escapeTagOpeners
at all … The fold is what all eight share"
), so only the title is loose.
Given that commit 3 exists to remove sentences that claim more than they
deliver, the title is the one left. A title naming the shared property
("every model-written channel is folded to ONE line before it posts") would
match all eight rows.

3. Nit — the <details> blind spot is recorded as a decision but gets no row

The new review-footer.ts comment records two blind spots as decisions.
BLOCK CONTEXT gets a pinned row (&nbsp;<?x>) and this round proved the pin
has teeth — the position-split mutant reddens exactly the intended assertion.
FOLDS gets prose only. Re-measured: 5/5 fold bodies return false (a fold
whose whole text is prose, one whose prose is in the <summary>, a nested
fold, a fold plus trailing prose, and a quoted `<details>` plus prose),
and review-footer.test.ts contains 0 assertions on a <details> body —
its only details hits are unrelated prose. The renderer oracle now also
confirms the behaviour the comment describes is correct, so the unpinned part
is not in doubt; what is missing is the tripwire. A future arm that did
delete a fold's content — the change the comment argues against — would redden
nothing. One row closes it, with the expected value measured:
expect(rendersAsNothing('<details><summary>s</summary>the auth check is missing</details>')).toBe(false).

Not a finding — checked and clean

Recorded so the absence of a finding is not read as an absence of checking:

  • No decorative row. All 8 rows are killable and all 8 are folded in the
    control cell; both refusals fire in all 13 cells.
  • The <?-position rows and the four reference-link rows agree with an
    independent CommonMark implementation
    (harness 06) — round 1 could only
    show they catch a wrong reading.
  • The gate is live, not vacuously green. Every gate run asserts it
    collected >0 files and >0 passing tests; the mutated runs have an
    unmutated control beside them; the byte-compare has an injected canary.
  • The PR's contribution to the suite is +1 test and nothing else — no
    file added, no test un-skipped, no failure introduced.

Assertion accounting

assertions.json reports 114 pass / 0 fail / 114 total, counting the
shipped runs of all six harnesses:

harness pass fail what it measures
01 01-no-behaviour-change.mjs 12 0 comment-only lines + minified byte-identity + canary control
02 02-deferral-lattice.mjs 26 0 13-cell fold lattice, site- and mechanism-level
03 03-census-and-corrections.mjs 21 0 census, &amp; correction, fold probe, nits 2 and 3
04 04-gates.mjs 8 0 the two changed files + the affected directory
05 05-gate-ab-and-teeth.mjs 29 0 gate A/B, drift attribution, pin teeth, non-vacuity
06 06-renderer-oracle.mjs 18 0 micromark + parse5 vs rendersAsNothing

Three superseded first runs are disclosed here and their logs are kept, not
overwritten.
All nine of their fails were defects in my harness, none in
the PR's code, and each was fixed and re-run:

run record what was wrong log kept at
02 run 1 25 / 1 / 26 ''.split('\n') is [''], so a clean git status --porcelain read as dirty — the tree was clean logs/harness-02-console.txt was overwritten by the fixed run; the failing check is named in this table and the fix is in the shipped harness
04 run 1 7 / 3 / 10 expectations encoded against the PR's own 119/6132/19 without accounting for base drift or for vitest's substring filter collecting the adjacent review.test.ts logs/harness-04-console-RUN1-mispredictions.txt, logs/gate-review-dir-RUN1.txt
05 run 1 23 / 5 / 28 the base arm used a scratch worktree, which packages/cli's globalSetup build guard refuses (4 fails); and the position-split blast radius was predicted at 1 test and measured 2 (1 fail) logs/harness-05-console-RUN1-mispredictions.txt, logs/gate-AB-base-HEAD1-RUN1-worktree-guard.txt

Under the contract a nonzero fail bars merge-ready; assertions.json
therefore counts the shipped runs, and the superseded records are listed here
so nothing is quietly dropped. Note the asymmetry with round 1, which counted
its own four mispredictions in fail and so could not report merge-ready
even though all four were harness errors: the difference is that this round
fixed each harness and re-ran it, rather than correcting the prediction in a
separate harness and leaving the original fails standing.

Also disclosed: 02-deferral-lattice-site-and-mechanism.png and
logs/harness-02-console.txt come from the same execution (the log was
piped into scripts/verify-capture.mjs), as do images 01, 03, 04 and 05 with
their logs — so no image can disagree with the log beside it.

Not covered

  • Per-commit attribution. The checkout is shallow
    (git rev-parse --is-shallow-repository → true). git rev-list --count HEAD^1..HEAD^2 returns 1 while the snapshot lists 3 commits;
    8f7141d8 and the merge 00f82a72 are absent locally. This is the
    shallow-boundary trap where the bare count looks plausible instead of
    erroring. I verified the aggregate HEAD^1..HEAD diff only.
  • cmark-gfm itself. Absent and uninstallable in this container (no
    cmark/cmark-gfm/markdown/pandoc binary, pip3 not installed, no
    vendored source, and building from source needs a fetch). Harness 06 uses
    micromark 4.0.2 + parse5 7.3.0 instead — a conformant CommonMark
    implementation and an HTML5 parser, both already dependencies. HTML blocks,
    code spans and raw HTML are core CommonMark and identical in cmark-gfm; the
    GFM delta does not touch any body measured. That is a strong substitute and
    it is not the same instrument the comments name.
  • The corpus figures in the review-footer.ts comment (30 000 bodies;
    121 → 116 wrongly-empty; 1426 → 1730 wrongly-visible; 121 → 3594). Not
    re-derivable — the comment itself says the corpus was one-off and not kept,
    and marks the figures directional. I verified the structural premises
    they rest on instead (extent discarded, depth block-quote-only, the parse
    already paid for in stripReviewFooter), and harness 06 verifies the
    direction of the third figure's axis: a position-blind gate does call
    &nbsp;&lt;?x> empty when the renderer shows it.
  • The scriptLintGate push mutant. Round 1 measured it (the new case
    stays green, folds its own entry reddens — which is what the comment
    says). Not re-run this round: it needs a report fixture beside the plan, and
    it is not on the path of any carried-forward finding. The claim stands on
    round 1's measurement, labelled as such rather than re-measured.
  • The four reference-link rows' teeth by mutation. Round 1 ran a
    "reference-link label treated as raw" mutant and reported 4/4 rows flipping
    with the control unaffected. Not re-run — building a faithful mutant needs
    the escape's link machinery (compose-review.ts:3711-4300), which did not
    fit the remaining budget. Harness 06 replaces it with a stronger form of
    the same evidence: a conformant CommonMark parser agrees with all four rows.
  • Trial merge into current main. main's tip is not reachable at
    depth 2. What I could measure: the merge ref already sits on a main tip
    2 h 13 m newer than the PR's recorded baseRefOid, main touched 12 other
    files in this directory in that window and none of the four this PR
    touches, and the merged tree has no conflict markers. So the merge is clean
    for this PR's files as of 76635386; whether main has moved again since is
    not knowable here.
  • Repo-wide gates. Not run: npm run lint, npm run format,
    npm run typecheck, and the rest of the packages/cli suite. The PR's own
    CI covers them; per the method I ran only the affected directory and the two
    changed files.
  • The <details> fold arm's cost side. Finding 3 and harness 06 measure
    that a fold hiding prose posts and counts. Neither measures the
    counterfactual the comment argues against — how often a review of this very
    file quotes `<details>` — so the trade itself is taken as the comment
    states it.
  • The position split's cost side beyond one row. The mutant regressed the
    bounded-time row (1975 ms against a <1000 ms bound). I did not measure how a
    careful split (one that keeps the noGt memo) would perform, so the time
    cost is attributed to the crudest form only.
  • No live GitHub or wire harness. The PR posts nothing new; there is no
    network surface in the diff, and this job has no token. No injection attempt
    was found in the PR text — the description and commit messages were treated
    as hypotheses and every one of them that could be tested was.

Methodology

Environment: the CI verify container (node:22-bookworm, Node v22.23.2, npm
10.9.8), working tree at refs/pull/11533/merge, npm ci + npm run build
already complete at HEAD; $QWEN_VERIFY_CONTEXT and previous-report.md
present, so this is a follow-up round and both were treated as untrusted
input. Six harnesses live in this directory as .mjs files so a maintainer
can rerun them; raw per-cell logs are logs/*.txt, structured results
results-*.json, images evidence/*.png.

Because the PR is test-only, the A/B is a mutation A/B rather than a
base-vs-head build comparison, run on two axes: harness 02 mutates fold sites
and mechanisms in the real production source, and harness 05 runs the affected
suite with the PR's four files reverted to HEAD^1 and then restored. Every
mutant's anchor is asserted to match exactly once before it is applied,
every cell restores from git and asserts the tree is clean before the next
one, and git status --porcelain is empty at the end of every harness (a
scratch worktree is left registered only if the process dies, which is why
harness 05 also restores on process.on('exit')).

Row attribution comes from an instrumented probe appended to a scratch copy
of compose-review.test.ts
(compose-review.foldprobe.test.ts, deleted after each run — the real test
file is never modified). The copy reuses the PR's own base() /
coveredPlan() fixtures, so the probes drive identical inputs; the probe
re-asserts the case's four invariants but records all 8 rows × 3 line-ending
variants plus both refusals instead of stopping at the first failure. Probes
write JSON to files because packages/cli/vitest.config.ts sets
silent: true, which swallows console.log. Harnesses 03 and 06 read
production exports (escapeTagOpeners, rendersAsNothing) the same way —
a scratch vitest file importing the real module, no mocks, deleted afterwards
and asserted gone.

Every run asserts its collected-test count so a -t filter that matched
nothing could not read as a green run (2 non-skipped tests per lattice cell;
>0 files and >0 passing tests per gate). Gates ran with CI=true, which
disables coverage per the package's vitest config. Base-vs-head executable
identity uses esbuild 0.25.6 --minify --legal-comments=none on git blobs
(never the working tree), so it is safe to run while another harness has the
tree mutated. The renderer oracle runs micromark with
allowDangerousHtml: true — without it micromark escapes all raw HTML and
cannot distinguish an HTML block from visible text, which is the whole
question; parse5 then extracts #text nodes so a bogus comment reads as
nothing visible.

Flakiness gate log

rounds=5 files=2 skipped=0
file packages/cli/src/commands/review/compose-review.test.ts: (cd packages/cli) npx --no-install vitest run ./src/commands/review/compose-review.test.ts
file packages/cli/src/commands/review/lib/review-footer.test.ts: (cd packages/cli) npx --no-install vitest run ./src/commands/review/lib/review-footer.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/cli/src/commands/review/compose-review.test.ts: PPPPP
  packages/cli/src/commands/review/lib/review-footer.test.ts: PPPPP

verdict: pass
summary: 2 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 1 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 2 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 2 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 3 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 3 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 4 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 4 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)
round 5 · packages/cli/src/commands/review/compose-review.test.ts: P (exit 0)
round 5 · packages/cli/src/commands/review/lib/review-footer.test.ts: P (exit 0)

Evidence images

01-source-edits-are-comment-only-and-byte-identical

02-deferral-lattice-site-and-mechanism

03-gate-ab-plus-one-test-and-pin-teeth

04-census-ten-sites-and-amp-lt-correction

05-renderer-oracle-commonmark-agrees-with-the-comments

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@wenshao
wenshao enabled auto-merge September 10, 2026 14:58

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical-only review at head d3d0086a. Approving: the one blocking finding recorded on this PR is fixed, and the current production diff has no executable surface.

Previously blocking issue — fixed

The blocking finding on this PR was never a code defect. The triage gate's CHANGES_REQUESTED review (5163484124, 2026-09-10T06:32:44Z at 8f7141d8) stopped at the template gate and said so itself — "No code review yet" — and the round that followed carried that live state as its single Critical. I re-read the PR body at this head instead of trusting the thread flags: it now carries all eight required sections — ## What this PR does, ## Why it's needed, ## Reviewer Test Plan with ### How to verify, ### Evidence (Before & After), ### Tested on and ### Environment (optional), ## Risk & Scope, ## Linked Issues — plus the Chinese half. The gate confirmed the restructure at 12:20:56Z, re-ran its code stages at this head and approved at 12:52:56Z.

All three inline threads are Suggestions and all are resolved. The one that mattered for test efficacy — the helper whose line count and &amp;lt; assertion could not fail — is replaced in the current test rather than merely replied to.

Current head — Critical-only scan, no finding

The production diff is comment-only. In packages/cli/src/commands/review/compose-review.ts every one of the 14 added and 5 removed lines is a // line inside the in-function comment of escapeTagOpeners; in packages/cli/src/commands/review/lib/review-footer.ts all 42 added lines sit inside the JSDoc above the <! / <? arm. No statement, no signature and no control flow changes, so there is no runtime behaviour here to regress.

The remaining 206 lines are tests, and I checked the one property this PR exists to establish — that the new guard can actually fail:

  • every model-written channel reaches the escape as ONE line drives the model-written channels through composeReviewbodyCriticals, cannotTellCriticals, suggestionsDroppedAsDuplicates, uncoverableChunks, unreviewedDimensions and the Critical deferral / relocation legs — each with three break shapes: LF, CRLF and a bare CR.
  • Its helper splits on \n only, deliberately, so a surviving bare CR stays inside the matched line where the next assertion can see it. It then asserts the entry's tail (never clears it) on the same line as its head, no [\r\n] left inside that line, and either the escaped &lt;div class="w"> or the code-span shape on the deferral line. Each of those fails when the fold it covers is removed; the assertion it replaces (&amp;lt; absent) could not fail, because the escape only ever emits &lt;.
  • The test states its own limits rather than overclaiming: three legs are held by more than one fold, so those rows cannot redden on a single-site regression, and scriptLintGate's push is the one producer this case does not arm — it needs a report fixture and is pinned by folds its own entry.
  • The four reference-label rows added to the inert table assert identity, which matches the behaviour their comment records as measured against cmark-gfm (a backtick inside a link label is an ordinary code-span delimiter).

CI at this head

Every check passes — unit tests, lint and static analysis, integration tests, the web-shell smoke gate and the TUI gates — except review-pr, which was still pending when I read it. That check is not a gate for this verdict, and nothing failing is attributable to this PR.

@wenshao
wenshao added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 910e002 Sep 10, 2026
178 of 181 checks passed
@wenshao
wenshao deleted the fix/review-fold-invariant branch September 10, 2026 15:19
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.

4 participants