Skip to content

feat(review): measured failure attribution, round ledger, richer mutants, doc parity - #8218

Merged
wenshao merged 35 commits into
mainfrom
feat/review-round2-borrowings
Aug 1, 2026
Merged

feat(review): measured failure attribution, round ledger, richer mutants, doc parity#8218
wenshao merged 35 commits into
mainfrom
feat/review-round2-borrowings

Conversation

@wenshao

@wenshao wenshao commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Stacked on #8215. Four more /review capabilities, borrowed from the shape of a live round-2 maintainer re-verification (#7818's round-2 comment):

  1. Measured test-failure attribution (qwen review test-delta). Agent 7 has always judged failures by path — a failure in a touched file is a Critical, in an untouched file pre-existing — which misclassifies in both directions. The new subcommand reruns the PR side's failed test commands on the merge base feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215's base-tree builds, and diffs the failing file sets: netNew fails on the PR side only (the PR's own, whatever files the diff touches), shared fails on base too (pre-existing by measurement). Counts are deliberately never compared — a flaky suite fails different test names between two runs of the same tree — and a base rerun that times out attributes nothing rather than promoting PR-side failures to evidence.
  2. A round ledger in the incremental review cache. A human re-reviewer's round-2 comment opens with "M1 is fixed"; the pipeline's round 2 opened with a fresh list, because the cache stored only a count and a verdict. The cache now persists confirmed findings under round-scoped ids (R1-2), and a high-effort incremental re-review owes each one a ruling — fixed / still stands / cannot tell — under the same trace-the-mechanism bar the open-Criticals re-check already enforces, rendered as a table at the top of the report.
  3. Three replacement mutation operators in test-efficacy, each lifted from a survivor the deletion operator cannot express: drop a ?? fallback (the miss path is frequently the entire safety property), force a comparison-bearing guard condition to true (a survivor means nothing pins when the guard must not fire — the round-2 fix in feat(cli): add /model --compaction for configurable chat compression model #7818 shipped exactly so), and drop a + UPPER_CONST term (an unpinned reserve in an estimate). A line whose raw text and literal-blanked code view disagree yields no candidate at all — an edit index computed across the two views once spliced iftrue 0) into a guard, and a mangled mutant reads as inconclusive while quietly spending a cap slot. Deletion mutants keep cap priority; every skip is counted.
  4. A documentation-parity lens in the quality brief: a new user-facing surface (CLI flag, slash-command option, settings key) whose siblings are documented is a Suggestion that names the sibling precedent and its file. No documented sibling, no finding — the check asserts the codebase's own convention, never a docs mandate.

Why it's needed

Round-2 re-verification is where a hands-on maintainer produces the evidence a from-scratch review cannot: what became of last round's findings, which failures are genuinely the PR's, and whether the fixes made in response to review are themselves tested. Each of the four is the deterministic half of one of those habits, moved into the pipeline so every re-review gets it.

Reviewer Test Plan

How to verify

  • Run cd packages/cli && npx vitest run src/commands/review src/commands/review.test.ts; expect all 43 files and 1185 tests to pass, including the new test-delta.test.ts and the replacementMutantsOf / selectMutants — replacement operators suites in test-efficacy.test.ts.
  • Read the four new rationale sections in packages/core/src/skills/bundled/review/DESIGN.md and the wiring in SKILL.md (Agent 7 downstream, Step 6 previous-round table, Step 8 cache format).
  • Exercise test-delta mechanically: point --report at a build-test JSON whose test[] carries a failure, --baseline at any built checkout of the base; confirm a failure present on both sides lands in shared, one absent from base in netNew, and that a green report answers "nothing to attribute" without running anything.
  • Confirm the conservative selectors: replacementMutantsOf(' if (s === ")") …', …) (string-bearing line) yields no candidate; if (ready) (no comparison) yields none; state.clear(); still selects the deletion mutant, not a replacement.

Evidence (Before & After)

The attribution case is the previous live run of this pipeline (#8215's evidence): npm test --workspace=packages/core failed with three environment-sensitive files (extensionManager, session-writer-lease) that the model had to judge pre-existing by path. test-delta's test suite pins that exact scenario as a measurement — both files fail on base, netNew empty. The mutation operators' three shapes are the three surviving mutations of #7818's round-2 report (?? config.getModel() untested, the guard-skip condition untested, + COMPACT_MAX_OUTPUT_TOKENS unpinned); each now maps to a selector with a matching unit test. Non-UI change otherwise: N/A.

Tested on

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

Environment (optional)

Unit suites only for this branch (the pipeline integration paths it extends were exercised live on this machine under #8215).

Risk & Scope

  • Main risk or tradeoff: every new selector is biased toward silence — test-delta falls back to the existing path judgment wherever it cannot rule (no merge base, unparsed output, timed-out base rerun), and the mutation operators skip any line whose code view disagrees with its raw text. The cost of that bias is missed candidates, never wrong findings.
  • Not validated / out of scope: the round ledger is prose-driven (cache schema + SKILL steps), so its adherence is enforced the way the rest of the skill is — by the model following Step 6/8; no subcommand validates ledger shape yet. A live incremental round-2 run has not been driven end to end.
  • Breaking changes / migration notes: none. One new subcommand, additive cache fields (round, findings — older caches without them are handled), additive mutant fields (operator, mutated — deletion mutants keep the legacy shape).

Linked Issues

Stacked on #8215 (uses its base-tree; retarget to main once #8215 lands). Workflow reference: the round-2 maintainer verification on #7818.

中文说明

本 PR 做了什么

叠在 #8215 之上。为 /review 再增加四个能力,借鉴自一次真实的二轮 maintainer 复验(#7818 的 round-2 评论):

  1. 测试失败的实测归因qwen review test-delta)。Agent 7 一直按路径判断失败归属——改动文件中的失败算 Critical、未触及文件中的算预存在——两个方向都会误判。新子命令在 feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215base-tree 构建出的 merge base 上重跑 PR 侧失败的测试命令,并对比失败文件集netNew 只在 PR 侧失败(无论 diff 是否触及该文件,都是 PR 自己的问题),shared 在 base 上同样失败(实测预存在)。刻意不比较计数——flaky 套件在同一棵树的两次运行中失败的测试名都不同——base 侧重跑超时则不归因任何东西,而不是把 PR 侧失败提升为证据。
  2. 增量评审缓存中的轮次台账。人类复审者的二轮评论开头是"M1 已修复";流水线的第二轮却从一张全新列表开始,因为缓存只存了数量和结论。现在缓存以轮次作用域 id(R1-2)持久化已确认的 findings,高档增量复审对每一条负有裁定义务——fixed / still stands / cannot tell——采用与既有 open-Criticals 复查相同的"追踪机制"标准,并在报告顶部以表格呈现。
  3. test-efficacy 的三个替换型变异算子,每个都来自删除算子无法表达的幸存变异:删 ?? fallback(miss 路径往往就是整个安全属性)、把含比较的 guard 条件强制为 true(幸存意味着没有测试钉住 guard 不该触发的情形——feat(cli): add /model --compaction for configurable chat compression model #7818 的 round-2 修复正是如此裸奔上线)、删 + UPPER_CONST 项(估算中未钉住的预留量)。原始文本与字面量剥离后的代码视图不一致的行不产生任何候选——曾有一次跨视图计算的编辑下标把 guard 拼接成 iftrue 0),而损坏的变异体以 inconclusive 呈现、还悄悄占掉一个 cap 名额。删除变异体保持 cap 优先级;所有跳过都有计数。
  4. quality brief 的文档奇偶性 lens:新增用户可见入口(CLI flag、slash 命令选项、settings 键)而其同类兄弟均有文档时,报一条指名兄弟先例及其文件的 Suggestion。没有已文档化的兄弟就没有 finding——该检查主张的是代码库自身的惯例,绝不是文档强制令。

为什么需要

二轮复验正是人工 maintainer 能产出从零评审给不出的证据的地方:上一轮的 findings 都怎么样了、哪些失败真正属于本 PR、以及为回应评审而做的修复本身是否有测试。这四项分别是其中一种习惯的可确定性化的一半,移入流水线后每次复审都能得到。

Reviewer 测试计划

验证方式

  • 运行 cd packages/cli && npx vitest run src/commands/review src/commands/review.test.ts;预期 43 个文件、1185 项测试全部通过,含新增的 test-delta.test.ts 以及 test-efficacy.test.ts 中的 replacementMutantsOf / selectMutants — replacement operators 套件。
  • 阅读 packages/core/src/skills/bundled/review/DESIGN.md 新增的四节依据,以及 SKILL.md 的接线(Agent 7 downstream、Step 6 前轮表格、Step 8 缓存格式)。
  • 机械验证 test-delta--report 指向 test[] 携带失败的 build-test JSON,--baseline 指向任一已构建的 base checkout;确认两侧都失败的文件落入 shared、base 上不失败的落入 netNew、全绿报告不运行任何东西即回答"nothing to attribute"。
  • 确认选择器的保守性:replacementMutantsOf(' if (s === ")") …', …)(含字符串的行)不产生候选;if (ready)(无比较)不产生;state.clear(); 仍选择删除变异体而非替换。

前后对比证据

归因场景来自本流水线上一次实测(#8215 的证据):npm test --workspace=packages/core 带着三个环境敏感文件(extensionManagersession-writer-lease)失败,模型只能按路径判断预存在。test-delta 的测试套件把这一确切场景钉为测量——两个文件在 base 上同样失败、netNew 为空。变异算子的三个形态即 #7818 round-2 报告的三个幸存变异(?? config.getModel() 无测试、guard 跳过条件无测试、+ COMPACT_MAX_OUTPUT_TOKENS 未钉住);每个现在都有对应选择器和配套单测。其余非 UI 变更:N/A。

测试平台

OS 状态
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux

环境(可选)

本分支仅单元测试(其扩展的流水线集成路径已在 #8215 下于本机实测)。

风险与范围

  • 主要风险或取舍:所有新选择器都偏向沉默——test-delta 在无法裁定处(无 merge base、输出不可解析、base 重跑超时)回退到既有路径判断,变异算子跳过代码视图与原文不一致的行。这种偏向的代价是漏掉候选,绝不是错误的 finding。
  • 未验证 / 范围外:轮次台账由 prose 驱动(缓存 schema + SKILL 步骤),其遵循与 skill 其余部分同一方式约束——模型执行 Step 6/8;尚无子命令校验台账形状。端到端的真实增量二轮运行尚未驱动。
  • 破坏性变更 / 迁移说明:无。一个新子命令、增量式缓存字段(roundfindings——无这些字段的旧缓存已兼容处理)、增量式变异体字段(operatormutated——删除变异体保持旧形状)。

关联 Issue

叠在 #8215 之上(使用其 base-tree#8215 合并后 retarget 到 main)。工作流参考:#7818 上的二轮 maintainer 复验。

wenshao added 2 commits July 31, 2026 17:02
… A/B, per-hunk probes

- qwen review test-plan: rule on the PR Test Plan's checkable claims (paths,
  npm scripts, test counts) against the reviewed tree; contradictions and
  differing counts are disclosed via compose-review, never capping.
- qwen review base-tree: build the merge base in a sibling worktree so the
  verifier can A/B a comparative claim instead of reading it; swept by cleanup.
- test-efficacy: third probe kind — reverse-apply one hunk at a time and
  re-run the affected tests, attributing a still-green suite to the specific
  change nothing gates; shares the mutants' budget window, runs last.
… summaries

Both measured on a live /review of #8176 with the built CLI:

- test-plan's observedTestCounts strips SGR sequences before matching; a
  color-enabled pipe interleaves them BETWEEN tokens, and the count claim
  fell to 'unchecked' with the summary right there in the report.
- build-test's trimOutput rescues runner summary lines from the omitted
  middle (like module-resolution errors): a failing suite's tail is all
  failure details and npm epilogue, which pushed the one-line summary out
  of the kept text entirely.
@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 31, 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 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR! Re-running the gate now that #8215 has landed and this stack has been retargeted to main (reviewed commit e7b3f67).

  • Template: complete ✓ — bilingual body, a concrete reviewer test plan, and an honest Risk & Scope section that names what is not validated.
  • Problem: observed, not theoretical. Each capability is the deterministic half of a habit a hands-on maintainer already does in round-2 re-verification, and each is anchored to a real run: the attribution case comes from feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215's live run (three env-sensitive core failures that had to be judged pre-existing by path), and the three mutation operators each map to a documented survivor from feat(cli): add /model --compaction for configurable chat compression model #7818's round-2 report. This clears the "does the problem exist" bar.
  • Direction: aligned. Measured attribution, a findings ledger, the survivors deletion can't express, and a docs-parity (not docs-mandate) lens are all squarely the review skill's mission. Internal review-pipeline tooling — no user-facing CHANGELOG surface to cite.
  • Size: production logic ≈ 761 lines (test-delta.ts 453, test-efficacy.ts 228, test-plan.ts 50, build-test.ts 22, review.ts 4, agent-briefs.ts 4), tests ≈ 716, skill docs ≈ 58. The only packages/core/src/** paths are the two skill markdown files (DESIGN.md, SKILL.md) — i.e. the core footprint is documentation, with 0 production logic lines in core; every production line lives in packages/cli/src/commands/review/**, which is not a protected core path. feat type, maintainer author. I'm therefore treating this as a docs-only core touch rather than escalating under Stage 0's size provision — but I'm flagging the 761 production lines for awareness, since that crosses the 500-line mark, and noting the judgment call so a maintainer can disagree.
  • Approach: four capabilities in one PR is a lot, but they cohere (all round-2 habits, all sharing the base-tree dependency from feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215) and each is independently conservative. One edit sits just outside the advertised four — the test-plan.ts file-count false-positive fix (maskFileCounts) — but it's a genuine, well-tested bug in the same subsystem that surfaced during this branch's own review rounds, not drive-by churn; worth a sentence in the body next time. No simpler path jumps out; the file-set-diff design is the right altitude.
  • Risk: no elevated risk signals — none of the changed files match the high-revert-path patterns.

Moving on to code review. 🔍

中文说明

感谢贡献!#8215 已合并、本栈已 retarget 到 main,借此重新跑门禁(被审 commit e7b3f67)。

  • 模板: 完整 ✓ —— 双语正文、具体的 reviewer 测试计划,以及一份诚实的"风险与范围"(点明了验证的部分)。
  • 问题: 已观测,而非理论。每项能力都是人工 maintainer 在二轮复验中已有习惯的可确定性化一半,且各自锚定到一次真实运行:归因场景来自 feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215 的实测(三个环境敏感的 core 失败只能按路径判断为预存在),三个变异算子各自对应 feat(cli): add /model --compaction for configurable chat compression model #7818 二轮报告中记录在案的一个幸存变异。过了"问题是否真实存在"这一关。
  • 方向: 对齐。实测归因、findings 台账、删除算子无法表达的幸存变异、文档奇偶性(而非文档强制令)lens——完全属于 review skill 的职责范围。内部评审流水线工具——没有可引用的用户可见 CHANGELOG 面。
  • 规模: 生产逻辑约 761 行test-delta.ts 453、test-efficacy.ts 228、test-plan.ts 50、build-test.ts 22、review.ts 4、agent-briefs.ts 4),测试约 716,skill 文档约 58。唯一触及 packages/core/src/** 的是两个 skill markdown 文件(DESIGN.mdSKILL.md)——即 core 足迹仅为文档,core 中生产逻辑为 0 行;所有生产代码都在 packages/cli/src/commands/review/**,并非受保护的 core 路径。feat 类型、作者为维护者。因此我将其视为"仅文档触及 core",不按 Stage 0 的规模条款升级——但 761 生产行已过 500 行门槛,我在此提示维护者知悉,并说明这一判断以便维护者复核。
  • 方案: 一个 PR 四个能力信息量不小,但它们内聚(都是二轮复验习惯,都依赖 feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215base-tree),且每一项都独立地偏向保守。有一处改动略微超出 advertised 的四项——test-plan.ts 的文件计数误报修复(maskFileCounts)——但它是同一子系统中一个真实、有充分测试的 bug,在本分支自身的评审轮次中浮现,并非顺手夹带;下次值得在正文里用一句话说明。没看到更简路径;按文件集做差的设计是合适的深度。
  • 风险: 无升级风险信号——改动文件均未命中高回滚路径模式。

进入代码审查。🔍

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Code review

Read independently first: for "make test-failure attribution a measurement," I'd add a subcommand that takes the build-test report plus a built base tree, reruns only the failed commands on base, parses failing files from each side, and diffs the sets (not counts — a flaky suite fails different names across two runs of the same tree). That is exactly what test-delta ships, and I didn't find a simpler path it missed.

No Critical blockers. Notes, all non-blocking:

  • test-delta.ts — sound, and noticeably hardened since the last pass. Two things I flagged before are now fixed: unparsed is set when the PR side parses nothing (prFailingFiles.length === 0) rather than requiring both sides empty, so the asymmetric case is disclosed; and the verbatim-path concern is gone — failingFilesOf keys each file by its vitest project token and normalizes it root-relative via relativeToRoot, so the two sides (running in different roots) compare correctly instead of manufacturing a netNew out of every absolute path. The RERUNNABLE_COMMAND_RE whitelist is the right instinct for a command that reads a string off disk and re-executes it with shell: true — a workspace token is a directory name a PR can choose, and the anchored grammar admits only what build-test emits. baseUnusable (timed out, or failed without naming a file) fails closed in every direction, and the base side is parsed off the untrimmed text so trimming can only understate shared, never inflate netNew.
  • test-efficacy.ts — the three replacement operators are precision-gated. The raw.trim() !== codeLine equivalence guard skips any line carrying a string/comment rather than risk the cross-view index splice (iftrue 0)) that a mangled mutant would otherwise read as inconclusive while eating a cap slot; the guard-true selector excludes arrow functions and generic calls, each with a measured test; and the discriminated union makes a half-populated candidate (action from mutated, wording from operator) unrepresentable by type. Deletion mutants keep cap priority and the REPLACEMENT_SUB_CAP (3, against a measured ~24x pool) keeps replacements from draining the window hunk probes draw from last. The replacement apply branch I noted as uncovered before now has an end-to-end integration test (runs a REPLACEMENT mutant end-to-end).
  • test-plan.ts maskFileCounts — legitimate fix, slightly outside the advertised four. Length-preserving blanking of Test Files/Test Suites lines so a file count stops being extracted as a test count; the per-line mask is label-distance independent and the tests pin the mixed (1 failed | 44 passed), jest, and next-line cases. Cohesive with the subsystem, just worth naming in the body.
  • Conventions: ESM with .js suffixes, kebab-case files, collocated tests, no bare any (one as unknown as in the yargs handler, consistent with the sibling subcommands). Comment density is high but matches this module's established rationale style and DESIGN.md.
Files changed (13 of 13)
File What changed
packages/cli/src/commands/review/test-delta.ts New subcommand: rerun the PR side's failed test commands on the built base tree and partition failing files into netNew (PR-only) vs shared (both sides), failing closed whenever the base cannot be measured
packages/cli/src/commands/review/test-delta.test.ts Unit tests for the failing-file parser and the attribution, including timed-out-base, unparsed, unrecognised-command and truncated-output fail-closed paths
packages/cli/src/commands/review/test-efficacy.ts Three replacement mutation operators (coalesce, guard-true, term-drop) as a discriminated union, a replacement sub-cap with deletion priority, and operator-specific verdict wording and findings
packages/cli/src/commands/review/test-efficacy.test.ts Tests for the new operator selector, the deletion-before-replacement cap ordering, and the cross-view / arrow / generic exclusions
packages/cli/src/commands/review/test-efficacy.integration.test.ts Adapt the number-6832 fixture so the zero-candidate premise holds under the new guard operator, and add an end-to-end replacement-mutant run
packages/cli/src/commands/review/test-plan.ts Mask file-count lines so a Test Files or Test Suites count is no longer extracted as a test count claim
packages/cli/src/commands/review/test-plan.test.ts Tests for the mixed, jest, and next-line file-count cases
packages/cli/src/commands/review/build-test.ts Extract and export the spawnTimedOut predicate so test-delta asks the same timeout question instead of a weaker copy
packages/cli/src/commands/review/lib/agent-briefs.ts Agent 4 gains a documentation-parity lens; Agent 7 measures attribution via base-tree plus test-delta instead of judging by path
packages/cli/src/commands/review.ts Register the test-delta subcommand
packages/cli/src/commands/review.test.ts Add test-delta to the subcommand-list assertion
packages/core/src/skills/bundled/review/DESIGN.md Four rationale sections: measured attribution, findings ledger, the three operators, doc parity
packages/core/src/skills/bundled/review/SKILL.md Wire test-delta into Step 2 and Agent 7, add the previous-round findings table (Step 6), and persist a findings ledger in the cache (Step 8)

Test evidence

This is an unattended CI run, so nothing here was built or executed by the reviewer — but unlike the previous pass, the PR's own CI now runs on the reviewed commit, because #8215 landed and the stack was retargeted to main. Qwen Code CI completed success on e7b3f67, with the Linux unit suite green and zero pending pull_request workflow runs.

Check Conclusion
Qwen Code CI (workflow run) success
Test (ubuntu-latest, Node 22.x) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Post Coverage Comment (ubuntu-latest, 22.x) success
Classify PR / label success
Test (macos-latest, Node 22.x) skipped (matrix-gated)
Test (windows-latest, Node 22.x) skipped (matrix-gated)
Integration Tests (CLI, No Sandbox) skipped

The green Linux suite includes the new test-delta.test.ts and the replacementMutantsOf / selectMutants — replacement operators suites, so the author's "43 files / 1185 tests" figure is now corroborated by CI rather than resting on a local run. Two caveats, neither blocking: macOS/Windows/integration are skipped on this PR trigger (matrix-gated to ubuntu), so cross-platform isn't exercised by CI — the Windows FAIL C:\... parse shape is unit-tested, but the suite itself only ran on Linux; and the unit tests pin runTestDelta through an injected exec seam, not a full live base-tree → test-delta run against a real built tree.

Sandboxed verification would settle the remaining gap: @qwen-code /verify — that test-delta partitions failing files into netNew / shared at runtime against a genuinely built base tree (and falls back correctly on a timed-out or un-built base), which the unit suites pin only in isolation. The author has write access, so /tmux is also available, though this is non-TUI tooling so /verify is the better fit.

中文说明

代码审查

先独立设想:要把"测试失败归因"变成测量,我会加一个子命令——接收 build-test 报告加一棵已构建的 base 树,只在 base 上重跑失败的命令,从两侧解析失败文件,再对集合做差(而非计数——flaky 套件在同一棵树的两次运行中会失败于不同的测试名)。这正是 test-delta 所交付的,没找到它漏掉的更简路径。

无 Critical 阻断项。以下均为非阻断观察:

  • test-delta.ts —— 稳健,且较上一轮明显加固。 我此前提出的两点现已修复:unparsedPR 侧解析不出任何文件时(prFailingFiles.length === 0)即置位,而非要求两侧皆空,因此非对称情形会被披露;逐字路径比较的隐患也已消除——failingFilesOf 以 vitest 项目 token 为键、并经 relativeToRoot 归一为相对根路径,于是运行于不同根下的两侧能正确比较,而不会把每个绝对路径都捏造成 netNewRERUNNABLE_COMMAND_RE 白名单对于一个"从磁盘读回字符串并以 shell: true 重新执行"的命令是正确的直觉——workspace token 是 PR 可选的目录名,而锚定的语法只放行 build-test 实际产出的形状。baseUnusable(超时,失败但未给出任何文件)在所有方向上都 fail-closed;base 侧从未裁剪的原文解析,因此裁剪只会低估 shared,绝不会夸大 netNew
  • test-efficacy.ts —— 三个替换算子精度门控。 raw.trim() !== codeLine 等价守卫会跳过任何含字符串/注释的行,宁可漏掉也不冒跨视图下标拼接(iftrue 0))之险——损坏的变异体 otherwise 会以 inconclusive 呈现并占用 cap 名额;guard-true 选择器排除箭头函数与泛型调用,各有实测测试配套;可辨识联合(discriminated union)让"半填充候选"(动作取自 mutated、措辞取自 operator)在类型层面即不可表示。删除变异体保持 cap 优先级,REPLACEMENT_SUB_CAP(3,对应实测约 24 倍的候选池)防止替换变异体耗尽 hunk 探针最后才动用的时间窗。我此前提到未覆盖的替换应用分支,现已有端到端集成测试(runs a REPLACEMENT mutant end-to-end)。
  • test-plan.tsmaskFileCounts —— 合理修复,略微超出 advertised 的四项。 以保长度方式空白化 Test Files/Test Suites 行,使文件计数不再被当作测试计数提取;按行掩码与标签距离无关,测试钉住了混合(1 failed | 44 passed)、jest、以及下一行三种情形。与子系统内聚,只是值得在正文里点名。
  • 约定: ESM 带 .js 后缀、kebab-case 文件名、测试同目录 colocated、无裸 any(yargs handler 中一处 as unknown as 强转,与兄弟子命令一致)。注释密度高,但符合本模块既有的"记录依据"风格与 DESIGN.md

测试证据

这是无人值守的 CI 运行,因此评审者此处未构建、未执行任何东西——但与上一轮不同,本 PR 自己的 CI 现在会在被审 commit 上运行,因为 #8215 已合并、本栈已 retarget 到 mainQwen Code CIe7b3f67success 完成,Linux 单测全绿,且无 pending 的 pull_request 工作流运行。

绿色的 Linux 套件包含新增的 test-delta.test.ts 以及 replacementMutantsOf / selectMutants — replacement operators 套件,因此作者"43 个文件 / 1185 项测试"的数字现在由 CI 佐证,而不再依赖本地运行。两点保留,均非阻断:本次 PR 触发下 macOS/Windows/integration 被跳过(矩阵限定于 ubuntu),所以 CI 未覆盖跨平台——Windows 的 FAIL C:\... 解析形状有单测,但套件本身只在 Linux 上跑过;此外单测通过注入的 exec seam 钉住 runTestDelta,而非针对一棵真实构建的树做完整的 base-tree → test-delta 实测。

沙箱验证可以定案剩余缺口:@qwen-code /verify —— 验证 test-delta 在运行时针对一棵真实构建的 base 树确实把失败文件划分为 netNew / shared(并在 base 超时或未构建时正确回退),而单测仅在隔离下钉住了这些。作者有写权限,因此 /tmux 也可用,但本 PR 非 TUI 工具,/verify 更合适。

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean review with no blockers and green CI on the reviewed commit; the only reservations are non-blocking (cross-platform CI is matrix-skipped, the round ledger is prose-driven, and the integrated base-tree → test-delta path is unit- rather than live-verified).

Stepping back: this is genuinely good work, and the one reservation I had last time is gone. My independent proposal for measured attribution was exactly the file-set-diff subcommand the PR ships, and I didn't find a simpler path it missed. The design is consistently conservative — every selector is biased toward silence (no merge base, unparsed output, timed-out or un-built base, unrecognised command, exhausted budget → fall back and say so, with its own reason), so the cost of the new machinery is missed candidates, never wrong findings. The two notes from the prior pass are both addressed in the current head: the asymmetric unparsed disclosure is fixed, and the verbatim-path comparison is now project-keyed and root-relative. The replacement operators are the right shape — a discriminated union that makes a half-populated candidate unrepresentable, deletion keeping cap priority, and a sub-cap calibrated against a measured pool so hunk probes aren't starved. The code reads as something I'd be happy to maintain in six months.

What changed since the defer: #8215 landed, the stack was retargeted to main, and Qwen Code CI now runs on the reviewed commit — the Linux suite is green with zero pending runs, so "the tests pass" is no longer the author's word but an observable fact on e7b3f67. The gate's reason for deferring last time (declining to attest to a result that did not yet exist) no longer applies.

Non-blocking, for the maintainer's awareness: (1) macOS/Windows/integration are matrix-skipped on this trigger, so the suite only ran on Linux — the Windows path parse is unit-tested, but a maintainer may want the full matrix before merge; (2) the findings ledger is prose-driven (cache schema + SKILL steps) with no subcommand validating ledger shape yet, as the author discloses; (3) a @qwen-code /verify run would settle the integrated attribution path the unit tests reach only in isolation. None of these block on the merits.

Approving, pinned to the reviewed commit. ✅

中文说明

置信度:4/5 —— 各阶段评审干净、无阻断项,且被审 commit 上 CI 全绿;唯一的保留是非阻断性的(跨平台 CI 被矩阵跳过、轮次台账由 prose 驱动、集成的 base-tree → test-delta 路径是单测而非实测验证)。

退一步看:这确实是高质量的工作,而我上一次的保留已经消失。我对"实测归因"的独立设想,恰好就是 PR 交付的按文件集做差的子命令,也没找到它漏掉的更简路径。设计一以贯之地保守——每个选择器都偏向沉默(无 merge base、输出不可解析、base 超时或未构建、命令不可识别、预算耗尽 → 回退并说明,且各有其因),所以新机制的代价是漏掉候选,绝不是错误的 finding。上一轮的两点观察在当前 head 中均已处理:非对称 unparsed 披露已修复,逐字路径比较现已按项目 token 加键并归一为相对根路径。替换算子形态正确——一个让"半填充候选"在类型层面即不可表示的可辨识联合、删除保持 cap 优先级、以及一个针对实测候选池校准的子 cap(使 hunk 探针不被饿死)。代码读起来是那种六个月后我乐意维护的样子。

自上次 defer 以来的变化:#8215 已合并,本栈已 retarget 到 mainQwen Code CI 现在会在被审 commit 上运行——Linux 套件全绿、无 pending 运行,因此"测试通过"不再是作者的一面之词,而是 e7b3f67 上可观测的事实。门禁上次 defer 的理由(拒绝为一个尚不存在的结果背书)已不再成立。

非阻断,供维护者知悉:(1) 本次触发下 macOS/Windows/integration 被矩阵跳过,套件只在 Linux 上跑过——Windows 路径解析有单测,但维护者或许希望合并前跑完整矩阵;(2) findings 台账由 prose 驱动(缓存 schema + SKILL 步骤),尚无子命令校验台账形状(作者已披露);(3) 跑一次 @qwen-code /verify 可以定案单测仅在隔离下够到的集成归因路径。这些都不构成基于实质的阻断。

批准,钉在被审 commit 上。✅

Qwen Code · qwen3.8-max-preview

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

All measured in the review (#8215 review comment):

- test-plan: linear-time bold-heading scan (the old pattern backtracked
  catastrophically on an unclosed ** line an untrusted PR body controls);
  a flag preceding the npm script yields no claim instead of a false
  'no package defines this script'.
- test-efficacy: a hunk probe's restore recreates the parent directory a
  reverse-applied 'new file' hunk removed (the ENOENT from finally lost
  the verdict and marked every remaining hunk inconclusive); hunks get
  their own skippedForBaseline instead of mislabeling a red baseline as
  a budget skip; splitDiffIntoHunks re-captures the file header at every
  diff --git boundary; a hunk-survived finding notes when it restates an
  inert file-level revert at hunk granularity.
- base-tree: idempotent fast path keyed on a build marker + HEAD check —
  concurrent verifier shards reuse one built tree instead of sweeping it
  out from under each other mid-A/B (a fabricated base-side difference
  with a deterministic source tag was the worst case); cost wording is
  now 'an install and a build' everywhere it was 'one extra build'.
@wenshao
wenshao force-pushed the feat/review-round2-borrowings branch from aa59e92 to 38664ab Compare July 31, 2026 12:05
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@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.

Reviewed — no blockers. Suggestions are inline.

中文说明

已审查——无阻断问题。 建议见行内评论。

— qwen3.8-max-preview via Qwen Code /review

Comment thread packages/cli/src/commands/review/test-efficacy.ts
Comment thread packages/cli/src/commands/review/test-delta.ts
Comment thread packages/cli/src/commands/review/test-delta.ts Outdated
Comment thread packages/cli/src/commands/review/test-delta.ts Outdated
Comment thread packages/cli/src/commands/review/test-delta.ts Outdated
Comment thread packages/cli/src/commands/review/test-efficacy.ts Outdated
Comment thread packages/cli/src/commands/review/test-delta.ts Outdated
Comment thread packages/cli/src/commands/review/test-delta.test.ts
Comment thread packages/cli/src/commands/review/test-efficacy.test.ts
qwen-code-dev-bot and others added 3 commits July 31, 2026 16:14
…line (#8215)

A per-hunk probe reported `survived` whenever the green baseline probes still
passed with the hunk reverted. When the hunk's own collocated test dropped out
of the baseline (a probe-tree import error collects nothing), the remaining
green probes prove only that THEY do not cover the hunk, so the verdict is now
`inconclusive` — the same dropped-test asymmetry the mutants already hold.

Also scope the hunk-survived cross-reference note to the hunk's own collocated
test, and let test-plan match a workspace-scoped run of the plan's bare command
instead of falling through to the manifest on an exact-string miss.
…his branch

The two blocking findings, reproduced on this PR's own Test Plan:

- test-plan files no false contradicted notes: npm rulings move from a
  four-verb denylist to an allowlist (the run form + npm's script aliases —
  the ~fifty other builtins each used to become 'no package defines this
  script'); a slash token is claimed as a repo path only with evidence (an
  extension or ./ prefix), never when it is a flag's value (--repo
  owner/repo) or under the review's own temp root; HEADING_LINE_RE drops
  the same quadratic shape its bold sibling was rewritten to remove.
- base-tree gets a real mutual-exclusion lock around sweep+add+build
  (mkdirSync test-and-set; the loser returns busy instead of deleting the
  tree the winner is mid-install in), and a failed build writes a settled
  marker so later shards stop re-paying the install to relearn
  'unavailable'.

Also: Agent 7's brief now names hunk-survived and the hunks.* counters
(it is the report's only consumer, and the finding class was invisible);
hunk findings anchor at the first ADDED line instead of up to three
context lines above the change.
… round ledger, richer mutants, doc parity

Shaped by a live round-2 maintainer re-verification (#7818):

- qwen review test-delta: rerun the PR side's failed test commands on the
  built merge base and diff the failing FILE SETS — netNew is the PR's own
  failure by measurement (whatever files the diff touches), shared is
  pre-existing by measurement. Counts are never compared: a flaky suite
  fails different test names between runs of the same tree. An unfinished
  base rerun attributes nothing.
- Round ledger: the incremental review cache persists confirmed findings
  under round-scoped ids (R1-2); a high-effort re-review rules on each
  (fixed / still stands / cannot tell) and opens its report with the table,
  the way a human round-2 comment opens with 'M1 is fixed'.
- Three replacement mutation operators in test-efficacy: drop '?? fallback',
  force a comparison-bearing guard condition to 'true', drop a '+ CONST'
  term. Each survivor maps to one crisp untested-protection sentence.
  A line whose raw text and literal-blanked code view disagree yields no
  candidate — an edit index computed across the two views once spliced
  'iftrue 0)' into a guard, and a mangled mutant reads as inconclusive
  while quietly spending a cap slot.
- Quality brief: documentation-parity lens — a new user-facing surface whose
  siblings are documented is a Suggestion naming the sibling precedent; no
  documented sibling, no finding.

@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.

Reviewed — no blockers. Suggestions are inline.

中文说明

已审查——无阻断问题。 建议见行内评论。

— qwen3.8-max-preview via Qwen Code /review

Comment thread packages/cli/src/commands/review/test-efficacy.ts
Qwen Code added 2 commits July 31, 2026 19:18
…est-plan false positives, hunk-probe ranges (#8215)

- base-tree: only stamp a base tree available when runBuildTest actually
  compiled something (ok AND npm toolchain AND a non-empty build). An
  `unsupported` handoff or an empty npm scope returns ok:true having built
  nothing; marking that tree available let an A/B read the absence of a build
  as a behavioural difference.
- cleanup: sweep the stale base-tree build lock a killed builder leaves behind.
- test-plan: read the root manifest's scripts directly so a root-only script
  survives when the root defines no build/test; bail on the inline --root=./dir
  rebasing form; stop treating a positional after an inline --flag=value as the
  flag's value; prefer a failed scoped run when ruling a bare command; anchor
  the npm script alias to a full token so `yarn test:unit` is not truncated.
- test-efficacy: exclude `\ No newline at end of file` from the startLine offset
  count; compute the mutant-overlap range from the header's new-side span so it
  no longer overshoots into a closely following hunk.
@wenshao

wenshao commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Review — feat(review): measured failure attribution, round ledger, richer mutants, doc parity

Verified on a clean worktree of pr8218 (e896669) with a full npm ci. npm run lint clean, npm run typecheck clean, npx vitest run src/commands/review src/commands/review.test.ts → 43 files / 1201 tests pass. Everything below was reproduced by running code, not read off the diff.

Overview

Four additions to the /review pipeline: a test-delta subcommand that reruns the PR side's failed test commands on #8215's built base tree and diffs the failing file sets; a round-scoped findings ledger in the incremental cache; three replacement mutation operators (coalesce / guard-true / term-drop) in test-efficacy; and a documentation-parity lens in the quality brief. The framing is right — path-based attribution genuinely misclassifies in both directions, and the deletion operator genuinely cannot express the three survivor shapes named. The design writing in DESIGN.md is, as usual on this branch, better than most codebases' design docs.

Two things do not hold up under measurement. One inverts test-delta's core claim; the other silently disables the sibling feature the base branch just shipped.


🔴 Critical — a base rerun that fails for a non-test reason is scored as "base is green", and every PR-side failure is promoted to netNew

runTestDelta derives baseUnusable from base.timedOut alone (test-delta.ts:451). base.exitCode is captured and never consulted. So a base rerun that exits non-zero while naming no failing test file — the unbuilt-tree, missing-binary, install-never-ran shapes — is indistinguishable from a base rerun that passed cleanly: baseFailingFiles is [] either way, unparsed stays false (it requires both sides empty, test-delta.ts:444), and every PR-side failing file lands in netNew under the note "do NOT fail on base — the PR's own by measurement".

Agent 7's new brief turns that sentence into a public Critical ("a file in netNewthat is the Critical, whatever file the diff touches"), and SKILL.md Step 3B exempts [test]-sourced findings from Step 4 verification as deterministic facts. So there is no second gate.

The trigger is not exotic. base-tree populates path on both of its build-failure returns (base-tree.ts:190-198 previously-failed marker, base-tree.ts:263-272 build failed) while setting available: false — the tree is deliberately left standing as evidence. The brief tells Agent 7 to pass "the base-tree report's path field" and never mentions gating on available; its only unavailable caveat is scoped to "on a local or lightweight review". runTestDelta checks existsSync(baseline) and nothing else. Run the same failed npm test --workspace=… in a base tree with no dist/ and you get Cannot find module …, exit 1, zero FAIL lines.

Reproduced (exec seam, verbatim output):

input : PR side FAIL src/extensionManager.test.ts, FAIL src/session-writer-lease.test.ts
        base rerun exit 1, output "Error: Cannot find module '@qwen-code/qwen-code-core/dist/index.js'"
output: { "netNew": ["src/extensionManager.test.ts","src/session-writer-lease.test.ts"],
          "shared": [], "unparsed": false,
          "note": "2 failing file(s) do NOT fail on base — the PR's own by measurement: …" }

Those are the exact two files the PR body cites as the motivating case — the ones that are pre-existing. A base tree that failed to build converts them into two blocking Criticals. exit 127 / "vitest: not found" produces the same report, and so does a genuinely green base run — all three are byte-identical downstream.

This is the one place where the branch's stated bias ("the cost is missed candidates, never wrong findings") does not hold: the fallback here is not silence, it is the strongest claim the command can make.

The fix is cheap and uses data already in hand — same shape as the timedOut guard, one condition wider:

// A base run that failed while naming no failing test file did not measure the
// base suite — it did not get to run it. Same class as a timeout.
const baseUnusable =
  base.timedOut || (base.exitCode !== 0 && baseFailingFiles.length === 0);

and, belt-and-braces, have --baseline accept the base-tree report path rather than a bare directory so available === true can be enforced in code instead of asked of the model. The note should name the reason, as the timeout branch does.


🟠 Important — the new operators inflate the mutant pool ~24×, and hunk probes are what pays

Measured over the last 40 commits on main, running selectMutants with an unlimited cap on each commit's added non-test TS lines:

candidates commits producing ≥1
deletion (pre-PR pool) 9 6 / 40
replacement (new) 215 17 / 40

Per-commit replacement counts include 45, 39, 33, 28, 18 — 11 of the 17 overflow MAX_MUTANTS = 8, several by 20-38. Before this PR the cap was essentially unreachable (max 3 in one commit). guard-true is the driver: any single-line if with a comparison qualifies, which is most guards in most diffs.

The cap keeps selection at 8, so the report stays bounded. The cost lands on the time budget instead. Mutants and hunk probes share one window (startedAt + 540s − 300s ≈ 240 s minus the baseline run), each experiment costs a full runProbeSuite, and mutants drain it first (test-efficacy.ts:1659-1704). At a ~30 s probe suite that is ~4 runs total — so on any diff with ≥4 replacement candidates (12 of 40 commits sampled), hunkCandidates gets zero and reports skippedForBudget. Previously ~34 of 40 commits produced no mutants at all and hunk probes had the whole window.

That means hunk-survived — the finding class the stacked base #8215 adds, MAX_HUNK_PROBES = 6 — stops firing on most PRs, and nothing in the diff says so. The comment that authorises the ordering is now load-bearing on a premise the PR itself contradicts:

test-efficacy.ts:1671-1676 — "a safety-verb mutant is the higher-precision experiment, so it gets the budget first"

Replacement mutants are explicitly not that: the PR queues them behind deletions in the cap precisely because deletions "have the track record". Yet in the time budget they outrank hunk probes anyway. Two ways out, either fine: give replacements their own sub-cap (2-3 slots) so deletions and hunk probes keep theirs, or move replacements after hunk probes in the time loop to match the stated precision ordering. Whichever — the ordering comment needs to be rewritten to say what it now means.


🟠 Important — test-delta has no whole-command budget

--timeout is per command, default 300 s, and runTestDelta loops over every failed command with no aggregate deadline. build-test's own --timeout help says it is "Kept strictly below the 600s (600000ms)" tool ceiling, and test-efficacy reserves an explicit TOTAL_BUDGET_MS = 540_000. Three failed test commands — routine on a monorepo where the widening loop scopes several workspaces — is 900 s worst case, killed by the tool ceiling with no report written at all, discarding the base-tree install+build that was just paid for.

test-efficacy's pattern applies directly: one deadlineAt, priced per entry, remaining entries disclosed rather than attempted.


🟠 Important — run() drops three deliberate properties of build-test's run()

test-delta.ts:386-404 reimplements build-test.ts:213-239 and diverges on all three points build-test's comments call out as intentional:

  1. No trimOutput. CommandResult.output is documented "Trimmed output" (build-test.ts:77). test-delta stores the raw base output — up to the 64 MB maxBuffer — into entries[].base.output, which is then JSON.stringify(…, null, 2)'d into --out and printed whole to stdout. A failing monorepo npm test is easily hundreds of KB; Agent 7 reads that file. trimOutput(…) is already imported-adjacent and keeps exactly the tail the parser needs.
  2. Timeout detection is r.error?.message?.includes('ETIMEDOUT') instead of build-test's error.code === 'ETIMEDOUT' || (r.signal === 'SIGTERM' && r.status === null). I confirmed the message form does work on the default path (msg = "spawnSync /bin/sh ETIMEDOUT"), so this is not live today — but build-test calls error.code "the authoritative signal" and added the SIGTERM fallback on purpose, and one case it catches that the substring does not is a maxBuffer overflow kill (ENOBUFS → SIGTERM, status: null). A missed timeout here is not cosmetic: it is exactly the baseUnusable = false path from the Critical above.
  3. No stdio: ['ignore', 'pipe', 'pipe']build-test.ts:222, "A build that asks a question is a build that hangs until the deadline." test-delta reruns those same commands without the guard.

Simplest resolution: export build-test's run and call it, rather than keeping a second copy that will keep drifting.


🔵 Suggestions

The overlap test is vacuous. test-efficacy.test.ts"emits one candidate per line — a safety-verb line is not also mutated by replacement" — uses cache.delete(key);, which yields no replacement candidate under any implementation (replacementMutantsOf('cache.delete(key);', …) === null, confirmed). I deleted the continue at test-efficacy.ts:681 and the test still passed. Its own comment names the right input: map.delete(k) ?? fallback. Using cache.delete(key) ?? fallback; does exercise it — I checked, it selects the deletion candidate with operator: undefined, which is the claim.

The literal-blanking skip removes the most common ?? shape. raw.trim() !== codeLine → null is sound, and the iftrue 0) story justifies it. But codeLines blanks string contents without preserving length (scanFileLines, buf += ch + ch), so every line carrying a string literal is skipped — including const name = cfg.name ?? 'unknown';, which produced no candidate in my run. The headline ?? config.getModel() example is the literal-free minority. A recovery that keeps the index safety fully: detect on codeLine, then re-run the same regex against raw.trim() and splice at that match's index — no index ever crosses views, and the string-fallback case comes back.

Equivalent mutants will file unactionable Suggestions. guard-true on a guard that is redundant with the body survives by construction: if (list.length > 0) { for (const x of list) use(x); }if (true) { … } is semantically identical, as is if (m.get(k) !== undefined) return m.get(k); (both verified to be selected). The survivor message then says "no test pins when the guard must NOT fire", which the author cannot act on. Worth one hedging clause in the guard-true message, or a filter for conditions whose body is itself iteration/optional-chained.

Ledger id continuity is under-specified. Step 1 says to read the cache's findings, but not its round — and round is what the R<round>-<n> ids are built from. A round-2 run that does not read it emits R1-* ids again and collides with the carried-forward R1-* entries it is supposed to be ruling on, which is the whole continuity mechanism. Step 8 says "write every confirmed finding of this round under a fresh R<round>-<n> id" while Step 6 says a still-standing finding is re-reported "under its original id" — reconcilable, but only if you read both. Making Step 1 read round and Step 8 say carried-forward entries keep their original id closes it. Separately: the ledger has no bound. fixed entries leave, still stands entries never do, and Step 6 requires ruling on every entry — a PR at round 6 with a disputed Suggestion pays for it every round.

failingFilesOf minor gaps (all fail safe to unparsed → path rule, so noting only): [\w@./-] excludes \, so Windows-style paths never match — relevant given the 🪟 ⚠️ in the test matrix; and only *.test.* / *.spec.* filenames are recognised, so a __tests__/foo.ts convention yields nothing. term-drop on x = + CONST_ABC; produces x =; (a broken mutant → inconclusive → a spent cap slot) — contrived, but the same class the raw/code-view guard exists to prevent.


What's good

  • The timedOut → attributes nothing branch, and the test comment that records it shipped wrong first because the test asserted only the note text. That is the right instinct and the right disclosure; the Critical above is the same bug one condition over.
  • One candidate per line, deletions first, every skip counted — the cap discipline is consistent with the rest of the file.
  • replacementMutantsOf's balanced-paren scan handles } else if, nested calls, and && chains correctly (if (i < arr.length && arr[i] !== x) break;if (true) break;); the ?? simple-fallback lookahead correctly refuses x ?? y + z and correctly takes the last link of a ?? b ?? c. I probed ~20 shapes and found no unsound edit outside the x = + CONST curiosity above.
  • The doc-parity lens is well-bounded — "no documented sibling, no finding" is the difference between a convention check and a reviewer's taste, and it says so.

Verdict

Request changes, on the Critical alone — the netNew promotion off an unusable base run is a false-blocker generator wired straight into a verification-exempt path, and the guard is three tokens. The hunk-probe starvation is the one I'd want settled before merge too, since it silently degrades the feature shipping directly beneath this one. Everything else is comfortably follow-up material.

wenshao added a commit that referenced this pull request Jul 31, 2026
- test-delta (Critical, #8218 review): a base rerun that fails with ZERO
  parseable failing files (install/toolchain failure) no longer reads as
  'base green' — it attributes nothing, like a timeout, instead of
  promoting every PR-side failure to net-new.
- extract-step (Critical, #8225 review): every line of a multi-line env
  value is comment-prefixed — an unprefixed block-scalar second line sat
  in the emitted script as an executable line.
- extract-step: expressionsOf no longer misses expressions containing
  their own brace (format('{0}')).
- cleanup (#8215 review): sweeps an orphaned base-tree build lock, which
  otherwise wedges the A/B for that PR permanently.
Qwen Code Bot and others added 3 commits July 31, 2026 23:51

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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/cli/src/commands/review/test-delta.ts:98

  • relativeToRoot() strips the worktree/root prefix using a case-sensitive startsWith. On Windows (case-insensitive paths), a drive-letter or path segment case mismatch between --pr-worktree/--baseline and the runner’s reported path can prevent stripping, leaving absolute paths in the failing-file identity and causing pre-existing failures to be misclassified as netNew. Consider doing the prefix comparison case-insensitively on win32 while preserving the original-cased relative path in the return value.
  const norm = (v: string) => v.replace(/\\/g, '/').replace(/\/+$/, '');
  const f = norm(file);
  const r = root ? norm(root) : '';
  const rel = r && f.startsWith(`${r}/`) ? f.slice(r.length + 1) : f;
  return rel.replace(/^\.\//, '');

`trimOutput` rescues module errors and runner summaries out of the
omitted middle, not the per-file FAIL lines this command reads. A base
suite whose failure section overruns the tail budget therefore lost
failing files into the gap - and a SHORT base set is the dangerous
direction, because netNew is the PR side minus the base side. Every file
the trim hid came back as a Critical attributed to this PR by
"measurement". Parse the raw text, report the bounded one.

The PR side cannot be fixed here: it is read out of build-test's stored
output, trimmed before this command existed. That loss runs the other
way - it understates `shared`, never invents a netNew - so the entry
carries `prTruncated` and the note says the list may be partial. A
silence-biased gap is still a gap the author should hear about.

Also names both selection caps in the mutant-skip diagnostic. The count
accumulates replacement sub-cap drops, and with 2 deletions and 6
replacements the total is exactly MAX_MUTANTS: the main cap never fires,
yet 3 are dropped, and the message sent the reader looking for a pool of
11 candidates that never existed.

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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

This command reads a report off disk and then executes the strings in it
with `shell: true`, in the base worktree. Nothing else in the pipeline
re-executes a value it read back from a file, so nothing else has to
care where that value came from - this does, and the provenance is worse
than it looks: the command is `npm test --workspace="<dir>"`, the
workspace token is a directory, and a directory is a name a pull request
chooses. `packages/x";curl …|sh;"` is a legal path in git and on Linux
and it round-trips through the report into a shell.

Restricting to the emitter's own grammar costs nothing real, since that
grammar is the two shapes build-test produces. A command outside it is
skipped and disclosed, the same treatment everything else this command
cannot do already gets, so a future shape degrades to "judge it by the
diff" rather than to arbitrary execution.
@wenshao
wenshao requested a review from Copilot August 1, 2026 11:03

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.

@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.

⚠️ Downgraded from Approve to Comment: CI failing: Test (ubuntu-latest, Node 22.x). Reviewed.

中文说明

⚠️ 已从批准降级为评论:CI failing: Test (ubuntu-latest, Node 22.x)。 已审查。

— qwen3.8-max-preview via Qwen Code /review

@wenshao

wenshao commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 1, 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: 280 passed · 0 failed · 280 total

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

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

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

Verification report

PR #8218 Deep Verification — feat(review): four round-2 borrowings

Verdict: merge-ready — 280 scripted assertions executed, 0 failed. Verified head e7b3f6701d9707658907158d2e54db23e710df62 (git rev-parse HEAD^2); base tip bd855991398325607caa69cf4539c0433467aed4 (HEAD^1, CI merge-ref checkout). The central claim — test-delta attributes test failures by measurement (file-set difference) rather than by path judgment — is proven load-bearing through a mock-free harness that drives the real spawnSync path the unit tests seam out, and by a mutation matrix that kills every central hunk. The command-injection surface is disproved by ground truth.

中文摘要
  • 结论:merge-ready。280 项脚本化断言全部通过,0 失败。
  • A/B 结论:核心新子命令 test-delta(在 base 树上重跑 PR 侧失败的测试命令、对失败文件集做差集得到 netNew/shared)通过一个不替换被测代码的 harness 验证——该 harness 驱动真实的 spawnSync/npm test 路径(单元测试用 exec seam 把这条路径整个换掉了),28/28 通过。最关键的安全属性「base 没真正跑起来(exit≠0 且无 FAIL 行)→ 不归因任何东西」在真实 spawn 下端到端成立(Cell C)。对中心逻辑做的 4 个变异全部被 PR 自己的测试钉死(4/4 killed,含一个阳性对照)。
  • findings:无阻断项。唯一观察项是语法白名单 RERUNNABLE_COMMAND_RE 接受 workspace token 中的 ..(如 --workspace="packages/../etc/passwd")——这是路径穿越式的过度接受,不是命令注入:字符类 [\w@./-] 不含任何 shell 元字符,ground truth 验证 10 个注入形状全部被拒、无任何 /tmp/td-pwned* 标记文件被创建。属设计内、不可利用,仅备查。
  • 未覆盖范围:round ledger(轮次台账)与 doc-parity(文档奇偶性)均为 prose(SKILL.md 缓存 schema + Agent 4 brief 文本),无可执行代码路径,无法机械化验证(PR 自身亦声明「尚无子命令校验台账形状」);test-deltabase-tree 的真实联动(真实 merge base 构建)未端到端驱动(harness 手工构造 base 树);仓库级整套 43 文件 / 1185 测试未全跑,仅跑受影响文件。

Scope

The PR body claims four capabilities. The effective diff (git diff HEAD^1..HEAD, 13 files, +1506) contains all four, but two are prose:

# Capability Where Mechanically testable?
1 Measured failure attribution (test-delta) test-delta.ts (+453, new) Yes — central claim
2 Three replacement mutation operators test-efficacy.ts (+228) Yes — secondary
3 Documentation-parity lens agent-briefs.ts (Agent 4 brief prose) No — prompt text
4 Round ledger SKILL.md (cache schema + Step 6/8 prose) No — model-followed prose

Central claim: test-delta reruns the PR side's failed test commands on a built base tree and diffs the failing file setsnetNew (PR's own) vs shared (pre-existing) — with conservative silence wherever it cannot rule (unparsed, base timed out, base failed-without-naming-a-file, command outside the emitter grammar). Secondary: the three replacement operators are high-precision (conservative selectors), and the shell:true re-execution of a report-read command is injection-safe.

Central claim — A/B / wire-oracle evidence

test-delta is a new command, so the base arm has no equivalent code path to diff against; the load-bearing question is whether its classification logic is correct and pinned. Two instruments:

(a) Mock-free harness through the real spawnSync path (harness-delta.mjs, capture 01-test-delta-ab-real-spawn.png). The unit tests inject an exec seam, so the production run()spawnSync(shell:true)npm test → raw failingFilesOf parse → trimOutputspawnTimedOut — is never exercised by them. This harness drives the compiled runTestDelta with no seam, building real minimal npm packages as the base tree so npm test actually runs and emits vitest-shaped output:

Cell Scenario (real npm test in base tree) Oracle Result
A same failure both sides, base exit 1 shared=[flaky], netNew=[], base exitCode observed = 1
B PR-only + shared failure netNew=[new], shared=[flaky]
C base FAILED TO RUN (exit 1, Cannot find module, no FAIL lines) netNew=[] (not promoted), note "did not measure the base"
D base green (exit 0, no FAIL) every PR failure → netNew
E FAIL line buried in the trimmed middle (>8 kB prose) raw parse still sees it → shared; output trimmed
F base rerun times out (real SIGTERM/ETIMEDOUT, 8 s sleep / 2 s deadline) base.timedOut=true, netNew=[]
G grammar guard — 10 metacharacter injection shapes + 2 legit + 1 ../ see Security below
H failingFilesOf parser edges (project token, root strip, ANSI, , truncated token) 7/7

28/28 assertions pass. Cell C is the load-bearing safety property — a base that exits non-zero without naming a failing file (unbuilt tree, missing install, workspace the PR added) must NOT promote PR-side failures to netNew. Proven end-to-end through a real npm test exiting 1. Capture: 01-test-delta-ab-real-spawn.png.

(b) Mutation matrix (mutation-matrix.mjs, capture 02-mutation-matrix.png) — revert one central hunk in the source, run the PR's own test, confirm red, restore:

Mutant Hunk reverted Killed by Suite failures
M1 drop baseUnusable's "failed without naming a file" clause "does NOT read a base that failed to RUN" 1
M2 permissive grammar /^npm test/ "never hands a command outside the emitter grammar" 1
M3 positive controlnetNew ignores the base set "attributes a PR-only failure as netNew…" 4
M4 drop replacementMutantsOf's raw.trim() !== codeLine guard "yields NOTHING when the raw line and the code view disagree" 1

4/4 killed, M3 (positive control) confirms the harness detects red, and the named test appears among the failures each time. Source restored cleanly (git status --porcelain empty). The central tests are not vacuous and do not pass for the wrong reason. Capture: 02-mutation-matrix.png.

Security — the shell:true re-execution surface

test-delta reads a command out of a report file and re-executes it with shell: true, guarded only by RERUNNABLE_COMMAND_RE = /^npm test(?: --workspace="[\w@./-]+")?$/. The report's workspace token is a directory name a PR can choose, so this is the sharpest consequence. Cell G tested it with ground truth — each hostile shape carries a touch /tmp/td-pwnedN payload, so an executed injection leaves an artifact:

  • GROUND TRUTH: no /tmp/td-pwned* file was created — no injection ran.
  • All 10 metacharacter shapes refused (;, backtick, $(), single-quote, &&, |, >, embedded space, newline, bare ;).
  • Every command that DID run is structurally npm test [--workspace="<metacharacter-free>"] — the invariant that makes shell:true safe, since [\w@./-] contains no shell metacharacter.
  • Both legit emitter shapes (npm test, npm test --workspace="packages/core") ran; the grammar matches what build-test actually emits (build-test.ts:67).

Command injection is disproved, not merely unobserved. The grammar matches the real emitter exactly, so there is no false-negative under-attribution for legitimate commands either.

Secondary claim — replacement mutation operators

harness-selectors.mjs (capture 03-replacement-selectors.png) drives the compiled selectMutants end-to-end (the real literal-blanking scanner produces codeLine), 21/21:

  • Positive: coalesce (return a ?? defaultValue;return a;), guard-true (if (a !== b) {if (true) {), term-drop (const x = base + MAX_EXTRA;const x = base;), and the brace-less else if (a !== 1) shape all fire and edit the line correctly.
  • Conservative gates (the PR Test Plan's exact claims + measured siblings): a string-bearing line if (s === ")") → no candidate (raw ≠ code view); if (ready) → none (no comparison); if (isRecord<string>(v)) → none (the generic-call <string trap — the trailing-\s requirement excludes it); if (arr.map(x => x).length) → none (the arrow => is not read as a > comparison); a comment-bearing line → none. A real comparison if (a > 5 && …) still fires.
  • Deletion priority: state.clear(); stays a deletion mutant (no operator, no mutated).
  • Sub-cap: REPLACEMENT_SUB_CAP=3 — 6 guard-true candidates yield exactly 3 selected + 3 counted in skippedForCap, within MAX_MUTANTS=8.

The new end-to-end integration test (runs a REPLACEMENT mutant end-to-end and reports the survivor) passes, exercising the real runOneMutant write→probe→classify path for the lines[line-1] = mutated branch.

Targeted gate (affected suites, unmutated)

Suite Tests Result
test-delta.test.ts 24 ✅ pass
test-plan.test.ts 73 ✅ pass
test-efficacy.test.ts 104 ✅ pass
test-efficacy.integration.test.ts 25 ✅ pass
Total 226 0 failures

Scaling probe (untrusted input): test-plan parses the author-controlled PR body. A ReDoS ladder over extractTestPlanSection + extractClaims at 2 k / 5 k / 20 k / 65 536 chars (the GitHub body cap) across five hostile shapes (repeated Test Files labels, dense N passed tokens, the historical unclosed-** shape, many backticked spans, fenced # comments): worst single call 140 ms at the 65 k cap, the unclosed-** shape 0.1 ms (the historical backtracking is fixed). No catastrophic backtracking (harness-redos.mjs).

Findings

None blocking.

Observation (informational, by-design, not exploitable): grammar admits .. in the workspace token

RERUNNABLE_COMMAND_RE accepts npm test --workspace="packages/../etc/passwd" because . and / are intentionally in the character class. This is path-traversal-shaped over-acceptance, not command injection: the accepted string is still structurally npm test --workspace="<no-metacharacter>", so shell:true cannot be made to run a second command — confirmed by ground truth (no marker file created when the payload rode alongside such a token). Blast radius is nil: the command runs npm test against an odd directory inside a disposable base worktree in the sandbox, where npm simply fails to resolve the workspace (→ baseUnusable → no attribution). The PR's stated security claim is specifically about shell injection (packages/x";curl|sh;"), which holds; .. being in the class is documented design ([\w@./-]), not an oversight. No reviewer action required; recorded so the boundary is named precisely.

Not covered

  • Round ledger (capability 4) and doc-parity lens (capability 3) are prose, not executable code — the ledger is a cache schema + Step 6/8 instructions in SKILL.md, and doc-parity is a paragraph in Agent 4's brief (agent-briefs.ts). I verified both texts are present and wired (the test-delta/base-tree instructions in Agent 7's brief, the findings[] ledger schema, the parity paragraph), but there is no code path to assert against; adherence is model-followed, exactly as the PR states ("no subcommand validates ledger shape yet", "a live incremental round-2 run has not been driven end to end").
  • Real base-treetest-delta integration was not driven end-to-end: the harness hand-built the base npm packages rather than invoking qwen review base-tree to build a real merge base. The classification logic and the real spawnSync path are proven; the upstream base-tree build is feat(review): Test Plan claim check, base-tree A/B harness, per-hunk probes #8215's surface (already in the base tip) and was not re-verified here.
  • Repo-wide gate not run: the PR Test Plan cites 43 files / 1185 tests for the whole review directory; I ran the four affected files (226 tests). Unaffected review subcommands were out of scope.
  • Per-commit attribution is out of reach: the checkout is depth 2 (shallow), so only the merge commit, base tip, and PR head are local; the 20 commits in the metadata commits[] array are not individually exercisable. Verified the aggregate HEAD^1..HEAD diff.
  • Windows/macOS behaviour of test-delta (the failingFilesOf Windows-path branch is unit-tested but not run on Windows); the lane is Linux.

Methodology

CI merge-ref checkout (HEAD=merge 6041f088, HEAD^1=base bd855991, HEAD^2=PR head e7b3f670), node:22-bookworm, Node v22.23.2, no zstd. npm ci + npm run build were pre-run; harnesses import the compiled packages/cli/dist/src/commands/review/*.js. Four harnesses (all in this dir, rerunnable): harness-delta.mjs drives runTestDelta with no exec seam — real minimal npm packages emit vitest-shaped FAIL output so the production spawnSync/raw-parse/trimOutput/spawnTimedOut path is under test; harness-selectors.mjs drives selectMutants/replacementMutantsOf; harness-redos.mjs times the untrusted-body parsers; mutation-matrix.mjs reverts central hunks in source, runs the PR's own vitest, and restores from the clean tree. Internal workspace link verified pointing into the head tree (readlink -f node_modules/@qwen-code/qwen-code-corepackages/core), so the head-side harnesses load head code. Raw per-harness logs are in logs/; evidence captures in evidence/.

Evidence images

01-test-delta-ab-real-spawn

02-mutation-matrix

03-replacement-selectors

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

Qwen Code · sandboxed verification

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 1 finishedview run. See this round's report below.

中文说明

AutoFix 第 1 轮已完成 —— 查看运行。本轮报告见下方。

@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. ✅

@doudouOUC doudouOUC 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✅

@wenshao
wenshao enabled auto-merge August 1, 2026 12:25
@ytahdn

ytahdn commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

PR #8218 — feat(review): four round-2 borrowings — measured failure attribution, round ledger, richer mutants, doc parity

Stacked on #8215, 13 files changed, +1506 lines. CI all green.

What it does

  1. qwen review test-delta (new subcommand): Reruns the PR side's failed test commands on the built merge base and diffs failing file setsnetNew = PR's own by measurement, shared = pre-existing by measurement. Key design: compares sets not counts (flaky suites fail different names between runs); base timeout/unmeasured attributes nothing; command grammar whitelist (RERUNNABLE_COMMAND_RE) prevents shell injection from untrusted workspace names; whole-command budget (540s) prevents timeout cascade; PR-side trimming disclosed.

  2. Round ledger in incremental cache: Cache persists confirmed findings under round-scoped ids (R1-2). High-effort re-review owes each entry a ruling (fixed / still stands / cannot tell). SKILL.md Step 6 updated.

  3. Three replacement mutation operators in test-efficacy: coalesce (drop ?? fallback), guard-true (force comparison-bearing guard to true), term-drop (drop + UPPER_CONST). Each with conservative guards (raw/code view agreement, comparison detection excluding generics/arrows). Sub-cap of 3 replacements (24x pool inflation measured). Union types (DeletionMutant | ReplacementMutant) make invalid states unrepresentable.

  4. Documentation parity lens in quality brief: new user-facing surface whose siblings are documented → Suggestion naming the precedent file. No documented sibling → no finding.

  5. spawnTimedOut extracted from build-test.ts for reuse. test-plan maskFileCounts prevents Test Files 45 passed from being parsed as a test count claim.

Tests

  • New test-delta.test.ts (467 lines): parser, attribution, edge cases, security (grammar whitelist), budget, trimming, CLI flag round-trip
  • New replacementMutantsOf / selectMutants replacement suites in test-efficacy.test.ts
  • New integration test for replacement mutant end-to-end
  • New test-plan.test.ts cases for file count masking

Assessment

High-quality PR. Silence-biased design throughout — every selector errs toward not producing findings. Security-conscious (shell injection guard). Type system used effectively. DESIGN.md rationale sections are excellent. No issues found.

— Qwen Code /review (v0.5.0)

@ytahdn ytahdn 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 — well-designed, thoroughly tested, silence-biased throughout. No issues found.

@tanzhenxin tanzhenxin 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!

@wenshao
wenshao added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit 9bfe608 Aug 1, 2026
144 of 146 checks passed
wenshao added a commit that referenced this pull request Aug 1, 2026
#8218 landed as a squash, so its content now arrives on this branch from
two histories at once and every shared file conflicts. Four of them, all
resolved to this branch: the three `extract-step` registrations and its
DESIGN sections are this PR's own contribution and main has no copy, and
the ledger's `fixed` ruling keeps the sibling-entrance sentence that
main's squashed copy predates.

Checked the pair that a wrong resolution here would silently break: the
registered subcommands and the list the error message names are still
identical, extract-step included.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Review feedback addressed — PR #8218

One change this round; the rest of the feedback was verified already resolved in
the current tree (the maintainer's "Fixed in …" replies reference commits that
were rebased/squashed, so every claim was re-checked against the code at HEAD,
not against the commit references).

Change made

  • rc:3692647513 — orphaned selectMutants contract doc (commit 4221316bf).
    The earlier fix left the "Deterministic mutant selection" contract block sitting
    above replacementMutantsOf, and a parenthetical that claimed "selectMutants'
    own contract doc sits directly above selectMutants"
    — which was false, since
    selectMutants had no doc above it at all. Re-attached the contract block
    directly above selectMutants and updated it to name the replacement operators
    and the deletion-first ordering (REPLACEMENT_SUB_CAP), and reworded the
    helper's doc so it no longer makes a false positional claim. Comment-only; no
    behavior change.

Bot inline [Suggestion] findings — all resolved in code (re-verified)

  • rc:3690842940 (e2e test for a replacement mutant) — resolved: the
    integration test runs a REPLACEMENT mutant end-to-end and reports the survivor
    drives a coalesce operator through the real write→probe→classify handler and
    asserts the mutated line, the survived verdict, and the operator wording.
  • rc:3690842946 (missing stdio: ['ignore','pipe','pipe']) — resolved in
    test-delta.ts's run().
  • rc:3690842964 (weak timeout detection) — resolved: uses spawnTimedOut, the
    sibling's ETIMEDOUT || (SIGTERM && status===null) predicate.
  • rc:3690842970 (unparsed required both sides) — resolved: unparsed is
    decided by the PR side alone, which is what netNew/shared derive from.
  • rc:3690842981 (baseUnusable only guarded timeouts) — resolved: widened to
    base.timedOut || (base.exitCode !== 0 && baseFailingFiles.length === 0), so a
    base rerun that crashed without naming a failing file attributes nothing.
  • rc:3690842993 (comparison gate tested the whole line) — resolved: the gate
    tests ifm[2].slice(0, condEnd), the condition span only.
  • rc:3690842998 (base output never trimmed) — resolved: output: trimOutput(raw).
  • rc:3690843006 (test named "baseline dir" never asserted it) — resolved: the
    exec helper passes cwd through and the test asserts cwds === [baseline].
  • rc:3690843016 (vacuous overlap test) — resolved: the input is now
    cache.delete(key) ?? fallback.reset();, which triggers both paths so the
    continue under test is load-bearing.
  • rc:3692647513 — resolved this round (see above).

Maintainer review Criticals / Important — all resolved in code (re-verified)

  • Base rerun that fails for a non-test reason read as "base is green" → resolved
    (the baseUnusable widening above).
  • Timeout detection weaker than the sibling → resolved (spawnTimedOut).
  • Absolute-path comparison fabricating netNew → resolved: failing files are
    normalized root-relative (relativeToRoot) and keyed by the vitest project
    token, so the two roots compare and same-named files across workspaces don't
    collapse.
  • Base output stored untrimmed (megabyte reports) → resolved (trimOutput).
  • Base rerun inheriting stdin → resolved (stdio: ['ignore','pipe','pipe']).
  • No whole-command budget → resolved (TOTAL_BUDGET_MS, with clamped/skipped
    deadlines disclosed).
  • run() diverging from build-test's three deliberate properties → resolved.
  • Replacement operators inflating the mutant pool and starving hunk probes →
    resolved (REPLACEMENT_SUB_CAP = 3, deletions first).
  • Command injection via a PR-chosen workspace dir re-executed under shell: true
    → resolved: a RERUNNABLE_COMMAND_RE allowlist runs only the emitter's grammar
    and discloses anything else.

Maintainer suggestions — resolved in code (re-verified)

Discriminated union for MutantCandidate (deletion vs replacement, so a
half-populated candidate is unrepresentable); => arrow excluded from the
guard-true comparison gate; brace-less else if admitted; the trailing-\s
asymmetry documented as deliberate (it keeps isRecord<string>(v) from matching);
unparsed interface doc corrected to PR-side; the note no longer says "on either
side"; programmatic timeout-omitted NaN closed with DEFAULT_TIMEOUT_S;
Windows path shapes admitted in failingFilesOf; base output parsed for failing
files before trimming.

Maintainer suggestions — declined as follow-up (recorded reasons)

These are silence-biased (they miss a candidate or slightly soften wording; they
never produce a wrong finding), and the maintainer's own verdicts label them
follow-up material. Per AGENTS.md's Simplicity First / ratchet guidance, none is
worth the diff growth this round:

  • Coalesce literal-blanking recovery (recover const name = cfg.name ?? 'unknown';):
    the recovery re-runs the regex against the raw line and splices at that index —
    exactly the index-crossing-two-views hazard the raw.trim() !== codeLine gate
    exists to prevent. Recall-only; the headline example is the literal-free minority.
  • Equivalent-mutant hedge clause (redundant-guard survivors): narration-only;
    the survivor statement ("no test pins when the guard must not fire") stays true.
  • term-drop on string concatenation wording: contrived and silence-biased;
    + UPPER_CONST in arithmetic is the intended shape, and the survivor detail
    already branches on mutant.mutated ("when it changes").
  • Ledger id-continuity prose (SKILL.md): prose-only; Step 6 was already
    reworded (rc:3695241555) and the maintainer flagged the ledger as "the failure
    mode to watch for in the first live run."
  • baseUnusable as a field on DeltaEntry: the unusable state is already
    disclosed in the note, and the re-derivation is sound; adding a schema field is
    growth for a JSON whose consumer reads the note.

Conflict

--conflict false — no merge performed.

Verification

Commands actually run this round (comment-only change in packages/cli):

  • npm run typecheck — passed
  • npx vitest run src/commands/review/test-efficacy.test.ts src/commands/review/test-delta.test.ts (packages/cli, touched) — 128 passed
  • npx vitest run src/commands/review/test-efficacy.integration.test.ts (packages/cli) — 25 passed
  • npx eslint packages/cli/src/commands/review/test-efficacy.ts — passed (no findings)
  • npm run build — passed
中文说明

已处理的审查反馈 — PR #8218

本轮只做了一处改动;其余反馈经复核在当前代码树中已解决(维护者"已在 … 修复"的回覆所引用的提交已被 rebase/squash,因此每一条都对照 HEAD 处的代码重新核验,而非对照提交引用)。

本次改动

  • rc:3692647513 — 被孤立的 selectMutants 契约文档(提交 4221316bf)。
    早先的修复把 "Deterministic mutant selection" 契约块留在了 replacementMutantsOf 上方,并留下一句括注 "selectMutants' own contract doc sits directly above selectMutants"——而这是假的,因为 selectMutants 上方根本没有任何文档。本次把契约块重新贴回 selectMutants 正上方,并更新它以点名替换算子与"删除优先"的排序(REPLACEMENT_SUB_CAP),同时改写辅助函数的文档,使其不再做出错误的位置声明。仅注释改动,无行为变化。

机器人行内 [Suggestion] 发现 — 全部已在代码中解决(已复核)

  • rc:3690842940(替换型变异体的端到端测试)— 已解决:集成测试 runs a REPLACEMENT mutant end-to-end and reports the survivor 驱动一个 coalesce 算子走真实的「写文件→跑探针→判定」链路,并断言被变异行、survived 结论与算子措辞。
  • rc:3690842946(缺少 stdio: ['ignore','pipe','pipe'])— 已在 test-delta.tsrun() 中解决。
  • rc:3690842964(弱超时检测)— 已解决:使用 spawnTimedOut,即兄弟函数的 ETIMEDOUT || (SIGTERM && status===null) 判定。
  • rc:3690842970unparsed 要求两侧)— 已解决:unparsed 仅由 PR 侧决定,而 netNew/shared 正是由 PR 侧推导。
  • rc:3690842981baseUnusable 只防超时)— 已解决:放宽为 base.timedOut || (base.exitCode !== 0 && baseFailingFiles.length === 0),因此一个崩溃但未点出任何失败文件的 base 侧重跑什么都不归因。
  • rc:3690842993(比较判定门测试整行)— 已解决:判定门测试 ifm[2].slice(0, condEnd),仅条件区间。
  • rc:3690842998(base 输出从未裁剪)— 已解决:output: trimOutput(raw)
  • rc:3690843006(测试名为 "baseline dir" 却从未断言)— 已解决:exec 辅助函数透传 cwd,测试断言 cwds === [baseline]
  • rc:3690843016(空转的重叠测试)— 已解决:输入改为 cache.delete(key) ?? fallback.reset();,同时触发两条路径,使被测的 continue 承重。
  • rc:3692647513 — 本轮解决(见上)。

维护者审查的 Critical / Important — 全部已在代码中解决(已复核)

  • base 侧重跑因非测试原因失败却被读作 "base 是绿的" → 已解决(即上述 baseUnusable 放宽)。
  • 超时检测弱于兄弟函数 → 已解决(spawnTimedOut)。
  • 按绝对路径比较凭空造出 netNew → 已解决:失败文件被归一化为相对根路径(relativeToRoot)并按 vitest 项目标记键控,因此两侧根目录可比较,跨 workspace 同名文件不会被合并。
  • base 输出未裁剪存储(数 MB 的报告)→ 已解决(trimOutput)。
  • base 侧重跑继承 stdin → 已解决(stdio: ['ignore','pipe','pipe'])。
  • 无整命令预算 → 已解决(TOTAL_BUDGET_MS,并披露被钳制/被跳过的截止时间)。
  • run() 偏离 build-test 的三个刻意属性 → 已解决。
  • 替换算子放大变异池、饿死 hunk 探针 → 已解决(REPLACEMENT_SUB_CAP = 3,删除优先)。
  • 经由 PR 选定的 workspace 目录在 shell: true 下被重新执行的命令注入 → 已解决:RERUNNABLE_COMMAND_RE 允许清单只运行发射器自身的语法,其余形态披露而不执行。

维护者建议 — 已在代码中解决(已复核)

MutantCandidate 的判别联合(删除 vs 替换,使半填充候选不可表示);guard-true 比较判定门排除 => 箭头;接纳无大括号的 else if;尾部 \s 的不对称被记录为刻意(用以避免匹配 isRecord<string>(v));unparsed 接口文档更正为 PR 侧;note 不再说 "on either side";程序化调用省略 timeout 导致的 NaNDEFAULT_TIMEOUT_S 收口;failingFilesOf 接纳 Windows 路径形态;base 输出在裁剪之前先解析出失败文件。

维护者建议 — 作为后续事项 declined(记录理由)

这些都是沉默偏向(只会漏掉候选或略微软化措辞,绝不会产出错误发现),且维护者自己的结论已将其定为后续事项。按 AGENTS.md 的 Simplicity First / 棘轮原则,本轮都不值得增加 diff:

  • coalesce 字面量留白恢复(恢复 const name = cfg.name ?? 'unknown';):该恢复会对原始行重跑正则并在那个下标拼接——恰是 raw.trim() !== codeLine 判定门所要防止的"下标跨两种视图"风险。仅影响召回;标题示例属于无字面量的少数情形。
  • 等价变异体的对冲措辞(冗余守卫的存活者):仅措辞;存活者陈述("没有测试钉住守卫何时不应触发")仍然为真。
  • term-drop 作用于字符串拼接的措辞:造作且沉默偏向;算术中的 + UPPER_CONST 才是目标形态,且存活者 detail 已按 mutant.mutated 分支("when it changes")。
  • 轮次账本 id 连续性散文(SKILL.md):仅散文;Step 6 已改写(rc:3695241555),且维护者已把账本标记为"首次实跑要观察的失效模式"。
  • baseUnusable 作为 DeltaEntry 的字段:不可用状态已在 note 中披露,且再推导是可靠的;为一个消费方读 note 的 JSON 增加 schema 字段属于扩张。

冲突

--conflict false — 未执行合并。

验证

本轮实际运行的命令(packages/cli 中的纯注释改动):

  • npm run typecheck — 通过
  • npx vitest run src/commands/review/test-efficacy.test.ts src/commands/review/test-delta.test.ts(packages/cli,触及)— 128 通过
  • npx vitest run src/commands/review/test-efficacy.integration.test.ts(packages/cli)— 25 通过
  • npx eslint packages/cli/src/commands/review/test-efficacy.ts — 通过(无发现)
  • npm run build — 通过

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

wenshao added a commit that referenced this pull request Aug 1, 2026
…ings artifact

The rebase onto #8218 left two id schemes for one finding: the incremental
cache's cross-round ledger names findings R<round>-<n>, while the findings
artifact accepted any unique id. Same defect, two names, and the outcome
ledger and next round's report could no longer be joined. The artifact now
uses the R-ids whenever the run writes the cache ledger.

Conflict resolutions from the rebase itself: review.ts keeps both new
subcommands (test-delta from main, findings from this branch); the
documentation-parity check #8218 added to the old Agent 3 brief lands in 3c,
the consistency slice that owns sibling-parity checks.
pull Bot pushed a commit to mcx/qwen-code that referenced this pull request Aug 1, 2026
…om Claude /review (QwenLM#8315)

* feat(review): borrow recall, a fix loop, and size-derived budget from Claude /review

Three changes, from a comparison of this skill against Claude Code's
`/code-review`. The orchestration half of that comparison went the other way —
nothing there has the worktree isolation, the transcript-backed coverage gate, or
the single computed verdict — so what is borrowed is what it does better:
how much it surfaces, what a finding *is*, and what a small diff costs.

Recall
------
The Exclusion Criteria are a filter on what KIND of thing is a finding. Read as a
confidence bar — which is how an agent under "silence is better than noise" reads
them — they license dropping anything half-believed, and that drop is invisible:
no later stage sees a candidate that was never filed. Every stage after the
finders removes wrong findings; none can add a missing one. Each finder brief now
carries the counterweight explicitly, and the Step 4 verifier deliberately does
not get it.

Code quality was one agent holding six unrelated checks — the shape this skill
already refuses for invariant agents, on measured evidence (PR QwenLM#6457: one agent
with an eight-item checklist found 1 of 5 defects; the same model split three ways
found all 5). Split into 3a reuse/duplication, 3b altitude/abstraction fit, 3c
consistency/clarity. 12 -> 14 agents in 3A.

Low was one undirected pass capped at 8, and its only alternative was a
nine-subagent fan-out. It is now an angle rotation in one context — line-by-line,
removed behaviour, language pitfalls, wrapper routing, reuse/dead code, sibling
consistency, then a gap sweep — dedup-only, no re-judging, cap 10. Still zero
subagents.

--fix and findings as data
--------------------------
`--fix` is `--comment` reflected and gated on the opposite target: `--comment`
writes to a pull request, `--fix` writes to a working tree, so a PR review (whose
tree is the ephemeral worktree Step 9 deletes) ignores it with a warning. An
effective `--fix` floors the effort at medium — editing the user's files on an
unverified finding is the same mistake as posting one.

New `qwen review findings` canonicalizes the findings into a JSON artifact the
terminal report, the saved report and the review JSON all read, instead of three
transcriptions of one list. With `--outcomes` it merges the fixer's ledger and
REFUSES one that does not account for every finding: a fixer that applies six of
nine and reports six has not lied about any of them, it has silently shortened
the list.

Size-derived budget
-------------------
New `plan.budget`, computed from srcDiffLines the way the topology gate is and
recorded in the plan rather than passed as a flag, so every reader sees one
number. Scopes the low tier's angle count and sweep, the Agent 8 ceiling (0 below
80 source lines — "one domain dominates" is a judgement, and one made about forty
lines finds a dominant domain every time), and the verify shard width. It never
scales a dimension away: that is the roster's answer and the roster reads effort.

Not included: per-model prompt routing. Claude's table exists because it was
measured per model family; shipping an invented mapping into this skill is the
kind of change its own review would flag.

Tests: 39 files, 1215 assertions.

* docs(review): align counts and level descriptions left stale by the 3a/3b/3c split

Round-1 self-review findings on this branch:

- SKILL.md medium tier still named 'quality (Agent 3)'; the Step-1 low bullet
  and Step 3C heading hardcoded six angles though plan.budget scales them 3-6;
  and no fallback was stated for a plan written by an older CLI that carries no
  budget field (falls back to the flat pre-budget behaviour — more coverage,
  never less).
- DESIGN.md still labelled the 12-agent roster '(current)', kept the ten-lens /
  crosses-twelve topology arithmetic beside the updated fourteen-agent copy,
  keyed the re-gating cost row to the 12-agent roster, and described low as one
  pass ≤8 and medium as unverified inline angles — both contradicting the
  SKILL.md this PR ships. The LLM-call-budget and Fork-Subagent sections were
  still summed for 12 agents.
- findings.ts: validateFindings accepted outcome but dropped outcomeNote, so
  the canonical artifact did not round-trip — a skipped finding fed back
  through --input kept its outcome and lost its reason.

Tests: 40 review files green, including two new round-trip cases.

* docs(review): one id per finding across the cache ledger and the findings artifact

The rebase onto QwenLM#8218 left two id schemes for one finding: the incremental
cache's cross-round ledger names findings R<round>-<n>, while the findings
artifact accepted any unique id. Same defect, two names, and the outcome
ledger and next round's report could no longer be joined. The artifact now
uses the R-ids whenever the run writes the cache ledger.

Conflict resolutions from the rebase itself: review.ts keeps both new
subcommands (test-delta from main, findings from this branch); the
documentation-parity check QwenLM#8218 added to the old Agent 3 brief lands in 3c,
the consistency slice that owns sibling-parity checks.

* test(cli): add findings to the pinned review subcommand surface

review.test.ts pins the exact subcommand list and sits one directory above
the review/ glob the branch's local runs used, so the new findings
subcommand never met it until CI. Ubuntu was the only matrix leg that ran.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
pchaganti pushed a commit to pchaganti/qx-qwen-code that referenced this pull request Aug 1, 2026
…rol, seven lenses (QwenLM#8261)

* feat(review): borrowed-verification trio — test-plan check, base-tree A/B, per-hunk probes

- qwen review test-plan: rule on the PR Test Plan's checkable claims (paths,
  npm scripts, test counts) against the reviewed tree; contradictions and
  differing counts are disclosed via compose-review, never capping.
- qwen review base-tree: build the merge base in a sibling worktree so the
  verifier can A/B a comparative claim instead of reading it; swept by cleanup.
- test-efficacy: third probe kind — reverse-apply one hunk at a time and
  re-run the affected tests, attributing a still-green suite to the specific
  change nothing gates; shares the mutants' budget window, runs last.

* fix(review): survive real runner output — ANSI-laced and trimmed-away summaries

Both measured on a live /review of QwenLM/qwen-code#8176 with the built CLI:

- test-plan's observedTestCounts strips SGR sequences before matching; a
  color-enabled pipe interleaves them BETWEEN tokens, and the count claim
  fell to 'unchecked' with the summary right there in the report.
- build-test's trimOutput rescues runner summary lines from the omitted
  middle (like module-resolution errors): a failing suite's tail is all
  failure details and npm epilogue, which pushed the one-line summary out
  of the kept text entirely.

* fix(review): address the eight findings from live review of this branch

All measured in the review (QwenLM/qwen-code#8215 review comment):

- test-plan: linear-time bold-heading scan (the old pattern backtracked
  catastrophically on an unclosed ** line an untrusted PR body controls);
  a flag preceding the npm script yields no claim instead of a false
  'no package defines this script'.
- test-efficacy: a hunk probe's restore recreates the parent directory a
  reverse-applied 'new file' hunk removed (the ENOENT from finally lost
  the verdict and marked every remaining hunk inconclusive); hunks get
  their own skippedForBaseline instead of mislabeling a red baseline as
  a budget skip; splitDiffIntoHunks re-captures the file header at every
  diff --git boundary; a hunk-survived finding notes when it restates an
  inert file-level revert at hunk granularity.
- base-tree: idempotent fast path keyed on a build marker + HEAD check —
  concurrent verifier shards reuse one built tree instead of sweeping it
  out from under each other mid-A/B (a fabricated base-side difference
  with a deterministic source tag was the worst case); cost wording is
  now 'an install and a build' everywhere it was 'one extra build'.

* fix(cli): never score a hunk survived when its own test left the baseline (#8215)

A per-hunk probe reported `survived` whenever the green baseline probes still
passed with the hunk reverted. When the hunk's own collocated test dropped out
of the baseline (a probe-tree import error collects nothing), the remaining
green probes prove only that THEY do not cover the hunk, so the verdict is now
`inconclusive` — the same dropped-test asymmetry the mutants already hold.

Also scope the hunk-survived cross-reference note to the hunk's own collocated
test, and let test-plan match a workspace-scoped run of the plan's bare command
instead of falling through to the manifest on an exact-string miss.

* fix(review): silence-bias hardening from four live review rounds of this branch

The two blocking findings, reproduced on this PR's own Test Plan:

- test-plan files no false contradicted notes: npm rulings move from a
  four-verb denylist to an allowlist (the run form + npm's script aliases —
  the ~fifty other builtins each used to become 'no package defines this
  script'); a slash token is claimed as a repo path only with evidence (an
  extension or ./ prefix), never when it is a flag's value (--repo
  owner/repo) or under the review's own temp root; HEADING_LINE_RE drops
  the same quadratic shape its bold sibling was rewritten to remove.
- base-tree gets a real mutual-exclusion lock around sweep+add+build
  (mkdirSync test-and-set; the loser returns busy instead of deleting the
  tree the winner is mid-install in), and a failed build writes a settled
  marker so later shards stop re-paying the install to relearn
  'unavailable'.

Also: Agent 7's brief now names hunk-survived and the hunks.* counters
(it is the report's only consumer, and the finding class was invisible);
hunk findings anchor at the first ADDED line instead of up to three
context lines above the change.

* feat(review): four round-2 borrowings — measured failure attribution, round ledger, richer mutants, doc parity

Shaped by a live round-2 maintainer re-verification (QwenLM/qwen-code#7818):

- qwen review test-delta: rerun the PR side's failed test commands on the
  built merge base and diff the failing FILE SETS — netNew is the PR's own
  failure by measurement (whatever files the diff touches), shared is
  pre-existing by measurement. Counts are never compared: a flaky suite
  fails different test names between runs of the same tree. An unfinished
  base rerun attributes nothing.
- Round ledger: the incremental review cache persists confirmed findings
  under round-scoped ids (R1-2); a high-effort re-review rules on each
  (fixed / still stands / cannot tell) and opens its report with the table,
  the way a human round-2 comment opens with 'M1 is fixed'.
- Three replacement mutation operators in test-efficacy: drop '?? fallback',
  force a comparison-bearing guard condition to 'true', drop a '+ CONST'
  term. Each survivor maps to one crisp untested-protection sentence.
  A line whose raw text and literal-blanked code view disagree yields no
  candidate — an edit index computed across the two views once spliced
  'iftrue 0)' into a guard, and a mangled mutant reads as inconclusive
  while quietly spending a cap slot.
- Quality brief: documentation-parity lens — a new user-facing surface whose
  siblings are documented is a Suggestion naming the sibling precedent; no
  documented sibling, no finding.

* feat(review): render adjudication, workflow step extraction, three verification lenses

Shaped by a live sanitizer-PR verification (QwenLM/qwen-code#8147):

- Render-adjudication capability (opt-in): with QWEN_REVIEW_SCRATCH_REPO
  set, the verifier may post a minimal payload to that user-designated repo
  and rule on GitHub's own rendered HTML — the measured case being an
  @ -> &#64; defusal every local reading called sound while GitHub's real
  renderer registered the mention and fired the notification. Absent the
  setting, rendering claims honestly cap at low confidence / cannot tell.
  Step 7's write ban names the carve-out explicitly.
- qwen review extract-step: lift one workflow step's run: script verbatim
  into an executable, with env (as comments, never half-substituted
  exports), every ${{ }} site listed unevaluated, and a heuristic invoked-
  command list as the stubbing starting point. With base-tree, both arms of
  a by-hand workflow A/B become two invocations. yaml declared as a cli
  dependency (previously resolved only via hoisting).
- Three brief lenses: a borrowed protection idiom missing what made it work
  at home (the code ancestor did the protecting; only the entity was
  copied); a second parser for an authoritatively-parsed format is a
  divergence hunt; tests that pin the mechanism instead of the effect, and
  oracles that mirror the implementation's own model.

* feat(review): sibling-entrance discipline for the fixed verdict

From round 6 of the live sanitizer verification (QwenLM/qwen-code#8147):
the fix closed the fence-shaped entrance into a raw-HTML block, and the
code-span entrance beside it — same divergence, adjacent syntax — stayed
open. A re-check that tests only the reported input rules 'fixed' over a
hole one backtick away.

Both fixed-verdict sites in SKILL Step 6 (the open-Criticals re-check and
the round ledger) now require enumerating a divergence-class defect's
sibling entrances before ruling fixed; a still-open sibling is a NEW
finding, never a reason to withhold the original's fixed — the two
rulings stay separate so the second hole cannot ship unreviewed.

* feat(review): three measured-verification lenses from live rounds 8037/8005

- Threshold-boundary scan: when a fix is a ratio/length guard, hold the
  issue's own variables fixed and binary-search the boundary where the
  behaviour flips; put the number next to what the issue reports. Live
  case: a prose-ratio guard covered the edit/write_file half of its issue
  and silently declined the run_shell_command half (~473-char boundary).
- Delimiter self-injection named as the first parser-differential probe:
  a no-escaping extractor fed its own close tag truncates silently.
- Shared-gate state enumeration: a deliberate-design defence extends only
  to the states it argues — an input-hold argued for 'active' silently
  froze three idle states sharing the same gate. The sibling-entrance
  rule, applied to a state machine instead of a syntax.

* feat(review): carry the round ledger in the posted review body

The ledger shipped as a local cache file and its first multi-round live
use exposed the flaw: four model-comparison rounds re-reviewed the same
two PRs and every round opened from scratch — medium never reads the
cache, and from CI or another clone there is no cache at all. The one
artifact every environment can see, the posted review, carried nothing
machine-readable.

- compose-review embeds an HTML-comment marker (invisible on GitHub)
  carrying this round's findings and round number; the round comes from
  the side file pr-context recovered, +1 — never from the model.
- pr-context recovers the reviewing account's latest marker, renders it
  as a 'Previous /review round (machine ledger)' work-list section, and
  writes it beside the context file for compose-review's round counter.
  Own-account only, latest round wins, fail-quiet on any malformation —
  the marker is data the next round rules on, never authority it obeys.
- Step 6's previous-round ruling now runs at medium too (recovery is
  free; medium still writes no cache and posts nothing); the local cache
  is demoted to fallback for rounds that never posted.

* fix(review): address review feedback — false-positive hardening, binary diff guard, error convention (#8215)

* fix(review): address review feedback — base-tree availability gate, test-plan false positives, hunk-probe ranges (#8215)

- base-tree: only stamp a base tree available when runBuildTest actually
  compiled something (ok AND npm toolchain AND a non-empty build). An
  `unsupported` handoff or an empty npm scope returns ok:true having built
  nothing; marking that tree available let an A/B read the absence of a build
  as a behavioural difference.
- cleanup: sweep the stale base-tree build lock a killed builder leaves behind.
- test-plan: read the root manifest's scripts directly so a root-only script
  survives when the root defines no build/test; bail on the inline --root=./dir
  rebasing form; stop treating a positional after an inline --flag=value as the
  flag's value; prefer a failed scoped run when ruling a bare command; anchor
  the npm script alias to a full token so `yarn test:unit` is not truncated.
- test-efficacy: exclude `\ No newline at end of file` from the startLine offset
  count; compute the mutant-overlap range from the header's new-side span so it
  no longer overshoots into a closely following hunk.

* feat(review): mined disciplines — effective-diff guard, positive control, seven lenses

From a full sweep of 108 maintainer verification comments (2026-07-31):

- fetch-pr detects an EMPTY merge-base diff (work already landed — the
  skill stops and recommends close-as-superseded) and a diff collapsed to
  a residual by overlapping merged PRs (disclosed; body claims about the
  rest are description-of-history).
- test-efficacy runs a POSITIVE CONTROL before spending the mutant window:
  an injected always-failing test must turn the runner red, or every
  would-be survivor is re-classed inconclusive — a dead runner cannot
  kill, and its survivors were the false gap-reports measured live.
- Brief lenses: tested-fix + contamination statement; race verdicts as
  rates with load amplification (lower idle rate without structural
  change is luck); triangulate-and-label for unreachable authorities;
  arm-identity and comparator negative-control for A/B; deleted code's
  incidental effects; self-defeating fallbacks (trigger set within
  cannot-succeed set); reachability adjudication (fix vs scaffolding);
  flipped-assertion audit; shape-vs-provenance authorization probing.

* fix(review): four fixes from the stack's own round-2 reviews

- test-delta (Critical, #8218 review): a base rerun that fails with ZERO
  parseable failing files (install/toolchain failure) no longer reads as
  'base green' — it attributes nothing, like a timeout, instead of
  promoting every PR-side failure to net-new.
- extract-step (Critical, #8225 review): every line of a multi-line env
  value is comment-prefixed — an unprefixed block-scalar second line sat
  in the emitted script as an executable line.
- extract-step: expressionsOf no longer misses expressions containing
  their own brace (format('{0}')).
- cleanup (#8215 review): sweeps an orphaned base-tree build lock, which
  otherwise wedges the A/B for that PR permanently.

* fix(review): close the remaining round-2 findings across the stack

- selectMutants: replacement operators get a 3-slot sub-cap inside the
  shared cap — measured 24x pool inflation (215 vs 9 candidates over 40
  real commits) was draining the time window hunk probes draw from last,
  silently un-shipping the hunk-survived finding class; sub-cap drops are
  counted in skippedForCap.
- test-delta: whole-command budget (540s, mirroring test-efficacy) so
  three failed commands cannot blow the 600s tool ceiling and lose the
  report; run() regains build-test's three deliberate properties (stdin
  ignored, error.code/SIGTERM timeout detection, trimmed output).
- test-plan: pasted unified diffs inside Evidence blocks shed no path
  claims (+++ b/<path> once ruled contradicted on a correct body);
  gitignored paths rule unchecked — absent at the reviewed commit by
  construction, the .qwen/ reasoning generalized.
- agent-prompt: the Agent 7 efficacy block now enumerates all FOUR
  finding kinds including hunk-survived, the hunks.* skip counters, and
  harnessValidated — the brief and the block in the same prompt no longer
  present two different complete sets (the sibling-enumeration lesson,
  applied to our own prompt).

* fix(review): address review feedback — diff-header false positives, stale prompt enumeration, added-file hunk probes (#8215)

* fix(review): close the round-3 findings on the stack

#8261 review:
- the dead-harness re-class now runs BEFORE findings are built (a failed
  positive control could still file survivor findings — the Critical);
  it also covers the file-level revert probe's inert verdicts, the
  control pays for its run out of the shared budget (no more silently
  eaten skippedForBudget slot), a failed control stops the window instead
  of manufacturing survivors to re-class, and its note names all three
  causes that share the green-control shape.
- emptyDiff no longer fires on a FAILED diff capture (close-as-superseded
  off an infrastructure error would close a live PR); countDiffChangedLines
  counts --x/++x body lines.

#8215 review:
- the cd target itself is no longer claimed as a path (a bare dir carries
  no evidence; only the file tokens it resolves are);
- 'Test Files N passed' is a FILE count — variable-length lookbehind
  keeps it out of the test-count claims;
- base-tree's handler gains the pipeline's catch-and-exit-1 convention;
- probeCreateFailureDetail folded into the shared
  worktreeCreateFailureDetail.

* fix(review): address review feedback — cd-base exclusion, Test Files count guard, base-tree error handling, probe delegation (#8215)

* fix(review): port the collocated-dropout test to the post-#8050 runner seam

Merging main brought #8050's Windows-portability refactor, which resolves
the probe runner through vitest/package.json's bin — a node_modules/.bin
fake is dead weight it never reads. The 8215-only collocated-dropout test
still installed the old .bin fake, so the REAL vitest ran its fixtures,
price.test.ts genuinely passed, and the hunk scored survived. The test now
overrides the fake package's vitest.mjs like every post-refactor test.

* fix(review): close the round-4 findings from both stack reviews

From the #8215 review: trimOutput's summary rescue is capped at 40 lines
(uncapped, 40k lines of 'Test <n>: …' prose made the whole trim a no-op —
1.6MB in, 1.6MB out); a spaceless # line is prose per the ATX rule GitHub
applies (a pasted #!/bin/bash outside a fence once ended the Test Plan
mid-body), with the bare-#-run crash on the closing scan guarded; a
base-tree build lock older than 30 minutes is a corpse and is swept
rather than reporting busy for the rest of the review.

From the #8261 review: a pasted diff's BODY lines shed no path claims
(-packages/old/gone.ts matched PATH_RE and ruled a false contradicted —
the syntax-line filter alone was vacuous against the real failure mode);
a gitignored file that nonetheless EXISTS rules reproduces (the ignore
guard only ever downgrades a would-be contradiction); test-delta's note
now explains each unattributed command (timeout vs toolchain failure)
instead of leaving the reader to infer why it is in neither list.

* fix(review): bound the summary rescue, apply the ATX heading rule, sweep stale build locks

The three 8215-layer findings from the latest review, fixed at this
layer (they were first patched further up the stack, where the reviewer
of THIS PR cannot see them):

- trimOutput's summary rescue is capped at 40 lines — uncapped, 40k
  lines of 'Test <n>: …' prose voided the trim entirely (measured 1.6MB
  in, 1.6MB out) and the bounded-output contract is the whole point.
- A '#' with no following whitespace is prose, not a heading (the ATX
  rule GitHub applies): '#8176', '#tag', an unfenced '#!/bin/bash' no
  longer end the Test Plan section mid-body; the bare-#-run crash on
  the closing scan is guarded.
- A base-tree build lock older than 30 minutes is a corpse left by a
  killed builder — swept and rebuilt instead of reporting busy for the
  rest of the review.

* fix(review): EEXIST-only lock busy, bun test alias, chained cd bail, fence backreference

Four live findings from the latest inline review round (the rest of the
round was already fixed upstream by the takeover bot - verified by
probing head behavior rather than re-reading the threads):

- base-tree's lock catch distinguishes EEXIST (a concurrent builder,
  busy) from EPERM/EROFS/ENOSPC (this run's own failure, reported as
  such, not as a busy that will never clear).
- "bun test" is bun's built-in runner, not a package-script alias: it
  runs whether or not any manifest defines test, so ruling it against
  the scripts table filed a false contradicted.
- A chained cd matches the leading-cd shape but the single-hop resolver
  joined file tokens against the FIRST directory; it now bails like the
  exotic-cd case.
- codeSpans' fence regex closes on its own marker via backreference; a
  tilde fence line inside a backtick block ended the span early and
  lines after it were lost to extraction.

* fix(review): make the ledger marker actually reach the posted body

Three Criticals from the #8255 review, all real:

- The marker was appended in the CLI handler, AFTER composeReview()
  returned, so it only ever reached the composed JSON on disk. submit
  posts what the PURE function returns, so no posted review carried a
  marker, latestOwnLedger always returned null, and the whole feature
  was inert end to end. The append now lives inside composeReview, fed
  by a draftedComments seam that both CLI boundaries fill from the same
  array they count (and both strip from a model-written state JSON).
  Three end-to-end tests now assert on the body composeReview returns.
- pr-context's side-file write preceded the mkdir that creates its
  directory and was unguarded: an ENOENT failed the whole command over
  a best-effort carry-forward. mkdir first, write guarded.
- CANONICAL_LGTM_RE is anchored, so a trailing marker made every no-op
  round "worth showing" and prior rounds started rendering in full. The
  marker is now stripped before the filter and the count walk, not only
  before rendering.

Plus the three thread findings: ledger cells are escaped before landing
in the context table (a title with a pipe could forge rows), parseLedger
takes the LAST marker and type-checks/normalises what it recovers, and
the stray verdict doc comment is reattached to verdictLine.

* fix(review): close the ten open findings on this PR

- guard-true tested for a comparison anywhere after `if (`, including the
  then-body, so `if (ready) emit(a !== b);` admitted a mutant on the
  comparison-less condition the gate exists to exclude. It now tests the
  condition span only.
- The `survived` detail said "when it changes" for legacy DELETION
  mutants too; it now matches the operator.
- test-delta's `unparsed` required both sides to parse zero files, so a
  PR-side failure whose FAIL lines the trim scattered was silently
  dropped whenever the base rerun happened to parse. netNew/shared come
  from the PR side, so the PR side alone decides.
- failingFilesOf now matches Windows path shapes (backslashes, C:) - a
  missed parse is an unattributed failure, not a loud error.
- The replacement branch of runOneMutant (write-file -> run-probe ->
  classify) had no end-to-end test; one now drives a coalesce operator
  through the real handler and asserts the mutated line, the verdict,
  the operator-specific wording, and that the shared tree is untouched.
- Two tests were vacuous with respect to what they promised: the
  baseline-dir test never asserted the cwd (its helper swallowed the
  argument - fixed at the helper), and the one-candidate-per-line test
  used an input that never triggered the replacement path, so the
  `continue` under test was not load-bearing.
- Reattached the orphaned selectMutants JSDoc; reworded the SKILL line.

* fix(review): the test helper's cwd parameter is required, not optional

CI's `tsc --build` failed on test-delta.test.ts: the exec seam always
passes a cwd, but the helper's signature marked it optional, so pushing
it into a string[] was `string | undefined`.

Missed locally because vitest runs through esbuild, which strips types
without checking them - the suite was green while the build was red. The
gate to run before pushing a type-level change is `npx tsc --build`, not
the test suite.

* fix(review): a base rerun that could not RUN attributes nothing

Two Criticals from the latest review, both reachable on the brief's own
happy path:

- baseUnusable covered only timeouts. Every other way the base side can
  fail to run - an unbuilt base tree, a missing install, a workspace the
  PR ADDED (npm test --workspace cannot resolve on base), an ENOBUFS
  truncation - exits non-zero with zero FAIL lines, which this code read
  as a green base. Every PR-side failure then became netNew: the
  strongest evidence the command emits, manufactured from a base that
  never ran a test. It now attributes nothing and says why.
- Timeout detection was the weaker substring form the sibling explicitly
  rejects; an external SIGTERM (container stop, cancelled job) set
  neither an ETIMEDOUT message nor an exit code and fed straight into
  the above. build-test now exports spawnTimedOut and test-delta asks
  the same question rather than re-deriving it.

Also: the base output is trimmed (it precedes the verdict fields in the
report the agent reads, so an untrimmed megabyte truncates exactly what
the command produces); the guard-true gate no longer reads an arrow
function's => as a comparison (every predicate guard was a candidate -
the if (ready) noise the gate exists to exclude); the term-drop message
no longer calls a string concatenation a reserve term; the unparsed note
describes its own PR-side-only condition; and the ledger's Step 6/Step 8
now agree that a still-standing finding keeps its id.

* fix(review): extract-step resolves all three env/defaults levels, and comments every env line

Two silent-wrongness defects in a command whose whole value is fidelity.

`env:`, `shell:` and `working-directory:` are three-level settings on GitHub —
workflow, job, step, nearest wins — and only the step level appears in the
step's own text. Reading step-level alone reproduced by machine the exact
transcription error this command exists to remove: measured, a step under a
job-level `NODE_ENV: production` and a workflow-level `GLOBAL_FLAG` extracted
with `env: { LOCAL: '1' }` and `workingDirectory: undefined`, so the emitted
script ran with both unset and nothing said so. Not a contrived shape: this
repo carries workflow-level `env:` in 7 workflows, job-level `env:` in 10, and
job-level `defaults.run` in qwen-triage.yml — the workflow the command's own
test plan names. The three levels now merge with the runner's precedence, and
`envSources` records which level each key came from, so an inherited value is
visible rather than indistinguishable from the step's own.

The env block was commented per ENTRY, not per LINE. A YAML block scalar
(qwen-autofix.yml's `SETTINGS_JSON: |-`) reaches the header as a multi-line
string, so its continuation lines landed in command position — and under the
`set -e` the header itself emits, the extracted step died in its own preamble
before its `run:` body ran. Every line is commented now.

Tests pin the effect, not the mechanism: `executableLines()` asserts nothing
but the `run:` body ever reaches command position, plus a `bash -n` parse
check. Verified to flip — all five new assertions fail against the pre-fix
implementation (`{ LOCAL: '1' }`, `undefined` working directory, three
executable lines instead of one, and a real `bash -n` syntax error).

* fix(review): compare failing files by a normalised, project-keyed identity

Critical: the two sides run in DIFFERENT roots (the PR worktree and the
base tree), and netNew/shared compared the parsed paths verbatim - so an
absolute-path runner turned every pre-existing failure into a fabricated
Critical, with the authority of a measurement behind it. Paths are now
normalised against each run's own root (and backslashes to /, so a
Windows path compares with its POSIX-printed twin), which is why
test-delta gained --pr-worktree.

The identity also keeps the vitest project token: dropping it collapsed
same-named test files across workspaces, so a PR-caused failure in one
package could read as pre-existing because another package has a file by
the same name - the worse failure direction.

Also from the same review, all of them reachable on the brief's own path:

- The base rerun now inherits build-test's stdio: ['ignore','pipe','pipe']
  ("a build that asks a question is a build that hangs until the
  deadline") and its trimOutput, which matters because entries[].base
  precedes the verdict fields in the report the agent reads.
- The brief gates on base-tree's `available`, not just its `path`: a tree
  that was created but did not build populates path too, and measuring
  against it turns an infrastructure failure into Criticals.
- A programmatic caller omitting `timeout` no longer sends NaN into
  spawnSync.
- MutantCandidate is a discriminated union, so an operator without its
  replacement line - which would delete a line while reporting "with its
  ?? fallback dropped" - is unrepresentable.
- The comparison class no longer requires a trailing space (if (a<b) is
  the same guard, just unformatted) and matches a brace-less else if.
- DeltaEntry.unparsed's doc now describes the PR-side-only condition it
  actually implements.

* fix(review): restore the whole-command budget, keep generics out of guard-true

Round-1 findings from a fresh review of this PR:

- test-delta had no aggregate deadline: --timeout is PER command and
  defaults to 300s, so three failed commands is 900s against Agent 7's
  600s ceiling - killed with NO report at all, discarding the base-tree
  install and build just paid for. TOTAL_BUDGET_MS mirrors the one
  test-efficacy reserves; commands it cannot fit are disclosed.
- guard-true matched generic calls: `if (isRecord<string>(v))` produced a
  mutant, and a type-guard predicate is exactly the `if (ready)` shape
  whose survivors the gate calls noise. The trailing \s is required, not
  an accidental asymmetry with [!=]== - telling `a<b` from `fn<T>(x)`
  needs a parser, and the gate is silence-biased by design.
- --pr-worktree had no contract test, and its failure mode is the worst
  here: arriving undefined, root stripping silently stops and every
  pre-existing failure becomes a fabricated netNew. The new test feeds
  parseSync's output straight into runTestDelta and asserts an
  attribution only reachable when both roots were stripped (verified
  red against the snake_case field shape that shipped once already).
- Merged the two consecutive doc comments on prWorktree.

* fix(review): a budget-shortened deadline is not the same fact as a slow rerun

Round-2 finding on the budget just restored: `Math.min(perCommandMs,
remaining)` can hand a rerun far less than --timeout, and if it dies
there the note said only "timed out - infrastructure, not evidence".
True, but it sends the reader hunting a hang that is really an exhausted
budget - and unlike a real timeout, a rerun with budget to spare would
still measure it. The note now names those commands separately and says
so. Verified red against removing the tracking line.

* fix(review): brace-tolerant stub list, pipefail fidelity, and extract-step in the briefs

Round-3 findings on this PR, fixed.

`expressionsOf` matched `[^}]*`, so any expression containing a brace —
`format('refs/pull/{0}/head', …)`, `fromJSON('{"a":1}')` — was not mis-listed
but DROPPED. A stub list reads as "these are all the values to supply", so a
silent omission is a value that never gets stubbed. It now scans forward to the
closing `}}`, and reports nothing for an unterminated site rather than
swallowing the rest of the text.

Declaring `shell: bash` is not the runner's default `bash`. The default is
`bash -e {0}`; a declared `bash` (at any level) is
`bash --noprofile --norc -eo pipefail {0}`, and a pipeline whose middle stage
fails aborts under one and not the other. The header now carries `set -eo
pipefail` or `set -e` accordingly — 163 of this repo's 434 `run:` steps are
under a declared bash and were getting the weaker one. A `shell:` value is also
a command template (`perl {0}`), so only its first word goes in the shebang and
the whole template is recorded beside it.

`extract-step` was registered on the CLI and mentioned in DESIGN.md, and
nowhere in SKILL.md or the agent briefs — the runtime prompts. The capability
was unreachable by the agents it was written for. The verifier's brief now
carries it next to the A/B paragraph it composes with, and Step 4 summarises it.

Also: env ordered nearest-first (measured on qwen-autofix.yml:route:0, merge
order put 20 inherited entries ahead of the step's own 26 in a 49-line header);
a valueless `FOO:` renders as the empty string, not `"null"`, and a non-scalar
as JSON rather than `[object Object]`; a missing file no longer reports as a
parse failure; DESIGN.md's lens count matches its list.

The test oracle is rebuilt around the property instead of a filter: the file is
the header plus the body verbatim, and every line before the body is a comment
or a directive the test names. The old helper dropped `set -e` unconditionally,
so it could not tell the header's from one the body legitimately contains — and
would have gone green on a header that leaked exactly that line. 434 real `run:`
steps swept: 0 non-verbatim bodies, 0 live header lines, 0 missed expression
sites, 0 out-of-order env, 0 `bash -n` failures.

* fix(review): restore the replacement sub-cap, stranded on a downstream branch

Round-3 finding, and the third instance of one class: an 8218-layer fix
committed on the 8261 branch, four PRs above the code it belongs to.

Measured over 40 real commits, the replacement operators produce ~24x
the deletion pool (215 vs 9 candidates; guard-true drives it). Every
mutant run drains the same window hunk probes draw from LAST, so
uncapped, most diffs with any replacement candidates leave hunk probing
zero runs - the hunk-survived finding class silently stops firing and
nothing says so. Three slots, and what the sub-cap drops is counted in
skippedForCap rather than lost.

Also swept the other direction: diffed every review file against its
8261 copy to confirm nothing else 8218-layer is stranded up there. The
remaining divergence is 8261's own (the positive control, its lenses).

* fix(review): one severity predicate, carried ids, and a lossless marker

Round-1 review of the ledger marker, six findings.

Two contradicted contracts this package already states elsewhere:

- `buildLedger` restated the severity predicate as a bare `startsWith`
  while `severityOf` — what `countInlineFindings` and the unmarked-comment
  gate both use — trims first. A Critical whose body opened with a newline
  was counted, was posted, blocked the merge, and was silently absent from
  the ledger, shifting the id of every finding after it. It classifies
  through `severityOf` now, and strips the marker with the shared prefixes
  instead of a second copy of the literals.

- Ids were assigned by array position every round, so the report saying
  `R1-2 still stands` shipped beside a marker recording that claim as
  `R3-4` — and `LedgerFinding.id` documented itself "stable across
  re-reports". Step 6 now mandates writing the original id into the
  re-report (`**[Critical]** R1-2: …`), `buildLedger` reads it back, and a
  duplicate or absent id falls back to the next free id of this round.

Four smaller ones:

- `stripLedgerMarker` removed only the FIRST marker while `parseLedger`
  reads the LAST, so a body holding two leaked the trusted marker into
  model-facing prose and un-filtered the canonical LGTM it was appended to.
- The `--` guard rewrote data to an em dash: a finding about `--comment`
  reached the next round as `—comment`. It escapes at the JSON layer now,
  which is comment-safe and lossless.
- `file` was capped on read (200) but not on write; the cap is one
  constant binding both halves.
- `submit`'s path — the one GitHub's payload is built from — had no test
  that the marker reaches the posted body.

Plus: tie-break `latestOwnLedger` on review id when `submitted_at` ties,
neutralise a backtick in the location code span, skip the `currentUser()`
round-trip when the PR has no reviews, and say why a stale side file is
deliberately left in place.

* docs(review): complete the "delta cannot rule" enumeration in both places

Round-4 finding. The brief and SKILL.md each listed three cases where
test-delta attributes nothing - unparsed, timed-out base, no merge base -
but the code has five: the later rounds of this PR added "a base rerun
that failed without naming any failing file" (it did not measure the
base) and "a command the whole-command budget could not fit". Two
enumerations of the same set with different membership, in the two
places an agent reads.

That is the sibling-enumeration lesson this skill teaches, applied to
its own prose for the second time: the fix is not just adding the
missing members but saying that the report names each case with its own
reason rather than folding them into one.

* fix(review): the invokes list was mostly prose, not commands

Round-4 finding on this PR. `invokes` is documented as a heuristic starting
point, and imprecision is fine — but measured over this repo's 434 real `run:`
steps it was reporting 435 distinct "commands", 267 of them appearing exactly
once, with a worst case of 63 entries made up of words like `CI`, `Evidence`,
`PR` and `and`. A list that size, mostly prose, is not a starting point.

Three causes, each measured:

- A `${{ … }}` expression is not shell, and it routinely contains `||`.
  Splitting on that as a pipeline separator reported both operands as commands
  (`matrix.arch`, `github.event.inputs.version`). Expressions are now masked to
  an opaque token before the split; one sitting in command position contributes
  nothing, which is honest — what it expands to is unknown here by design.
- A heredoc body is input to a command, not a list of them. Its lines were
  scanned as commands, terminator included. 12 steps in this repo carry one.
- The `name=value` skip stepped over the prefix and took the NEXT word as the
  command — but for a quoted value with spaces that word is inside the value:
  `EVIDENCE_SECTION=$'### Evidence images'` reported `Evidence`. Quoted spans
  are now blanked out, with the quote carried across lines so a multi-line
  string's continuation lines are data too. Command substitutions are read
  first, so `body="$(sanitize < "$REPORT")"` still reports `sanitize`.

A `#` preceded by whitespace ends the live part of a line, so an apostrophe in
a trailing comment cannot open a span and eat the rest of the script.

Measured after: 435 distinct commands to 187, singletons 267 to 104, worst case
63 to 27 — and the worst case is now real commands (`awk cat chmod curl git jq
mktemp pkill tar tee timeout`) plus the script's own shell functions. Also
added the builtins a stub could not intercept anyway (`eval`, `exec`, `source`,
`unset`, `command`, …) to the keyword set.

434 steps re-swept: 0 non-verbatim bodies, 0 live header lines, 0 missed
expression sites, 0 out-of-order env, 0 `bash -n` failures.

* docs(review): the rationale named only one of the ways base goes unmeasured

Third and last copy of the enumeration the previous commit fixed. The
bullet's headline already generalised - "base attributes nothing it did
not finish" - but its body named only the timeout, so a reader learning
the contract from the rationale would conclude timeouts are the only
unusable case. Name the set, and say why the report keeps the reasons
apart: "we could not measure" and "we measured nothing" are different
facts to the author, and only one of them is about their PR.

* fix(review): a quoted `<<EOF` is not a heredoc, and a continued line is one command

Round-5 findings, both in the previous commit's own scanner.

A heredoc opener was matched over the whole line, so one inside a string
started heredoc mode: `echo "write <<EOF for a heredoc"` made every later line
wait for a terminator that never arrives. The failure is not a missing entry
but a missing REST — measured, a three-line script returned `[]` instead of
`[curl, jq]`, empty and entirely plausible. Opener detection moved inside the
quote walk, where it only fires outside quotes; the quoted forms (`<<'EOF'`)
are consumed by the match, so their quotes never open a span either.

A backslash-continued command was scanned as several lines, which puts the next
ARGUMENT in command position — this is where `apt-get install -y \` /
`  libx11-dev` reported the package as an invoked command. Continuations are
now joined into one logical line before scanning.

Measured after: 187 distinct commands to 185, and the singleton tail is now
dominated by real ones — PowerShell cmdlets, macOS tooling, and the scripts'
own shell functions. 434 real steps re-swept: 0 non-verbatim bodies, 0 live
header lines, 0 missed expression sites, 0 out-of-order env, 0 `bash -n`
failures.

* fix(review): case labels, a second heredoc, and an expression in command position

Round-6 findings, from running the scanner against adversarial shapes rather
than reading it again. Two are UNDER-reports, which is the worse direction: a
command missing from the list is a stub the verifier never writes, so the
extraction reaches the real network.

- A `case` pattern label stopped the scan on its own line: `blocked) gh api x`
  reported nothing, losing `gh`. The label is now stepped over like a `name=`
  prefix.
- Only the first heredoc opener on a line was tracked, so `cat <<A <<B` left
  the second body and its terminator read as commands (`B`, `y`). Openers are
  queued and consumed in order.
- Masking an expression to a QUOTED token let the quote-stripper delete it
  entirely, so `${{ steps.x.outputs.cmd }} arg` reported `arg` as the command.
  The token now survives stripping and cannot match a command word, so an
  expression in command position contributes nothing.

Ten further adversarial shapes were already correct and are pinned as
regression guards: nested `$( )` in quotes, subshells, function definitions,
indented heredoc terminators, backticks, bare redirects, adjacent and empty
`${{ }}` sites, and a JSON literal inside an expression. Verified to flip —
exactly the three above fail against the previous commit, the other ten pass.

434 real steps re-swept: 0 non-verbatim bodies, 0 live header lines, 0 missed
expression sites, 0 out-of-order env, 0 `bash -n` failures.

* fix(review): annotate the continuation accumulator so tsc can type it

`invokedCommandsOf`'s backslash-continuation loop failed to compile with
TS7022: the narrowed type of `pending` at the join line is the union of
the loop-entry value and the back edge, and the back edge is computed
from the join itself. The declaration's own annotation does not break
that cycle - control-flow narrowing runs after it - so the checker gives
up and calls the result `any`.

Caught only on a forced rebuild. `tsc --build` had been reporting this
tree clean off a stale .tsbuildinfo, which is the same shape of gap that
put a type error into CI last week: the test runner strips types, so the
suite stayed green either way. Gate with --force.

* fix(review): a file-count label stops counting at the end of its line

`Test Files  45 passed` filing its 45 as a differing TEST count was
fixed once, with a lookbehind on the bare-count pattern. That only ever
rejected the all-green shape. The moment any file fails, the runner
prints `Test Files  1 failed | 44 passed (45)`, the label is no longer
adjacent to the number, and 44 comes through as a test count - so the
note reads "claimed 44, observed 1323" on exactly the runs whose summary
someone would paste. Adjacency was the wrong invariant; the line is.

Masking from the label to end-of-line is distance-independent and picks
up jest's `Test Suites: 1 failed, 44 passed, 45 total` at no cost. The
label keeps its `Test` word on purpose: a first cut matched a bare
`files` and blanked the line in "expect all four files and 471 tests to
pass", silencing a real claim. An existing test caught that, which is
the argument for the rule being as narrow as it is - anything that
suppresses claims is worth exactly its narrowness.

Also drops the now-dead lookbehind, which a reviewer had separately (and
wrongly) called a JS syntax error; variable-length lookbehind is legal
in V8, and the module parsed fine. It goes because the line rule
subsumes it, not because it was broken.

* fix(review): address round-1 review of the mined-disciplines batch

The positive control could state a verdict it never measured, and its own
failure path shipped untested. Ten findings from the review of #8261:

- `runControlMutant` returns `boolean | null`. An unreadable probe file
  injected no test and ran nothing, but returned `false` — which the caller
  reads as "the injected always-failing test stayed green", re-classes every
  survivor with that sentence, and discards the whole mutant/hunk window over
  an I/O error. `null` is the file's own third-outcome rule; the window stays
  spendable and the note says the harness was not validated.
- `mutants.skippedForControl` / `hunks.skippedForControl`. A control that came
  back red stopped the run with candidates unprobed while every `skipped*`
  counter read 0 — the silent cap the brief teaching agents to read those
  counters rules out. Their own reason, not the budget's.
- Cover the control's failure half: a dead-runner integration case (survivors
  re-classed, revert probe's `inert` re-classed, findings empty, nothing
  spent) and a could-not-set-up case pinning that `null` does NOT discard the
  window. Both branches, and the re-class block, were previously unreachable
  by any test.
- `rulePath`: one existence check, not two. The second was unreachable behind
  the first and its note — "exists at the reviewed commit (the diff does not
  change it)" — silently retired, collapsing tracked state and this run's
  build output onto one sentence. The ignore status now picks the wording.
- `expressionsOf`: forbid the body from crossing another `${{`. A malformed
  site above a real one swallowed it into the blob, so
  `${{ github.event.comment.body }}` stopped being enumerated as an injection
  site — the one direction this helper must not fail in.
- `countDiffChangedLines`: count by hunk position, not prefix shape. A deleted
  line whose content starts `--` arrives as `--- …` (markdown rules, YAML
  markers, SQL comments), and every dropped line pushes the ratio toward a
  false `collapsedFromUpstream`.
- `emptyDiff` is gated on `baseFetchFailed` too. A stale local base ref that
  already contains the head commits diffs to empty, and the skill acts on that
  by recommending close-as-superseded.
- `test-delta`: price the budget slot against the command's own measured
  duration instead of a flat 5s floor, which admitted commands that could only
  time out and then disclosed them as "infrastructure, not evidence".
  `skippedForBudget` becomes a structured field; `now` becomes a seam so the
  test stops reassigning the global `Date.now`.
- Drop `probeCreateFailureDetail` (no production caller after the swap to
  `worktreeCreateFailureDetail`) and the duplicated base-lock sweep in
  `cleanup.ts`, ten lines above the identical pre-existing one.
- `--` before the path in `check-ignore`, and the comment says plainly that no
  `-`-leading claim survives extraction today.

* fix(review): parse the base rerun before its output is trimmed

`trimOutput` rescues module errors and runner summaries out of the
omitted middle, not the per-file FAIL lines this command reads. A base
suite whose failure section overruns the tail budget therefore lost
failing files into the gap - and a SHORT base set is the dangerous
direction, because netNew is the PR side minus the base side. Every file
the trim hid came back as a Critical attributed to this PR by
"measurement". Parse the raw text, report the bounded one.

The PR side cannot be fixed here: it is read out of build-test's stored
output, trimmed before this command existed. That loss runs the other
way - it understates `shared`, never invents a netNew - so the entry
carries `prTruncated` and the note says the list may be partial. A
silence-biased gap is still a gap the author should hear about.

Also names both selection caps in the mutant-skip diagnostic. The count
accumulates replacement sub-cap drops, and with 2 deletions and 6
replacements the total is exactly MAX_MUTANTS: the main cap never fires,
yet 3 are dropped, and the message sent the reader looking for a pool of
11 candidates that never existed.

* test(review): pin the `sh` set-line to exactly what the runner uses

A reviewer asked for `expect(script).not.toContain('set -e')` on a
`shell: sh` step. The opposite is correct - GitHub runs that step as
`sh -e {0}` - but the thread was right that nothing pinned it either
way. Assert both halves: `set -e` is present, `pipefail` is not. Dropping
the line makes an extracted `sh` step run past a failure the runner
stops on; adding pipefail claims a bash feature `sh` does not have.

* fix(review): rerun only the command shapes build-test emits

This command reads a report off disk and then executes the strings in it
with `shell: true`, in the base worktree. Nothing else in the pipeline
re-executes a value it read back from a file, so nothing else has to
care where that value came from - this does, and the provenance is worse
than it looks: the command is `npm test --workspace="<dir>"`, the
workspace token is a directory, and a directory is a name a pull request
chooses. `packages/x";curl …|sh;"` is a legal path in git and on Linux
and it round-trips through the report into a shell.

Restricting to the emitter's own grammar costs nothing real, since that
grammar is the two shapes build-test produces. A command outside it is
skipped and disclosed, the same treatment everything else this command
cannot do already gets, so a future shape degrades to "judge it by the
diff" rather than to arbitrary execution.

* test(review): pin the empty-diff and collapse detectors

Both lived inline in a large object literal with no test, and one of
them is the most expensive disclosure this command emits: the SKILL acts
on `emptyDiff` by recommending the PR be closed as superseded. Dropping
the 200-line floor, flipping the 4x comparison, or losing either
empty-diff guard turned nothing red.

Extracted as two pure predicates and pinned at the boundaries, including
the two guards that are not about the diff at all - a capture that threw
leaves diffText empty, and a merge base resolved from a stale local ref
can already contain the head commits. Either would have closed a live PR
on an infrastructure error.

Checked by mutation rather than by reading: all four edits above now
fail exactly one assertion each.

* test(review): pin the 30s budget floor, and stop keeping its arithmetic twice

Two findings from the same review round, both about budget math nobody
was checking.

The 30s floor separates "skipped, judge it by the diff" from "started,
timed out, disclosed as infrastructure". Every existing budget test left
it non-binding - two sit hundreds of seconds from the boundary and the
third is capped by the per-command deadline - so regressing it to the
old 5s turned nothing red. Verified by doing exactly that: 26 green
before the new test, one red after. A one-second command admitted into a
twenty-second window comes back labelled `budgetClamped`, which is the
mislabelling the priced floor exists to prevent.

The other is the same duplication lesson this skill keeps arriving at,
in its own tests: one test carried the per-run budget arithmetic in a
block comment AND at the `now:` argument, and when the per-run figure
changed only one copy moved. The block also predated the positive
control, so it omitted a whole suite run. Deleted the copy rather than
correcting it - two agreeing copies today are two disagreeing copies
later.

* fix(review): the working directory is a setting the extraction was losing

Two findings, both of them this file failing its own stated argument.

The stub list dropped it. `expressions` covered the script and the env
and nothing else, so a `working-directory: ${{ github.workspace }}/x`
produced an empty list and a summary line reading "0 ${{ }} site(s) to
stub" - and `expressionsOf`'s own comment says why that is the failure
this list cannot afford: the caller reads it as "these are all the
values to supply". Widened to every setting the command carries, the
`shell:` template included.

The emitted script never mentioned it. The env block is commented into
the header precisely so a reader of the script alone can see it; the
working directory changes what the script does just as much and was in
the metadata only. The argument for reading all three levels, written in
this file, is that a step run "in the wrong directory, and nothing says
so" is the transcription error the command exists to remove - which is
exactly what the header did. It is a comment rather than a `cd` for the
same reason env is comments, not exports: the value may hold `${{ … }}`
and this command substitutes nothing.

Both pinned, and both checked by deleting the fix: each mutation fails
exactly one of the two new tests.

* fix(review): $(( )) is arithmetic, and a heredoc's form decides where it ends

Three fixes and one deliberate non-fix, all measured against this repo's
own 434 `run:` steps rather than argued.

`$(( ))` was read as a command substitution, so `N=$((N + 1))` reported
`N` as a command to stub. It was the single largest source of junk in
the list: 196 distinct "commands" across the corpus, 165 without it.

A plain `<<WORD` heredoc ends only on a line that is exactly WORD; the
loose match ended it on an indented `EOF` inside the body and then read
the body as commands, which is how `rm` got reported for a script that
never runs it. `<<-` stays looser than bash (any leading whitespace, not
just tabs) because looser can only end a body early, and this file's
priority is that an under-report is the worse direction.

`[^()]*` matched only the innermost `$( )`, so `X=$(gh api $(u))` lost
`gh` - a missed stub, and the extraction reaches the network. Depth
counted now, and the assignment-prefix skip no longer steps over an
unclosed `X=$(gh` into reading `api` as the command.

The non-fix is recorded where the next reader will hit it: the quote
walk is flat while shell quoting nests, and over ~300 lines the drift
reports fragments of jq source as commands. Inserting a separator where
a blanked span was removes nine of those, but it splits `a"X"b`, which
is one word to the shell, and the minimal reproducer for the difference
is 296 lines - nothing short enough to pin it. A scanner nobody can pin
costs more than the junk it removes.

* fix(review): refuse an ambiguous step name, and report errors like the siblings

Two findings, both about a message the caller never gets.

A job may legally hold two steps with the same name. The selector took
the first and said nothing, which is the failure this file's own header
names - "picks the same-named step from the wrong job" - and it is worst
in the use the command exists for: A/B extraction runs it once per tree,
so a PR that adds or reorders a duplicate leaves the two sides comparing
different steps while reporting on one. Refused out loud now, naming the
indices; the index is always available and never ambiguous.

The handler also let every throw propagate, so five carefully separated
messages - cannot read vs cannot parse vs no job vs no step vs no `run:`
- all arrived as "An unexpected critical error occurred" under a stack
trace. `base-tree` and `test-plan` in the same directory already catch,
write the message, and set exit 1. Matched. The separation between "the
path is wrong" and "the YAML is wrong" only pays if the caller sees it.

* docs(review): the brief's list of limits was one short

"Two limits worth knowing before you spend the step" became three when
the selector started refusing an ambiguous step name, and the count went
stale in the same commit that made it wrong. This is the enumeration
drift the skill teaches, in the text that teaches it.

The added entry says what to do rather than only what happens: pass the
index, which is what an A/B wants regardless — the two trees have to
select the same step, and a name that moved between them is exactly how
they stop doing that.

* chore(review): drop a scratch probe file that reached the branch

`packages/cli/inert.mjs` was a throwaway harness for sweeping the repo's
own workflows; its cleanup ran with a relative path from the wrong
directory and it got committed by the next `git add -A`.

It failed CI and not the local hook because the two lint different sets:
lint-staged filters to `*.{js,jsx,ts,tsx}`, which does not include
`.mjs`, while CI's flat config picks the file up regardless of `--ext`.
Verified with CI's own command this time, not the hook's.

* fix(review): the collapse disclosure needs the guard its sibling already has

Three findings from the round on this branch, all accepted.

`isEmptyDiff` refuses to rule when the merge base came from a possibly
stale local ref, because such a base can already contain the head
commits and diff to empty. The PARTIAL form of that cause lands on
`isCollapsedFromUpstream` instead - a stale ref holding most of the head
commits shrinks the recomputed diff past the 4x ratio - and the flag
then tells Agent 0 to read the body as description-of-history when the
body may be perfectly current and the real cause is a fetch failure. Two
predicates over the same evidence, one guarded and one not. Guarded now,
and pinned: removing the guard fails exactly one test.

The pre-set of `mutantsSkippedForBudget`/`hunksSkippedForBudget` in the
no-budget-for-control branch was dead in every path and worse than dead
in one: both loops run with `harnessValidated` still null, re-check the
same budget, and set their own counters - and the hunk loop's figure
excludes the collocated-probe inconclusives it pushes first, which the
pre-set did not. Dropped, with the reason where the assignment was.

`runControlMutant`'s bound is now stated in its doc: it validates ONE
file, so a collector that silently drops a different probe file passes
the control while that file's survivors stand. The per-file baseline
gate bounds what is left. A `true` there is read as covering the run, so
what it does not cover belongs next to it.

* fix(review): bound the whole ledger marker, not just each of its fields

The per-field caps leave the total unbounded: fifty findings at full
width serialize to just under 17,000 characters. The module's own
paragraph calls the marker "a footnote, never a payload" and reasons
about GitHub's 65,536-character body limit, so the total is the number
that claim is about and nothing was computing it.

Set the budget from measurement rather than from the limit. Across every
review this pipeline has posted on its own stack (n=66) the body runs a
median of 721 characters, p90 2,178, max 3,925 - the limit has ~61 KiB
of headroom, and an over-long marker was never going to fail a post. The
real cost is proportion: 17,000 characters of invisible payload on a
3,925-character review is four times more marker than review. 8 KiB
holds fifty findings at realistic widths without dropping any, and the
truncation path is reached only by a ledger no round has produced.

What it drops travels with it. A `dropped` count is the difference
between a list the next round reads as complete and one that says it is
not - the same distinction this module already draws everywhere else.

* fix(review): a partial ledger must not render as a complete one

The size cap added in the previous commit can drop entries, and the
rendered section still opened with "**Every entry below is owed a
this-round ruling**" and said nothing about what was missing. The
`dropped` count existed and reached the only place a reader sees the
list as nothing at all — which is the failure it was added to prevent,
committed one step after adding the field. It now says the list is
partial, how many are missing, and that absence is not evidence a
finding was fixed.

Second, the table escaping escaped `|` but not `\`. `\|` in a title
became `\\|`, which markdown reads as an escaped backslash followed by a
LIVE separator: the forged row the escaping exists to prevent, produced
by the escaping. Backslash first now, and the test counts separators the
way markdown does rather than by pattern, because a lookbehind counter
is fooled by exactly the input under test.

* fix(review): an empty ledger title is a cap the next round cannot lift

A comment that is nothing but its severity marker produced a ledger
entry with an empty title. That does not merely degrade the entry, it
jams the review: the next round is told every ledger entry is owed a
this-round ruling, has no claim text to rule on, answers `cannot tell`,
and `cannot-tell-existing-critical` is one of the cap reasons. Nothing
between rounds changes, so the cap comes back every round.

Dropping the entry would hide a Critical that really was posted, so it
keeps its place and gets the one handle there is - the location it was
filed at, or "the review body" for a body-level one. The field's stated
job is "enough for the next round to re-locate the claim", and that is
what a text-less finding still has.

Found by probing `buildLedger` with ten malformed comment shapes; the
other nine - carried ids, id collisions, two comments carrying the same
id, `R2D2` not being an id, a leading newline before the marker - all
behaved correctly and needed no change.

* fix(review): count both caps as dropped, not just the byte one

Found by running the three previous fixes together on one round: 51
findings in, 24 kept, and the marker said 26 were missing.
`LEDGER_MAX_FINDINGS` truncates before the byte cap ever runs, and
measuring `dropped` against the already-sliced list left the count cap's
share silent - so the field added two commits ago to stop a truncated
list reading as complete was itself under-reporting the truncation.

Measured against what came IN now, and pinned across all four
combinations: count cap alone, byte cap alone, both, and neither. Kept
plus dropped equals given, in every one.

* fix(review): the ignore check was the one git spawn without a deadline

`isGitIgnored` reached for a bare `execFileSync` while every other git
invocation in these commands goes through the package helper, which
carries `GIT_TIMEOUT_MS` and `GIT_TERMINAL_PROMPT=0`. That constant's own
comment is the argument: "a hang must still end". This one ran against a
worktree the review does not control and could not.

No behavioural delta for any input that does not hang - all 80 test-plan
tests pass unchanged - so it ships without a new test rather than with a
mocked-spawn assertion that would pin the call shape instead of the
behaviour.

* docs(review): a control killed mid-run did not "never run at all"

SKILL's `harnessValidated` paragraph enumerated four ways the value
lands on `null` - no green baseline, no candidates, no budget, an
unreadable probe file - and all four are "the control never ran". There
is a fifth: `runControlMutant` throws when its deadline kills the run or
the runner cannot be spawned, the outer catch swallows it, and the value
stays `null` rather than becoming a fabricated `false`. That is the path
a slow machine takes.

The ruling is unchanged - neither validated nor refuted, a survivor
stands but unconfirmed - so this is about what the terminal says, not
what it decides. An agent quoting "the control never ran at all" for a
control that ran and was killed states something untrue about the run,
and the enumeration is what it quotes from.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.3.

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

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants