Skip to content

feat(cli): Add review settings for attribution, default effort, and default comment - #8994

Merged
wenshao merged 12 commits into
QwenLM:mainfrom
wenshao:review-settings
Aug 13, 2026
Merged

feat(cli): Add review settings for attribution, default effort, and default comment#8994
wenshao merged 12 commits into
QwenLM:mainfrom
wenshao:review-settings

Conversation

@wenshao

@wenshao wenshao commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds three user settings for the /review skill, resolved from operator-controlled settings scopes only (system defaults → user → system); a repository's .qwen/settings.json cannot set them, because repository-controlled content must not control the policy a review of that repository runs under. All three are toggleable in the /settings dialog:

  • review.attribution (boolean, default true) — controls the attribution footer naming the reviewing model and CLI version (e.g. — qwen3-coder via Qwen Code /review (v0.21.2)) that is appended to posted review bodies and inline comments. With attribution off the footer is not emitted and modelId is neither required nor validated (it has no consumer), while forged footer copies in drafted comments are still stripped. Presubmit's duplicate detection stays sighted without the footer: it also recognises the reviewing account's own top-level comments by author.
  • review.effort (enum auto / low / medium / high, default "auto") — the default effort when --effort is not given. "auto" keeps the built-in rule (high for PRs, medium for local changes); the configured value is normalised through the same case-insensitive validation as the flag, and invalid values fall back to the built-in default. An explicit --effort always wins, and the existing forcings are untouched: an effective --comment still forces high (posting requires a verified review) and an effective --fix still floors at medium. The verdict gains an effortSource: "configured" value and the skill doc describes it.
  • review.comment (boolean, default false) — treats every PR /review as if --comment was passed, so findings are published without typing the flag each time. This replaces the flag in the authorization gate, not the target binding: the write is still authorised only for the pull request the recorded invocation names (repo and host included for URL targets), and the success message states that the standing setting — not a typed flag — authorised the post. Both callers of the shared gate (submit and publish-assets) resolve it the same way, so a posting run never publishes the review while being refused its evidence images.

Defaults preserve the current behavior exactly; the IDE settings.schema.json is regenerated and the user docs and bundled skill doc are updated (Step 7's gate and every comment-specified branch now key off comment.effective).

Why it's needed

Users who review many PRs currently re-type --comment and --effort on every invocation, and there is no way to make the AI-attribution footer match what a given account or repository wants posted. These are per-user decisions — the settings put them where user preferences belong (operator-level config), instead of requiring flags on every run.

Reviewer Test Plan

How to verify

  1. Unit tests: cd packages/cli && npx vitest run src/commands/review src/config — all pass; the suites pin the configured-effort precedence chain (explicit > configured > built-in, with the --comment/--fix forcings still overriding), effort normalisation ("Low"low, invalid → built-in default), the standing comment setting producing an effective comment on PR targets only, the authorization gate accepting the setting while refusing a submission bound to a different PR, publish-assets agreeing with submit on what authorises a run, presubmit recognising attribution-off posts, and handler-level wiring for all three settings (parse-args, submit, compose-review).
  2. Manual: put {"review": {"attribution": false}} in ~/.qwen/settings.json, run /review <pr> against a scratch PR, and confirm neither the review body nor the inline comments carry the via Qwen Code /review footer; with the key absent the footer appears as before. Same for review.effort: "low" (verdict shows effortSource: "configured") and review.comment: true (posting authorises without --comment, message names the setting). A workspace .qwen/settings.json carrying any of the three keys has no effect (operator scopes only).

Evidence (Before & After)

N/A — no TUI change; the observable effect is the posted GitHub review content, covered by the unit tests above.

Tested on

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

Environment (optional)

Unit tests plus npm run dev for handler wiring checks.

Risk & Scope

  • Main risk or tradeoff: review.comment: true makes publishing the default for every PR review. That is the explicit intent of the switch (a standing authorization the user opts into); posting still binds to the PR named in the invocation, so it cannot act as a bearer token for other PRs. Presubmit's attribution-off recognition keys on the authenticated account's own top-level comments — attribution-off posts from other accounts remain undetectable (no footer, no authorship signal), which the setting's documentation notes.
  • Not validated / out of scope: none of the changed behavior applies to untrusted workspaces (workspace settings never merge there) or to the workspace scope at all for these keys (operator scopes only by construction).
  • Breaking changes / migration notes: none — all defaults reproduce today's behavior.

Linked Issues

None.

中文说明

这个 PR 做了什么

/review skill 增加三个用户配置项,仅从操作者可控的配置作用域解析(系统默认 → 用户 → 系统);仓库的 .qwen/settings.json 无法设置它们——仓库可控的内容不应控制对该仓库所做 review 的策略。三项均可在 /settings 对话框中切换:

  • review.attribution(boolean,默认 true)——控制发布的 review body 与 inline comment 上标注模型与 CLI 版本的署名 footer(例如 — qwen3-coder via Qwen Code /review (v0.21.2))。关闭后不输出 footer,modelId 也不再被要求或校验(已无消费者),同时仍会剥离草稿评论中伪造的署名副本。presubmit 的重复检测不依赖 footer 也保持可见:它还会按作者识别审查账号自己的顶层评论。
  • review.effort(枚举 auto / low / medium / high,默认 "auto")——未显式指定 --effort 时的默认 effort。"auto" 保持内置规则(PR 用 high,本地改动用 medium);配置值经过与 flag 相同的大小写不敏感校验归一化,非法值回落到内置默认。显式 --effort 始终最高优先级,且原有强制规则不变:生效的 --comment 仍强制 high(发布需要已验证的 review),生效的 --fix 仍下限 medium。裁决结果新增 effortSource: "configured" 取值,skill 文档同步说明。
  • review.comment(boolean,默认 false)——每个 PR /review 视为带了 --comment,无需每次输入 flag 即发布 findings。它替代的是授权门里的 flag,而不是目标绑定:写入仍然只授权给本次调用点名的 pull request(URL 目标还包括 repo 与 host),且授权成功的提示信息会说明是常驻配置而非用户输入的 flag 授权的发布。共享授权门的两个调用方(submitpublish-assets)以相同方式解析该配置,因此发布 review 的运行不会在证据图片上被拒绝。

所有默认值完全保持现状行为;IDE 的 settings.schema.json 已重新生成,用户文档与 bundled skill 文档同步更新(Step 7 授权门与所有"是否指定 comment"的分支统一以 comment.effective 为准)。

为什么需要

频繁 review PR 的用户目前每次都要重复输入 --comment--effort,且无法让 AI 署名 footer 匹配账号或仓库的发布预期。这些都是用户个人的决定——配置项把它们放到用户偏好该在的位置(操作者级配置),而不是要求每次运行时都带 flag。

评审者测试计划

如何验证

  1. 单元测试:cd packages/cli && npx vitest run src/commands/review src/config —— 全部通过;套件钉住了配置 effort 的优先级链(显式 > 配置 > 内置,且 --comment/--fix 的强制规则仍然生效)、effort 归一化("Low"low、非法值 → 内置默认)、常驻 comment 配置只在 PR 目标上生效、授权门接受该配置但向另一个 PR 提交时仍然拒绝、publish-assetssubmit 对授权依据保持一致、presubmit 能识别无署名的历史评论,以及三个配置项的 handler 级接线(parse-args、submit、compose-review)。
  2. 手工验证:在 ~/.qwen/settings.json 写入 {"review": {"attribution": false}},对着试验 PR 运行 /review <pr>,确认 review body 与 inline comment 均不带 via Qwen Code /review footer;不配置时 footer 照常出现。review.effort: "low"(裁决显示 effortSource: "configured")与 review.comment: true(无需 --comment 即可授权发布,提示信息点名该配置)同理。工作区 .qwen/settings.json 写任何这三个键都不生效(仅操作者作用域)。

证据(改动前后)

N/A —— 无 TUI 变化;可观察的效果是发布到 GitHub 的 review 内容,已由上述单元测试覆盖。

测试环境

操作系统 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

单元测试 + npm run dev 验证 handler 接线。

风险与范围

  • 主要风险或权衡:review.comment: true 会让每个 PR review 默认发布。这正是该开关的明确意图(用户主动开启的常驻授权);发布仍绑定调用点名的 PR,因此不会成为对其他 PR 的通行证。presubmit 对无署名评论的识别基于已认证账号自己的顶层评论——其他账号的无署名评论仍不可识别(无 footer、无作者信号),配置文档已注明。
  • 未验证 / 超出范围:以上行为均不适用于未受信任工作区(该处工作区配置本就不参与合并),且这三个键按构造完全排除工作区作用域(仅操作者作用域)。
  • 破坏性变更 / 迁移说明:无——所有默认值均复现当前行为。

关联 Issue

无。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 12, 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 Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Re-run at f77b448 — after eight autofix rounds, the maintainer's live A/B verification, and the round-7 review pass.

Template looks good ✓

Problem: unchanged from the first pass — this is a feature, not a fix, so no reproduction is required, and the friction it addresses is concrete: /review users who publish findings re-type --comment and --effort on every invocation, and the attribution footer is not configurable today.

Direction: aligned. Per-invocation defaults for a first-party skill belong in user settings, every default preserves current behavior, and the operator-scopes-only rule keeps repository-controlled content out of review policy. Claude Code's CHANGELOG confirms the pattern (/code-review with no effort level now reuses the level you typed last), so this is a recognized ergonomics area, not an invention.

Size: core paths touched (packages/cli/src/config/settingsSchema.ts; the bundled review skill doc). Breakdown: 421 production logic lines, 969 test lines, 26 generated-schema lines, 37 docs lines. The diff roughly doubled since the first pass — nearly all of the growth is tests added by the review rounds. Under the 500-line awareness threshold, and the author holds admin access, so the maintainer exemption applies regardless.

Approach: scope still feels right — three small settings under one review.* namespace, each mapping to an existing flag or behavior, with publish-assets deliberately kept on the same shared gate. One hygiene note: the diff also raises the repository-context array bound 128 → 256 (deliberate commit, with matching docs and tests), which the PR description never mentions — worth a sentence there, not blocking.

Risk: no elevated risk signals — none of the changed files match the revert-correlated high-risk paths.

Moving on to code review. 🔍

中文说明

f77b448 上重跑——此时已经历八轮 autofix、maintainer 的实测 A/B 验证,以及第 7 轮 review。

模板完整 ✓

问题:与首轮结论一致——这是 feature 而非 fix,无需复现,痛点具体:频繁用 /review 发布 findings 的用户每次都要重输 --comment--effort,且署名 footer 不可配置。

方向:对齐。一方 skill 的调用级默认值放在用户设置里合理,所有默认值保持现状行为,"仅操作者作用域"规则把仓库可控内容挡在 review 策略之外。Claude Code 的 CHANGELOG 印证了这一方向(/code-review 不带 effort 时复用上次输入的级别),属于已被认可的人体工学领域,而非新发明。

规模:触及核心路径(packages/cli/src/config/settingsSchema.ts、bundled review skill 文档)。拆分:421 行生产逻辑、969 行测试、26 行生成的 schema、37 行文档。diff 自首轮以来大约翻倍——增长几乎全部来自 review 各轮新增的测试。低于 500 行关注阈值,且作者有 admin 权限,维护者豁免本就适用。

方案:范围依然合理——review.* 命名空间下三个小配置,各自映射一个已有 flag 或行为,publish-assets 刻意复用同一授权门。一项卫生提示:diff 还把 repository-context 数组上限从 128 提到 256(有意提交,文档与测试同步更新),但 PR 描述只字未提——值得在描述里补一句,不构成阻塞。

风险:无升级风险信号——改动文件均未命中与 revert 相关的高风险路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Code review

My independent proposal — written from the title and motivation before reading the diff: a review settings category resolved from operator scopes only (a repository's .qwen/settings.json must not control the policy a review of that repository runs under), comment.requested kept as the flag-only signal while effective widens, an additive effortSource value, the authorization gate accepting the standing setting in place of the flag but never in place of the target binding, forged-footer stripping that survives attribution being off, and presubmit dedup that recognizes footer-less posts by author. The implementation matches this shape on every point; I did not find a simpler path it missed.

What I verified by reading the code at this head:

  • Scope isolation holds at every site. All four resolution points (parse-args, submit, compose-review, publish-assets) go through operatorReviewSettings(), which loads settings with skipWorkspaceSettings: true — and each test suite mocks a workspace-polluted view that a flag-less call would read, so dropping the flag reddens the suite rather than passing silently. Non-boolean values are re-checked at the boundary: a quoted "true" never enables auto-posting, and a quoted "false" never disables attribution.
  • The gate stays target-bound. review.comment replaces the flag, not the binding: the recorded arguments must still name the PR being written to (repo and host included for URL targets), and the test aiming a setting-authorised submission at feat(review): capture untracked files, resolve anchors from snippets, and gate posting in code #6771 with arguments naming feat(web-shell): show sub-agents as a chronological transcript with a parallel-agent timeline #6772 is refused with exit 3. The audit text names the actual authorising source — flag and setting stay distinguishable in the refusal record.
  • The earlier Criticals are genuinely closed. The configured effort now validates case-insensitively exactly like an explicit flag (typo warns and falls back, 'auto'/'Auto' map to the built-in rule without warning); the presubmit authorship fallback classifies through the shared trimmed severityOf predicate — replies excluded, case-insensitive login match, fail-closed when the current user is unknown, hand-written same-line notes kept out of the dedup set; and stripReviewFooter is one shared function bounded twice (marker guard skips the regex entirely on marker-less bodies; an 8192-char tail limit bounds the marker-present worst case the guard cannot help), with timing pins for both shapes.
  • Contracts preserved. comment.requested still means the flag was typed; effortSource: "configured" is additive; the --comment-forces-high and --fix-floors-medium rules still override the configured value, and their warnings name the setting — not a flag the operator never typed — when the forcing came from it.

No new critical blockers. The four round-7 Suggestions are still open at this head — I verified each against the code: the two refusal-advice misroutes (the missing-args-file refusal carries the `--comment` was substring and lands in the flag branch even when the setting is on; PR-less arguments with comment never requested get one futile flag-retry), the /settings dialog exposing the three keys under every scope (a Workspace-scope toggle persists into the repository's .qwen/settings.json, where loading ignores it — the security direction is safe, the cost is silent UX divergence plus an inert key in a committable file; the two pre-existing operator-only keys both hide from the dialog), and one test comment that overclaims what it pins. All four sit on guidance prose or dialog ergonomics — none touches the write path, which fails closed throughout — so they are non-blocking here; the autofix loop already has them in scope.

One scope note repeated from Stage 1: the repository-context array-bound raise (128 → 256) is a deliberate commit the description should name.

Files changed (24 of 24 shown)
File What changed
packages/cli/src/commands/review/lib/review-settings.ts New: operator-scope-only resolution of the three settings, with boundary re-validation
packages/cli/src/commands/review/lib/review-settings.test.ts Scope flag, defaults, non-boolean coercion, and dialog-membership pins
packages/cli/src/commands/review/lib/authorization.ts Gate accepts the standing setting; refusal wording splits on the real blocker
packages/cli/src/commands/review/lib/review-footer.ts Shared stripReviewFooter with marker guard plus 8192-char tail bound
packages/cli/src/commands/review/lib/review-footer.test.ts Strip correctness and timing pins for both quadratic shapes
packages/cli/src/commands/review/parse-args.ts Configured effort/comment defaults, validation, warning texts
packages/cli/src/commands/review/parse-args.test.ts Precedence chain, normalisation, forcings, handler wiring, scope guard
packages/cli/src/commands/review/submit.ts Attribution and setting threaded to gate, compose, and footer strip; advice branches
packages/cli/src/commands/review/submit.test.ts Setting-authorised posts, cross-PR refusal, advice classes, forged-footer strip
packages/cli/src/commands/review/compose-review.ts Attribution parameter gates footer and modelId validation
packages/cli/src/commands/review/compose-review.test.ts Attribution-off compose paths and handler wiring leg
packages/cli/src/commands/review/presubmit.ts Footer-less self-comment dedup via author plus finding shape
packages/cli/src/commands/review/presubmit.test.ts Six classification cases incl. replies, hand notes, leading whitespace
packages/cli/src/commands/review/publish-assets.ts Same standing-setting default through the shared gate
packages/cli/src/commands/review/publish-assets.test.ts Gate agreement with submit, handler wiring leg
packages/cli/src/commands/review/lib/repository-context.ts MAX_ARRAY_ITEMS raised 128 to 256 (not in the description)
packages/cli/src/commands/review/lib/repository-context.test.ts Bound pins moved to 256/257
packages/cli/src/commands/review/lib/manifest-repository-context.test.ts Bound pins moved to 256
packages/cli/src/config/settingsSchema.ts New review category with the three keys, operator-scope note in each description
packages/vscode-ide-companion/schemas/settings.schema.json Regenerated for the new keys
packages/core/src/skills/bundled/review/SKILL.md Verdict contract, Step 7 gate, and tips re-keyed to comment.effective
docs/users/configuration/settings.md The three keys documented with the operator-scopes-only rule
docs/users/features/code-review.md Attribution opt-out pointer
docs/design/review-repository-context.md Bound text updated to 256

Test evidence — the PR's own CI

All checks that run for this fork PR completed green on the reviewed commit; the macOS/Windows unit legs and the merge-queue-only integration suite are skipped for fork PRs by design. The suite genuinely pins this change: the new tests exercise the changed API surface directly (they would not compile without the diff), assert the gate's acceptance and cross-PR refusal, the effort precedence chain, the scope flag at every resolution site, and both quadratic-strip shapes with wall-time ceilings.

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
precheck-pr / precheck ✅ success
Classify PR ✅ success
route ✅ success
review-pr ✅ success
Remind on force-push ✅ success

The one reservation my first pass carried — that live posting was proven only by mocked-gh handler tests — has since been addressed from two directions. The maintainer's verification report (comment by @wenshao) drove real bundles against a disposable PR with real GitHub writes: effort precedence, the authorization refusals (including workspace-scope settings refused while the identical user-scope content authorizes), attribution-off posts byte-checked through the API with a forged footer stripped, and presubmit A/B showing footer-less posts recognized on head but blind on base — treat it as the author's own evidence, but it is detailed and internally consistent. Independently, a sandboxed @qwen-code /verify run on this head is in flight right now (run 31703683160) and will post its own report here when it completes. Not verified by this pass: a live gate-driven post of my own — static review only, per the triage rules.

中文说明

代码审查

读 diff 之前我先按标题与动机独立写了方案:一个仅从操作者作用域解析的 review 配置类别(仓库自己的 .qwen/settings.json 不能控制针对该仓库的 review 策略)、comment.requested 保持仅表示 flag 而 effective 扩展、effortSource 增量新值、授权门接受常驻配置替代 flag 但绝不替代目标绑定、attribution 关闭时伪造署名仍被剥离、以及 presubmit 去重能按作者识别无 footer 的帖子。实现在每一点上都与这个方案一致,我没有找到被它遗漏的更简路径。

在这个 head 上逐项核实:

  • 四处解析点的作用域隔离全部成立。 parse-argssubmitcompose-reviewpublish-assets 都经由 operatorReviewSettings(),它以 skipWorkspaceSettings: true 加载设置——且每个测试套件都 mock 了一份"未带 flag 的调用就会读到"的 workspace 污染视图,去掉 flag 会让套件变红而不是静默通过。边界上对非布尔值二次校验:字符串 "true" 永远不会开启自动发布,"false" 也永远不会关掉署名。
  • 授权门仍绑定目标。 review.comment 替代的是 flag 而不是绑定:记录的参数必须点名被写入的 PR(URL 目标还包括 repo 与 host),"设置授权但参数点名 feat(web-shell): show sub-agents as a chronological transcript with a parallel-agent timeline #6772、提交指向 feat(review): capture untracked files, resolve anchors from snippets, and gate posting in code #6771"的测试被拒(exit 3)。审计文案点名真实授权来源——flag 与配置在拒绝记录里始终可区分。
  • 此前的 Critical 确实已关闭。 配置 effort 现在与显式 flag 走同一套大小写不敏感校验(拼错会警告并回退,'auto'/'Auto' 无警告地映射到内置规则);presubmit 的作者回退分类走共享的、会 trim 的 severityOf 谓词——回复被排除、login 比较大小写不敏感、当前用户未知时失败关闭、同行手写备注不进 dedup 集合;stripReviewFooter 是单一共享函数、双重限界(marker 守卫让无 marker 正文根本不进正则;8192 字符尾部上限限界守卫救不了的含 marker 最坏情形),两种形状都有计时钉。
  • 契约保持。 comment.requested 仍表示输入了 flag;effortSource: "configured" 是增量值;--comment 强制 high、--fix 下限 medium 的规则仍覆盖配置值,且强制来自配置时警告点名配置项,而非用户从未输入的 flag。

没有新的关键阻塞项。第 7 轮的四个 Suggestion 在这个 head 上仍未处理——我逐一对照代码确认过:两处拒绝建议误路由(缺少参数文件的拒绝文案恰好包含 `--comment` was 子串,即使配置开启也落进 flag 分支;参数未点名 PR 且从未请求 comment 时会得到一次徒劳的 flag 重试);/settings 对话框在所有作用域下都展示这三个键(选 Workspace 作用域切换会写入仓库的 .qwen/settings.json,而加载时被忽略——安全方向无虞,代价是静默的 UX 偏差加一个可提交文件里的死键;既有的两个仅操作者键都不进对话框);以及一处测试注释夸大了其钉住的内容。四项都在引导文案或对话框人体工学层面——均不触及始终失败关闭的写入路径——在此不构成阻塞;autofix 循环已在处理范围内。

一点与 Stage 1 重复的范围提示:repository-context 数组上限提升(128 → 256)是有意提交,描述里应当点名。

测试证据 —— PR 自身的 CI

该 fork PR 实际运行的所有检查在被审 commit 上全绿;macOS/Windows 单测与仅 merge-queue 的集成套件按设计对 fork PR 跳过。套件确实钉住了本次改动:新测试直接调用被改的 API 面(没有 diff 连编译都过不了),断言了授权门的接受与跨 PR 拒绝、effort 优先级链、每个解析点的作用域 flag、以及两种二次方剥离形状的墙钟上限。

首轮遗留的唯一保留意见——实际发布只由 mock 了 gh 的 handler 测试证明——此后已从两个方向补上。maintainer 的验证报告(@wenshao 的评论)用真实 bundle 对靶场 PR 做了真实 GitHub 写入:effort 优先级、授权拒绝(包括 workspace 作用域设置被拒而同样内容的用户作用域授权)、attribution 关闭的帖子经 API 逐字节核对且伪造署名被剥离、以及 presubmit A/B 显示 head 能识别无 footer 帖子而 base 失明——按作者自证看待,但细节充分、内部自洽。独立地,针对这个 head 的沙箱 @qwen-code /verify 正在运行(run 31703683160),完成后会在此发布自己的报告。本轮未验证:我自己的 gate 驱动实发——按 triage 规则仅做静态审查。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — a correct, carefully gated feature whose write path is preserved, tested, and now live-verified; the remaining items are four non-blocking Suggestions the round-7 review already surfaced, not doubts about the change itself.

Stepping back: the implementation matches the proposal I wrote before reading the diff, and the eight review rounds have done what they are supposed to — every Critical from rounds 1, 4, and 6 is closed in code I re-verified at this head, and the test growth (158 → 969 lines) is almost entirely mutation-probed pins rather than coverage padding. The design decision that matters — a standing setting may replace the --comment flag in the authorization gate but never the target binding, and repository-controlled settings can never supply it — is implemented at one shared gate, asserted by tests that try exactly the adversarial shapes, and was exercised against real GitHub writes in the maintainer's A/B report. The sandboxed verify run in flight will add an independent read of the same claims.

What keeps this at 4 rather than 5, all non-blocking and already in the autofix loop's scope: the two refusal-advice classes that still misroute (guidance prose only — the gate fails closed and nothing posts wrongly), the /settings dialog exposing operator-only keys under the Workspace scope (they persist to a repo file that loading ignores; safe direction, real UX divergence), and one overclaiming test comment. I'd also like the description to name the repository-context bound raise. None of it warrants holding the PR.

Approving, pinned to the reviewed commit.

中文说明

回顾整体:实现与我读 diff 之前独立写出的方案一致,八轮 review 也起到了应有作用——第 1、4、6 轮的每个 Critical 都已在这个 head 的代码里关闭并被我重新核实,测试的增长(158 → 969 行)几乎全部是经过突变探针验证的钉子,而非覆盖率填充。最关键的设计决策——常驻配置可以在授权门里替代 --comment flag,但绝不能替代目标绑定,且仓库可控的设置永远无法提供授权——落在同一个共享门上,由恰好尝试对抗形状的测试断言,并在 maintainer 的 A/B 报告中经受了真实 GitHub 写入的检验。正在运行的沙箱 verify 会对同样的声称给出独立读数。

给 4 分而不是 5 分的原因,均不阻塞且已在 autofix 循环范围内:仍有两类拒绝建议被误路由(只是引导文案——门失败关闭,不会有错误发布);/settings 对话框在 Workspace 作用域下暴露仅操作者键(会写入加载时被忽略的仓库文件;安全方向无虞,UX 偏差真实存在);以及一处夸大其词的测试注释。另外希望 PR 描述点名 repository-context 上限提升。这些都不足以扣住这个 PR。

批准,钉在所审 commit 上。

Qwen Code · qwen3.8-max

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship — CI landed green after the review. ✅

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Downgraded from Request changes to Comment: self-PR; CI still running. Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: build-and-test — packages/cli and packages/core test commands did not complete successfully in this environment; Node 22 macOS/Windows checks are skipped in CI.

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

[Critical] R1-3 Attribution-off reviews still fail footer-only model ID validation (packages/cli/src/commands/review/compose-review.ts:1036). Failure scenario: when review.attribution is false and a custom model ID contains a newline or the literal footer marker, footer-only validation still throws and refuses submission even though no footer would be emitted. Fix: condition footer-safety and footer-specific nonempty validation on attribution being enabled.

中文说明

⚠️ 已从请求修改降级为评论:self-PR; CI still running。 仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — packages/cli and packages/core test commands did not complete successfully in this environment; Node 22 macOS/Windows checks are skipped in CI。

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

[Critical] R1-3 Attribution-off reviews still fail footer-only model ID validation (packages/cli/src/commands/review/compose-review.ts:1036). Failure scenario: when review.attribution is false and a custom model ID contains a newline or the literal footer marker, footer-only validation still throws and refuses submission even though no footer would be emitted. Fix: condition footer-safety and footer-specific nonempty validation on attribution being enabled.

— gpt-5.6-sol via Qwen Code /review (v0.21.10)

Comment thread packages/cli/src/commands/review/parse-args.ts Outdated
Comment thread packages/core/src/skills/bundled/review/SKILL.md
Comment thread packages/cli/src/commands/review/parse-args.ts
Comment thread packages/cli/src/commands/review/submit.ts Outdated

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

Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; PR #8994 adds three user settings to the /review skill — ...: none — all checks I named were completed within budget..

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

中文说明

未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;PR #8994 adds three user settings to the /review skill — ...:none — all checks I named were completed within budget.

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

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

Comment thread packages/cli/src/commands/review/lib/authorization.ts
Comment thread packages/cli/src/commands/review/parse-args.ts Outdated
Comment thread packages/cli/src/commands/review/submit.ts Outdated
Comment thread packages/cli/src/commands/review/compose-review.ts Outdated
Comment thread packages/cli/src/commands/review/parse-args.ts Outdated
Comment thread packages/cli/src/commands/review/compose-review.ts Outdated
@wenshao

wenshao commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 12, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

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

中文说明

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

… gaps (QwenLM#8994)

Address review feedback on the review settings:

- Resolve review.attribution/effort/comment from operator-controlled
  settings scopes only (system defaults, user, system); a repository's
  .qwen/settings.json is content under review and must not control
  whether findings publish, whether the review names its model, or how
  deeply the pipeline verifies.
- Normalize the configured review.effort through the same case-
  insensitive validation as the --effort flag, so "Low" cannot miss the
  exact comparisons the forcings run and invalid values cannot leak into
  the verdict.
- Gate the modelId requirement and footer-safety validation on
  attribution: with the footer gated off, the field has no consumer and
  must not refuse the run.
- Pass the standing review.comment setting into publish-assets' call of
  the shared authorisation gate, so both callers agree on what
  authorises a run.
- Make presubmit's self-comment detection footer-independent by also
  matching the reviewing account's own top-level comments, so
  attribution-off posts still dedup.
- Align SKILL.md's Step 7 gate and every --comment branch on
  comment.effective, and add handler-level wiring tests for all
  configured defaults.
@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 round — all 10 findings addressed

Commit: cf7fd06511 on review-settings. No base merge was needed (--conflict false).

Critical findings

  • R1-1 (rc:3765686194) — workspace settings must not control review policy: FIXED. All three keys (review.attribution, review.effort, review.comment) now resolve through a new shared helper (review-settings.ts) that loads settings with the workspace scope excluded — operator scopes only (system defaults → user → system), same relative precedence as the full merge. A repository-shipped .qwen/settings.json can no longer make reviews auto-publish, hide provenance, or lower the verification depth. All four call sites (parse-args, submit, compose-review, publish-assets) use it. Workspace-override regression tests added: every settings mock answers a flag-less loadSettings call with a workspace-polluted view (attribution: false, comment: true, effort: 'low'), so dropping the exclusion flag reddens the wiring tests in all four suites.
  • R1-2 (rc:3765686205) — SKILL.md configured-comment rule vs Step 7 gate: FIXED. The Step 7 gate now lists three authorisation sources (the flag, the standing review.comment setting, a typed publish request), and every --comment was specified branch (incremental-cache check ×2, follow-up tips ×2) now gates on comment.effective, naming both the flag and the setting. The verdict-field explanation also states the operator-scope trust boundary.
  • R1-3 (review body) / rc:3765918250 — attribution-off runs failed footer-only modelId validation: FIXED. The modelId requirement and footer-safety validation in compose-review.ts are gated on attribution — the footer is the field's only consumer, so with the switch off a missing or newline/footer-marker-carrying modelId no longer refuses the run. With attribution on, both refusals stand (both sides pinned by new tests).
  • rc:3765918201 — publish-assets gate disagreed with submit: FIXED. publish-assets resolves the same operator-scope review.comment and passes it as defaultComment into the shared reviewWriteAuthorization gate, so a run authorised to post the review is also authorised to publish its evidence images. The publish-assets skill rule now names the setting as an authorisation source. Covered by a direct gate test and a handler-wiring test.
  • rc:3765918204 — configured effort bypassed validation: FIXED. reviewDefaultsFromSettings normalizes the configured value through the same case-insensitive asEffort as the flag path ("Low"low, so the exact effort === 'low' comparisons fire) and discards invalid values to the built-in default instead of leaking them into the verdict. Handler tests pin the normalization and the discard.
  • rc:3765918227 — attribution-off blinded presubmit dedup: FIXED. Self-comment detection is now footer-independent: a top-level comment authored by the reviewing account (case-insensitive login match, the same account presubmit already resolves for self-PR detection) joins the footer match, so attribution-off posts from the same account still drive overlap/stale classification and blockOnExistingComments. Replies stay excluded — a hand reply is not a posted finding. The residual gap (footer-less posts from other accounts have no signal) is disclosed in the setting's description and the docs. Four new handler-level tests pin the classification.

Suggestions

  • R1-4 (rc:3765686210, rc:3765686217) — handler-level coverage for configured defaults: DONE. New handler-wiring tests drive parseArgsCommand.handler (configured effort, configured comment, case-variant normalization, invalid-value discard, workspace exclusion) and submitCommand.handler (the standing setting authorises a post without --comment; without flag or setting the handler refuses) against a configurable settings mock.
  • rc:3765918235 — compose handler wiring leg: DONE. A handler test runs composeReviewCommand with attribution: false and asserts the persisted body carries no footer — the exact mutation (argument deleted) the finding verified green-before is now red.
  • rc:3765918244 — resolution text contradicted the forcing warning: FIXED. The resolution now says "the review.comment setting forces high effort" when the forcing came from the setting, and a probe-shaped test ('6711 --effort bogus' with { comment: true, effort: 'medium' }) pins it.

Declined / escalated

None — every finding was verified against the code and addressed.

Conflict notes

None (--conflict false; no merge performed).

Verification

Commands actually run (all from the repository root unless noted):

  • npm run generate:settings-schema — passed; regenerated packages/vscode-ide-companion/schemas/settings.schema.json (settings source changed), diff is exactly the new attribution description.
  • npm run build — passed.
  • npm run typecheck — passed.
  • npx eslint on all changed files, then on packages/cli/src/commands/review/ + settingsSchema.ts — passed (one @typescript-eslint/array-type error in a new test fixed and re-run clean).
  • npx prettier --check on all changed files — passed.
  • cd packages/cli && npx vitest run src/commands/review/parse-args.test.ts src/commands/review/presubmit.test.ts src/commands/review/publish-assets.test.ts — 162 passed.
  • cd packages/cli && npx vitest run src/commands/review/submit.test.ts src/commands/review/compose-review.test.ts — 277 passed.
  • cd packages/cli && npx vitest run src/commands/review/ (full review suite) — 65 files, 2408 passed, 4 skipped, 0 failed.
  • cd packages/cli && npx vitest run src/config/ — first run showed 17 failures caused by this workflow's own environment leaking into the test processes (QWEN_HOME exported for the autofix session, plus SANDBOX/QWEN_CODE_CUSTOM_SANDBOX_IMAGE from running inside the workflow sandbox); re-run with env -u QWEN_HOME -u SANDBOX -u QWEN_CODE_CUSTOM_SANDBOX_IMAGE — 38 files, 1186 passed, 0 failed. The failures reproduce without this diff and are environment-only.
  • Integration tests after npm run bundle — not run: every touched behavior (settings resolution, authorisation gating, compose validation, presubmit filtering) is exercised directly by the unit and handler tests above, not only through the bundled CLI or integration harness.
中文说明

审查反馈轮次——全部 10 条 finding 已处理

提交:review-settings 分支上的 cf7fd06511。无需合并 base(--conflict false)。

Critical 类

  • R1-1(rc:3765686194)——工作区设置不得控制 review 策略:已修复。 三个键(review.attributionreview.effortreview.comment)现在统一经由新的共享 helper(review-settings.ts)解析,该 helper 加载设置时排除 workspace scope——仅保留操作者控制的 scope(system defaults → user → system),相对优先级与完整合并一致。仓库内置的 .qwen/settings.json 不再能让 review 自动发布、隐藏来源署名或降低验证深度。四个调用点(parse-args、submit、compose-review、publish-assets)全部改用它。已补充 workspace override 回归测试:每个 settings mock 对不带排除标志的 loadSettings 调用返回"被工作区污染"的视图(attribution: false, comment: true, effort: 'low'),因此任何一个调用点丢掉该标志都会让四个测试套件中的接线测试变红。
  • R1-2(rc:3765686205)——SKILL.md 配置化 comment 规则与 Step 7 授权门矛盾:已修复。 Step 7 授权门现在列出三个授权来源(flag、常驻 review.comment 设置、用户键入的发布请求),所有"是否指定 --comment"分支(增量缓存检查 ×2、后续提示 ×2)改为以 comment.effective 为门,并同时点名 flag 与设置两种来源。裁决字段说明处也补充了操作者 scope 的信任边界。
  • R1-3(正文 finding)/ rc:3765918250——关闭署名时仍因 footer 专用的 modelId 校验而失败:已修复。 compose-review.tsmodelId 的必填要求与 footer 安全校验现在用 attribution 门控——footer 是该字段唯一的消费方,开关关闭时 modelId 缺失或携带换行/footer 标记不再拒绝整个运行。开关开启时两种拒绝仍然成立(两侧均有新测试钉住)。
  • rc:3765918201——publish-assets 与 submit 的授权门不一致:已修复。 publish-assets 以相同的操作者 scope 解析 review.comment,并作为 defaultComment 传入共享的 reviewWriteAuthorization 门,因此被授权发布 review 的运行同样被授权发布其证据图片。publish-assets 的 skill 规则也已注明该设置是授权来源之一。配有直接的授权门测试与 handler 接线测试。
  • rc:3765918204——配置的 effort 绕过了校验:已修复。 reviewDefaultsFromSettings 对配置值走与 flag 路径相同的大小写不敏感 asEffort 归一化("Low"low,从而精确比较 effort === 'low' 能够触发),非法值回退到内置默认而不是泄漏进裁决 JSON。handler 测试钉住了归一化与丢弃行为。
  • rc:3765918227——关闭署名导致 presubmit 去重致盲:已修复。 自评论检测不再只依赖 footer:审查账号本人发表的顶层评论(大小写不敏感的 login 匹配,与 presubmit 自 PR 检测所用账号一致)与 footer 匹配一同纳入去重集合,因此同一账号关闭署名的历史帖子仍能参与 overlap/stale 分类并触发 blockOnExistingComments。回复评论仍被排除——手动回复不是已发布的 finding。残留缺口(其他账号无 footer 的帖子无任何信号可匹配)已在该设置的描述与文档中说明。新增四个 handler 级测试钉住分类行为。

Suggestion 类

  • R1-4(rc:3765686210、rc:3765686217)——为配置化默认值补充 handler 级覆盖:已完成。 新的接线测试通过可配置的 settings mock 驱动 parseArgsCommand.handler(配置 effort、配置 comment、大小写归一化、非法值丢弃、workspace 排除)与 submitCommand.handler(常驻设置可在无 --comment 时授权发布;无 flag 且无设置时 handler 拒绝发布)。
  • rc:3765918235——compose handler 接线腿:已完成。 新增 handler 测试以 attribution: false 运行 composeReviewCommand,断言持久化的 body 不含 footer——finding 中验证过"删除实参后测试仍全绿"的变异,现在会变红。
  • rc:3765918244——resolution 文案与强制告警自相矛盾:已修复。 当 high-effort 强制来自设置时,resolution 现在显示 "the review.comment setting forces high effort",并用 probe 形状的测试('6711 --effort bogus'{ comment: true, effort: 'medium' })钉住。

拒绝 / 升级

无——所有 finding 均对照代码核实并处理。

冲突说明

无(--conflict false,未执行合并)。

验证

实际执行的命令(除特别注明外均在仓库根目录):

  • npm run generate:settings-schema — 通过;重新生成 packages/vscode-ide-companion/schemas/settings.schema.json(设置源已变更),diff 恰好只有新的 attribution 描述。
  • npm run build — 通过。
  • npm run typecheck — 通过。
  • npx eslint 先针对所有变更文件、再针对 packages/cli/src/commands/review/ + settingsSchema.ts — 通过(新测试中一处 @typescript-eslint/array-type 错误已修复并复跑通过)。
  • npx prettier --check 针对所有变更文件 — 通过。
  • cd packages/cli && npx vitest run src/commands/review/parse-args.test.ts src/commands/review/presubmit.test.ts src/commands/review/publish-assets.test.ts — 162 通过。
  • cd packages/cli && npx vitest run src/commands/review/submit.test.ts src/commands/review/compose-review.test.ts — 277 通过。
  • cd packages/cli && npx vitest run src/commands/review/(review 全量套件)— 65 个文件,2408 通过,4 跳过,0 失败。
  • cd packages/cli && npx vitest run src/config/ — 首跑出现 17 个失败,原因是本工作流自身环境泄漏进测试进程(为 autofix 会话导出的 QWEN_HOME,以及运行在工作流沙箱内带来的 SANDBOX/QWEN_CODE_CUSTOM_SANDBOX_IMAGE);以 env -u QWEN_HOME -u SANDBOX -u QWEN_CODE_CUSTOM_SANDBOX_IMAGE 复跑 — 38 个文件,1186 通过,0 失败。这些失败在无本 diff 时同样复现,纯属环境问题。
  • npm run bundle 之后的集成测试 — 未执行:本次触及的所有行为(设置解析、授权门、compose 校验、presubmit 过滤)都由上述单元测试与 handler 测试直接覆盖,并非只能通过打包 CLI 或集成测试框架验证。

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

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

Reviewed. Suggestions are inline.

Not reviewed: verification and reverse audit — neither the verifier nor the reverse auditor was launched with a prompt this skill builds — the posted findings were ruled on, and the misses the rest of the review left were hunted, if at all, without the briefs this skill certifies against.

中文说明

已审查。 建议见行内评论。

未审查:验证与反向审计——验证 agent 与反向审计 agent 都没有用本 skill 构建的 prompt 启动——发布的发现即便被裁定过、评审其余部分遗漏的问题即便被搜寻过,也都缺失了本 skill 用以认证的 brief。

— deepseek-v4-flash via Qwen Code /review (v0.21.10)

Comment thread packages/cli/src/commands/review/lib/review-settings.ts Outdated
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review round summary — PR #8994

Feedback dispositions

[Suggestion] rc:3766623100 — untested ?? true fallback for attribution in review-settings.ts → Addressed (with one correction to the finding's premise).

Before acting, the failure scenario ("deleting it leaves every affected test green") was checked with a mutation test: changing ?? true to ?? false actually reddens two existing handler tests in compose-review.test.ts (reads --input, counts the drafted comments… and pins the persisted footer to the inherited startup version…), which run under the default empty-settings mock and assert the attribution footer is present. So the default is already pinned transitively.

The suggestion is still implemented because the module is new in this PR and is the only one under review/lib/ without a colocated test file. Added packages/cli/src/commands/review/lib/review-settings.test.ts (6 tests) covering:

  • the operator-scope resolution (loadSettings called with { skipWorkspaceSettings: true });
  • the defaults (attribution: true, comment: false, effort: undefined) both when the review section is absent and when it is an object missing the fields — the exact miss path the finding names;
  • explicit values passing through unchanged;
  • effort passing through raw ('Low' stays 'Low' — normalization is the caller's job, as the interface documents);
  • a non-string effort being dropped to undefined.

Both default mutations (attribution ?? false, comment ?? true) were verified to redden the new tests before reverting the temporary mutations. No production code changed this round.

Conflicts

None (--conflict false; no merge performed).

Verification

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • npx vitest run src/commands/review/lib/review-settings.test.ts (new, packages/cli) — 6 passed
  • npx vitest run over all six review test files touched/added by this PR (review-settings, compose-review, submit, parse-args, presubmit, publish-assets) — 6 files, 445 passed
  • npx prettier --check on the new file — passed
  • Mutation checks (temporary, reverted): attribution ?? false and comment ?? true each caught by the new unit tests; attribution ?? false also caught by the two pre-existing compose-review handler tests
  • Integration tests after npm run bundle — not run: test-only change, no behavior reachable through the bundled CLI changed
  • npm run generate:settings-schema — not needed: no settings source changed this round

Commit: 58455a7e54 test(cli): pin the review-settings operator defaults with unit tests (#8994) (test file only).

中文说明

评审轮次总结 — PR #8994

反馈处理

[Suggestion] rc:3766623100review-settings.tsattribution?? true 回退值缺少测试 → 已处理(对发现的前提做了一处更正)。

在动手之前,先用变异测试核查了其失败场景("删掉它也不会让任何受影响的测试变红"):把 ?? true 改成 ?? false 实际上会让 compose-review.test.ts 中两个既有的 handler 测试(reads --input, counts the drafted comments…pins the persisted footer to the inherited startup version…)变红——它们在默认的空配置 mock 下运行,并断言 attribution 页脚存在。也就是说,该默认值已经被间接钉住了。

建议仍然被采纳实现,因为该模块是本 PR 新增的,且是 review/lib/ 目录下唯一没有同目录测试文件的模块。新增 packages/cli/src/commands/review/lib/review-settings.test.ts(6 个测试),覆盖:

  • operator 作用域解析(loadSettings{ skipWorkspaceSettings: true } 被调用);
  • 默认值(attribution: truecomment: falseeffort: undefined),分别覆盖 review 配置段完全不存在、以及 review 对象缺少相应字段两种情形——正是该发现所指出的未覆盖路径;
  • 显式配置值原样透传;
  • effort 以原始值透传('Low' 保持 'Low'——按接口注释所述,归一化是调用方的职责);
  • 非字符串的 effort 被丢弃为 undefined

两个默认值变异(attribution ?? falsecomment ?? true)均已验证会让新测试变红,随后回退了临时变异。本轮未改动任何生产代码。

冲突

无(--conflict false;未执行合并)。

验证

  • npm run build — 通过(exit 0)
  • npm run typecheck — 通过(exit 0)
  • npm run lint — 通过(exit 0)
  • npx vitest run src/commands/review/lib/review-settings.test.ts(新增,packages/cli)— 6 通过
  • 对本 PR 涉及的全部六个评审测试文件(review-settingscompose-reviewsubmitparse-argspresubmitpublish-assets)执行 npx vitest run — 6 个文件,445 通过
  • 对新文件执行 npx prettier --check — 通过
  • 变异检查(临时、已回退):attribution ?? falsecomment ?? true 均被新单元测试捕获;attribution ?? false 同时被两个既有的 compose-review handler 测试捕获
  • npm run bundle 后的集成测试 — 未运行:本轮为纯测试改动,未改变任何可经由打包 CLI 触达的行为
  • npm run generate:settings-schema — 无需执行:本轮未改动任何 settings 源

提交:58455a7e54 test(cli): pin the review-settings operator defaults with unit tests (#8994)(仅测试文件)。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix round: no new changes — all feedback re-verified as already landed

This round's feedback contains no new findings. All eleven inline comments are confirmations of fixes that already landed in commits cf7fd06 and 58455a7, both of which are already on this PR's head (58455a7e). They were collected back from the PR thread as new feedback, so this round re-verified each one against the code instead of editing anything.

Per-item verification (all confirmed present at HEAD):

  • rc:3767393160review.* resolves via operatorReviewSettings(), which loads settings with skipWorkspaceSettings, so only operator scopes (system defaults → user → system) feed attribution/comment/effort; confirmed in lib/review-settings.ts, pinned by its scope test.
  • rc:3767393806 — SKILL.md's Step 7 gate lists the standing review.comment setting as an authorising source alongside the flag and the typed publish request; the Step 1 incremental-cache branch and the Step 6 follow-up tips all key off comment.effective.
  • rc:3767394430parse-args.test.ts has handler-level wiring tests with a configurable settings mock (configured effort and comment asserted through the real yargs handler, including the polluted-workspace negative leg).
  • rc:3767395138submit.test.ts covers submitCommand.handler: comment: true authorises a post with no --comment in the recorded args, and the negative leg proves resolution goes through skipWorkspaceSettings.
  • rc:3767396151publish-assets' handler resolves operatorReviewSettings().comment and passes it as defaultComment into the shared gate, so both reviewWriteAuthorization callers agree.
  • rc:3767396856 — the configured effort goes through asEffort (same case-insensitive validation as the flag): "Low" normalises so the --fix floor fires, invalid values fall back to the built-in default.
  • rc:3767398010 — presubmit's self-comment detection matches the footer first, then falls back to authorship for the reviewing account's own top-level comments (replies excluded); the remaining limitation is documented in the code and the setting description.
  • rc:3767398740compose-review.test.ts has the third wiring leg: handler test with attribution: false, asserting the persisted verdict carries no footer.
  • rc:3767399389 — the forced-by-comment resolution line branches on source ("--comment forces high effort" vs "the review.comment setting forces high effort"), so the two lines in the probe scenario no longer contradict.
  • rc:3767400138 — the modelId requirement and footer-safety validation are gated on attribution; an attribution-off run no longer fails over a field with no consumer (tests cover missing and footer-unsafe modelId under both switch positions).
  • rc:3767400788review-settings.test.ts pins the miss paths (no review section, review object missing fields → {attribution: true, comment: false, effort: undefined}), explicit pass-through, non-string effort drop, and the skipWorkspaceSettings call shape.

No code was modified this round; the working tree is clean and HEAD is unchanged. All eleven threads are marked resolved in the code per the re-verification below.

Verification

  • npx vitest run src/commands/review/lib/review-settings.test.ts src/commands/review/parse-args.test.ts src/commands/review/submit.test.ts src/commands/review/compose-review.test.ts src/commands/review/presubmit.test.ts src/commands/review/publish-assets.test.ts (in packages/cli) — 445 passed (6 files)
  • Code inspection of every claimed fix against git diff origin/main...HEAD at commit 58455a7e — all eleven confirmed landed
  • No build/typecheck/lint run this round: no source files were modified (the last pushed commit already passed the deterministic gate)
中文说明

Autofix 本轮:无新变更——所有反馈均已复核为已落地

本轮反馈不包含任何新发现。全部 11 条行内评论都是对已落地修复的确认,这些修复位于提交 cf7fd0658455a7 中,两者均已在本 PR 的 head(58455a7e)上。它们作为新反馈从 PR 线程中被重新收集回来,因此本轮逐条对照代码进行复核,而未做任何编辑。

逐条核验(均确认存在于 HEAD):

  • rc:3767393160review.* 经由 operatorReviewSettings() 解析,该函数以 skipWorkspaceSettings 加载设置,因此只有操作者作用域(系统默认 → 用户 → 系统)为 attribution/comment/effort 提供取值;已在 lib/review-settings.ts 中确认,并由其作用域测试钉住。
  • rc:3767393806 — SKILL.md 的 Step 7 授权门将常驻 review.comment 配置与 flag、用户显式发布请求并列为授权来源;Step 1 增量缓存分支与 Step 6 后续提示均以 comment.effective 为准。
  • rc:3767394430parse-args.test.ts 具有带可配置 settings mock 的 handler 级接线测试(配置的 effort 与 comment 经由真实 yargs handler 断言,包含污染工作区的反向用例)。
  • rc:3767395138submit.test.ts 覆盖 submitCommand.handlercomment: true 在记录参数没有 --comment 时授权发布;反向用例证明解析经由 skipWorkspaceSettings 进行。
  • rc:3767396151publish-assets 的 handler 解析 operatorReviewSettings().comment 并以 defaultComment 传入共享授权门,reviewWriteAuthorization 的两个调用方保持一致。
  • rc:3767396856 — 配置的 effort 经过 asEffort(与 flag 相同的大小写不敏感校验):"Low" 归一化后 --fix 下限得以命中,非法值回落内置默认。
  • rc:3767398010 — presubmit 的自有评论识别先匹配 footer,再对审查账号自己的顶层评论回退到作者识别(回复被排除);剩余限制已写入代码注释与配置说明。
  • rc:3767398740compose-review.test.ts 补上了第三条接线:settings mock 设为 attribution: false 的 handler 测试,断言持久化裁决不带 footer。
  • rc:3767399389 — forced-by-comment 的 resolution 文案按来源分支("--comment forces high effort" 与 "the review.comment setting forces high effort"),探测场景中的两行不再互相矛盾。
  • rc:3767400138 — modelId 必填校验与 footer 安全校验以 attribution 为前提;无署名运行不再为没有消费者的字段失败(测试覆盖开关两种状态下缺失与 footer 不安全的 modelId)。
  • rc:3767400788review-settings.test.ts 钉住缺失路径(完全没有 review 段、review 对象缺少字段 → {attribution: true, comment: false, effort: undefined})、显式值透传、非字符串 effort 丢弃、以及 skipWorkspaceSettings 调用形态。

本轮未修改任何代码;工作区干净,HEAD 未变。依据下方复核结果,全部 11 个线程均被标记为已在代码中解决。

验证

  • npx vitest run src/commands/review/lib/review-settings.test.ts src/commands/review/parse-args.test.ts src/commands/review/submit.test.ts src/commands/review/compose-review.test.ts src/commands/review/presubmit.test.ts src/commands/review/publish-assets.test.ts(位于 packages/cli)— 445 通过(6 个文件)
  • 在提交 58455a7e 处对照 git diff origin/main...HEAD 逐条检查每项声称的修复——11 条全部确认已落地
  • 本轮未运行 build/typecheck/lint:未修改任何源文件(最近一次推送的提交已通过确定性门禁)

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


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

@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. Suggestions are inline.

Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — I finished within budget. The one adjacent area I did not deep-walk is the SKILL.md consumption of presubmit's inflated buckets (chunk 4 territory); the …; You are review agent reverse-audit — Reverse audit agen...: none — all planned checks completed within budget..

中文说明

已审查。 建议见行内评论。

未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — I finished within budget. The one adjacent area I did not deep-walk is the SKILL.md consumption of presubmit's inflated buckets (chunk 4 territory); the …;You are review agent reverse-audit — Reverse audit agen...:none — all planned checks completed within budget.

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

Comment on lines +178 to +180
why: verdict.comment.requested
? `\`--comment\` was in the review arguments for #${authorisedPr}`
: `\`review.comment\` is enabled in settings, and the review arguments name #${authorisedPr}`,

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.

[Suggestion] The new setting-path why text is asserted by no test — the mutation that swaps the ternary's two branches survives the entire suite (mutation-verified: the whole review directory passes 2417 tests with the swap in place; a probe test flips red/green). — Failure scenario: why is user-visible audit text printed on success (Posted COMMENT to repo#PR — <why>), on refusal, and persisted in the JSON result (reason). With the branches swapped, a post authorised solely by the standing review.comment setting reports "--comment was in the review arguments" — attributing an irreversible public write to a flag the user never typed.

Suggested fix: in the existing setting-authorisation tests, also assert the why/reason text names review.comment, plus one assertion that the flag path still names --comment.

中文说明

问题: 新增的设置路径 why 文案没有任何测试断言——交换三元表达式两个分支的变异可以通过整个测试套件(已用变异验证:交换后整个 review 目录 2417 个测试全部通过;探针测试可使其红绿翻转)。

失败场景: why 是用户可见的审计文案:成功时打印(Posted COMMENT to repo#PR — <why>)、拒绝时打印,并持久化到 JSON 结果(reason)。若两个分支被交换,仅由常驻 review.comment 设置授权的发布会报告 "--comment was in the review arguments"——把一次不可逆的公开发布归因于用户从未输入过的 flag。

建议修复: 在现有的设置授权测试中,同时断言 why/reason 文案点名 review.comment,并保留一条断言 flag 路径仍点名 --comment

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

Comment on lines +156 to +159
body:
footer === undefined
? comment.body.replace(REVIEW_FOOTER_RE, '')
: `${comment.body.replace(REVIEW_FOOTER_RE, '')}\n\n${footer}`,

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.

[Suggestion] The new footer-less strip branch applies REVIEW_FOOTER_RE unguarded, while compose-review.ts's stripReviewFooter guards on FOOTER_MARKER because the regex's unanchored leading \s* scans quadratically on long whitespace runs in marker-less bodies. Probe-measured: 477 ms @ 30k chars, 1862 ms @ 60k, 7184 ms @ 120k (~4x per doubling); the marker-guarded replace ran in 0.01–0.05 ms. — Failure scenario: this diff newly routes modelId-absent bodies through the unguarded regex (the old code early-returned before any regex when modelId was absent). A submission whose inline comment bodies contain long whitespace runs and no footer marker makes qwen review submit stall seconds-to-minutes per comment before posting.

Suggested fix: guard on FOOTER_MARKER as compose-review.ts does, or export stripReviewFooter from review-footer.ts and share it:

const stripped = comment.body.includes(FOOTER_MARKER)
  ? comment.body.replace(REVIEW_FOOTER_RE, '')
  : comment.body;
中文说明

问题: 新增的无 footer 剥离分支无条件套用 REVIEW_FOOTER_RE,而 compose-review.tsstripReviewFooter 会先用 FOOTER_MARKER 守卫——因为该正则未锚定的前导 \s* 在无标记、含长空白串的正文上呈二次方扫描。探针实测:30k 字符 477 ms、60k 1862 ms、120k 7184 ms(每翻倍约 4 倍);加了 marker 守卫的替换仅 0.01–0.05 ms。

失败场景: 本 diff 新把 modelId 缺失的正文引入这个无守卫正则(旧代码在 modelId 缺失时会在任何正则之前提前返回)。一次行内评论正文含长空白串且无 footer 标记的提交,会让 qwen review submit 在发布前每条评论卡住数秒到数分钟。

建议修复:compose-review.ts 的方式用 FOOTER_MARKER 守卫,或从 review-footer.ts 导出 stripReviewFooter 供两处共用(代码块见英文部分)。

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


- **No designation, no publish** — unset or malformed `QWEN_REVIEW_ASSETS_REPO` is exit 3 and `{"published": false}`, not a fallback to some repo it picked. A refusal is a complete outcome: the findings keep their local `assetFiles` paths, which the terminal report and the saved report can still name.
- **Unauthorised run, no publish** — it reads the same verbatim args record `submit` reads, through the same shared gate (`lib/authorization.ts`), and refuses unless this run was authorised to post the review itself (an effective `--comment` naming this PR, or `--user-authorized` under Step 7's rules). A terminal-only review must not push the PR's behaviour to a public branch. Since an effective `--comment` forces high effort, low and medium runs can never publish — no separate rule needed.
- **Unauthorised run, no publish** — it reads the same verbatim args record `submit` reads, through the same shared gate (`lib/authorization.ts`), and refuses unless this run was authorised to post the review itself (an effective `--comment` naming this PR — typed as the flag or standing via the `review.comment` setting — or `--user-authorized` under Step 7's rules). A terminal-only review must not push the PR's behaviour to a public branch. Since an effective `--comment` forces high effort, low and medium runs can never publish — no separate rule needed.

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.

[Suggestion] The stated invariant "low and medium runs can never publish — no separate rule needed" rests on the setting's state at Step 1 equaling its state at write time, but the new authorisation source is re-resolved live at the write (submit.ts ~639, publish-assets.ts ~550), and neither write path checks effort. Probe-verified: with frozen args 42 --effort medium, defaultComment: true yields ok: true ("review.comment is enabled in settings…") while the pre-diff call shape refuses; the gate never reads the resolved effort. — Failure scenario: an operator runs /review 42 --effort medium with review.comment off (a medium run, which Step 7 says never posts); mid-session the operator enables review.comment: true in ~/.qwen/settings.json; on "post comments" the CLI gate re-resolves live → comment.effective=true, target binds PR #42 → authorised, and the medium review publishes, contradicting the invariant this same bullet states. (Consent is present — the operator flipped the setting; the harm is the quality-tier rule being bypassed.)

Suggested fix: snapshot the Step-1 review.comment value into the session-private record the gate trusts and read the snapshot at write time, or soften this bullet to state the stability assumption.

中文说明

问题: 此处声明的不变量"low 与 medium 运行永远无法发布——无需额外规则"依赖于 Step 1 时的设置状态等于写入时的状态,但新增的授权来源在写入时是实时重新解析的(submit.ts ~639、publish-assets.ts ~550),且两条写入路径都不检查 effort。已用探针验证:冻结参数为 42 --effort medium 时,defaultComment: true 返回 ok: true("review.comment is enabled in settings…"),而 diff 前的调用形态会拒绝;授权门从不读取已解析的 effort。

失败场景: 操作者在 review.comment 关闭时运行 /review 42 --effort medium(一次 medium 运行,Step 7 规定其永不发布);会话中途操作者在 ~/.qwen/settings.json 里开启 review.comment: true;随后 "post comments" 时 CLI 授权门实时重解析 → comment.effective=true、目标绑定 PR #42 → 授权通过,medium review 被发布,与同一条目自己声明的不变量矛盾。(同意是存在的——操作者自己打开了设置;危害是质量分层规则被绕过。)

建议修复: 在 Step 1 把 review.comment 的值快照进授权门信任的会话私有记录,写入时读快照;或把这一条目软化为明确说明该稳定性假设。

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

Comment on lines +639 to +642
const review = operatorReviewSettings();
runSubmit(argv as unknown as SubmitArgs, cliVersion, {
attribution: review.attribution,
defaultComment: review.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.

[Suggestion] This diff adds a second authorisation source to submit (the standing review.comment setting, wired in the anchored lines), but the same file's --skill-args help text (line 617) still states exclusively "Its --comment is what authorises a post" — now a false description of the gate. Probe-verified against the built CLI's qwen review submit --help: the setting is never named as an authorising source, while lib/authorization.ts's JSDoc was updated from "two" to "three things". — Failure scenario: an operator who enabled review.comment: true reads --help to understand why a run posted without --comment; the help names only the recorded --comment, so they either misdiagnose a sanctioned post as a gate bypass, or wrongly conclude posting without the flag is impossible.

Suggested fix: reword the describe at line 617 to "Its --comment — or the standing review.comment setting — is what authorises a post."

中文说明

问题: 本 diff 为 submit 新增了第二个授权来源(常驻 review.comment 设置,即锚定行处的接线),但同文件的 --skill-args 帮助文案(第 617 行)仍声称"Its --comment is what authorises a post"——现在这是对授权门的错误描述。已对构建后的 CLI 运行 qwen review submit --help 验证:帮助输出从未提及该设置是授权来源,而 lib/authorization.ts 的 JSDoc 已从"两件事"更新为"三件事"。

失败场景: 开启了 review.comment: true 的操作者通过 --help 排查为什么某次运行没有 --comment 也发布了;帮助只提到记录在案的 --comment,于是要么把一次合法授权的发布误诊为绕过授权门,要么错误地认为不带 flag 不可能发布。

建议修复: 把第 617 行的 describe 改为 "Its --comment — or the standing review.comment setting — is what authorises a post."

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix address-review summary — PR #8994 (round: feedback after 2026-08-12T14:37:18Z)

Commit: d2ca37159afix(cli): share the guarded footer strip and pin the gate audit text (#8994) (additive, on review-settings).

All four inline findings were resolved in the code.

Feedback points and decisions

  1. [Suggestion] rc:3768217103 — the setting-path why text is asserted by no test (lib/authorization.ts:180) → Resolved. Added a gate-level test in submit.test.ts that pins BOTH branches of the final ternary: a setting-authorised post's why must name `review.comment` is enabled in settings and the PR number, and a flag-authorised post's why must still name `--comment` was in the review arguments. Mutation-probed: swapping the two ternary branches turns the new test red (it previously survived the whole suite, as the finding reported).

  2. [Suggestion] rc:3768217117 — unguarded REVIEW_FOOTER_RE in submit's footer-less strip branch (submit.ts:159) → Resolved. Took the finding's second suggested fix: exported stripReviewFooter from lib/review-footer.ts (the module that already exists to state the footer once for both commands) and used it from both call sites. submit.ts's normalizeInlineComments now strips through the marker-guarded helper in both branches; compose-review.ts's local copy was deleted and replaced by the shared import. The guard is lossless — a body the regex can match must contain FOOTER_MARKER, so a marker-less body was never going to change. Reproduced the quadratic scan on this repo's Node (v22.23.2) before writing the regression test: bodies carrying the footer's _— opening but no full marker (e.g. a forged footer truncated mid-line) defeat V8's literal prefilter and scan ~4x per doubling (387 ms @ 30k chars), matching the finding's measurements. Added a regression test that posts a 500k-char body of exactly that shape through the attribution-off path; it passes in milliseconds with the guard and dies on the suite timeout without it (mutation-probed by removing the guard). Also added two unit tests for the shared helper in review-footer.test.ts.

  3. [Suggestion] rc:3768217125 — the "low and medium runs can never publish" invariant rests on setting stability (SKILL.md:1069) → Resolved via the finding's second suggested fix (soften the bullet to state the stability assumption). The bullet now says the forcing happens at Step 1's parse and names the one assumption it rests on: the gate re-resolves review.comment at write time, so an operator who enables it mid-session authorises the run in hand, and Step 7's effort rule — which declines low and medium runs independently of the gate — is what still holds the tier in that case. Why not the snapshot option: recording the Step-1 setting value into a gate-trusted session-private record would mean writing non-argument data into the verbatim args file (whose contract is "the argument string, byte for byte") or adding review-specific snapshot machinery to the generic skill loaders (SkillCommandLoader/BundledSkillLoader) plus a new trust rule in the gate — a multi-file design change in the security-critical path, out of scope for a Suggestion round, where the finding itself offers the wording fix as sufficient. The consent analysis in the finding (operator flipped the setting, so consent is present; only the tier rule is at stake) supports fixing the doc rather than changing the gate's live-resolution design. If a maintainer wants the tier enforced against a mid-session flip after all, that is a small follow-up design decision on top of this wording.

  4. [Suggestion] rc:3768217133 — --skill-args help text still names only --comment as authorising (submit.ts:642) → Resolved. Reworded the describe exactly as suggested: "Its --comment — or the standing review.comment setting — is what authorises a post." (--user-authorized keeps its own option describe; publish-assets's --skill-args describe makes no authorisation claim and needed no change.)

Conflict notes

--conflict false — no merge performed.

Verification

Commands actually run, in order (Node v22.23.2):

  • npx vitest run src/commands/review (packages/cli) — 67 files, 2423 passed | 4 skipped (before the new tests were added)
  • Mutation probe 1: swapped the gate's two why ternary branches, npx vitest run src/commands/review/submit.test.ts -t 'audit text' — 1 failed (mutation killed), then reverted
  • Mutation probe 2: removed the marker guard from stripReviewFooter, npx vitest run src/commands/review/submit.test.ts -t 'quadratically' — 1 failed on suite timeout (mutation killed), then reverted
  • Node timing probes reproducing the quadratic scan: _— x + 30k spaces = 381 ms, 60k = 1521 ms (~4x per doubling, matching the finding)
  • npx vitest run src/commands/review src/services/BundledSkillLoader.test.ts src/services/SkillCommandLoader.test.ts src/services/skill-args-file.test.ts (packages/cli) — 70 files, 2492 passed | 4 skipped
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run build — passed
  • npm run generate:settings-schema — not needed (no settings source changed this round)
  • Integration tests — not applicable: no integration test exercises review submit; the touched behavior is covered by the focused Vitest suites above
中文说明

Autofix address-review 摘要 — PR #8994(本轮反馈:2026-08-12T14:37:18Z 之后)

提交:d2ca37159afix(cli): share the guarded footer strip and pin the gate audit text (#8994)(在 review-settings 分支上的增量提交)。

四条行内发现全部已在代码中解决。

反馈点与处理决定

  1. [Suggestion] rc:3768217103 — 设置路径的 why 文案没有任何测试断言lib/authorization.ts:180)→ 已解决。submit.test.ts 中新增了一个授权门级别的测试,同时钉住最终三元表达式的两个分支:仅由设置授权的发布,其 why 必须点名 `review.comment` is enabled in settings 及 PR 编号;由 flag 授权的发布,其 why 仍必须点名 `--comment` was in the review arguments。已做变异探针验证:交换三元表达式两个分支会使新测试变红(此前该变异能通过整个套件,与发现所述一致)。

  2. [Suggestion] rc:3768217117 — submit 无 footer 剥离分支无守卫地套用 REVIEW_FOOTER_REsubmit.ts:159)→ 已解决。 采用了发现给出的第二种修复:从 lib/review-footer.ts(该模块存在的意义就是让 footer 在两个命令间"只声明一次")导出 stripReviewFooter,并在两处调用点共用。submit.tsnormalizeInlineComments 两个分支现在都走带 marker 守卫的共享函数;compose-review.ts 的本地副本被删除、改为导入共享实现。该守卫无损——正则能匹配到的正文必然包含 FOOTER_MARKER,因此无 marker 的正文本来就不会被改动。写回归测试前已在本仓库的 Node(v22.23.2)上复现了二次方扫描:含 footer 起始 _— 但不含完整 marker 的正文(例如中途截断的伪造 footer)会绕过 V8 的字面量预过滤,扫描耗时约每翻倍 4 倍(30k 字符 387 ms),与发现的测量一致。新增回归测试:通过 attribution 关闭路径发布一条 50 万字符、恰好是该形状的正文;有守卫时毫秒级通过,去掉守卫则撞上套件超时(已用变异探针验证:移除守卫后测试失败)。另在 review-footer.test.ts 为共享函数新增两个单元测试。

  3. [Suggestion] rc:3768217125 — "low 与 medium 运行永远无法发布"这一不变量依赖设置的稳定性SKILL.md:1069)→ 已按发现给出的第二种修复解决(把该条目软化为明确说明稳定性假设)。 该条目现在说明:effort 强制发生在 Step 1 的解析阶段,并点名它所依赖的唯一假设——授权门在写入时会实时重新解析 review.comment,因此操作者在会话中途开启该设置即授权了当前运行;而 Step 7 的 effort 规则(独立于授权门拒绝 low 与 medium 运行)在这种情况下仍然守住质量分层。为何不选快照方案:把 Step 1 的设置值写入授权门信任的会话私有记录,意味着要么向 verbatim args 文件(其契约是"逐字节的参数字符串")写入非参数数据,要么在通用 skill loader(SkillCommandLoader/BundledSkillLoader)里加入 review 专属的快照机制、并在授权门中新增一条信任规则——这是安全关键路径上的多文件设计变更,超出 Suggestion 轮次的范围,而且发现本身已明确文案修复即为充分。发现中的同意分析(操作者自己打开了设置,同意存在;受影响的只是质量分层规则)也支持修文档而非更改授权门的实时解析设计。如果维护者最终希望质量分层对会话中途的设置翻转也强制生效,可以在本文案之上作为一个小的后续设计决定来实现。

  4. [Suggestion] rc:3768217133 — --skill-args 帮助文案仍只把 --comment 说成授权来源submit.ts:642)→ 已解决。 按建议原文改写了 describe:"Its --comment — or the standing review.comment setting — is what authorises a post."(--user-authorized 有自己的选项描述;publish-assets--skill-args 描述未对授权来源做任何断言,无需改动。)

冲突说明

--conflict false — 未执行任何合并。

验证

实际执行的命令及结果(Node v22.23.2,按顺序):

  • npx vitest run src/commands/review(packages/cli)— 67 个文件,2423 通过 | 4 跳过(新增测试之前)
  • 变异探针 1:交换授权门 why 三元表达式的两个分支,npx vitest run src/commands/review/submit.test.ts -t 'audit text' — 1 个失败(变异被杀死),随后还原
  • 变异探针 2:移除 stripReviewFooter 的 marker 守卫,npx vitest run src/commands/review/submit.test.ts -t 'quadratically' — 1 个失败(套件超时,变异被杀死),随后还原
  • Node 计时探针复现二次方扫描:_— x + 30k 空格 = 381 ms,60k = 1521 ms(约每翻倍 4 倍,与发现一致)
  • npx vitest run src/commands/review src/services/BundledSkillLoader.test.ts src/services/SkillCommandLoader.test.ts src/services/skill-args-file.test.ts(packages/cli)— 70 个文件,2492 通过 | 4 跳过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npm run build — 通过
  • npm run generate:settings-schema — 无需执行(本轮未改动任何设置源)
  • 集成测试 — 不适用:没有集成测试覆盖 review submit;改动的行为已由上述聚焦 Vitest 套件覆盖

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

@QwenLM QwenLM deleted a comment Aug 12, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix review round — failed-check repair

Feedback point

  • Failed check: Test (ubuntu-latest, Node 22.x) — addressed.

No review comments, inline comments, or issue-level comments were in this round's feedback; the failed check was the only actionable item. No base conflict (--conflict false), so nothing was merged.

Root cause

The failing test is packages/cli/src/commands/review/lib/manifest-repository-context.committed.test.tscommitted review context manifest > stays under the resolved-file bound when every rule co-matches.

Evidence chain:

  1. This PR merged main at 17:39:39 UTC; one minute earlier main had merged feat(cli): add native multi-agent coordination #8804 (feat(cli): add native multi-agent coordination), which added packages/core/src/skills/bundled/coordinate/SKILL.md.
  2. That file is the 129th file resolved by the relatedPaths globs of .qwen/review-context.json (packages/core/src/config/** + packages/core/src/skills/** + the web-shell globs: 12 + 43 + 9 + 2 + 41 + 13 + 3 + 6 = 129). The shared wire bound MAX_ARRAY_ITEMS is 128, and the provider throws fail-closed at 129.
  3. The test file, the provider, the manifest, and the set of tracked files under every glob root are identical between origin/main and this PR head (verified with git diff and git ls-tree), and the count at feat(cli): add native multi-agent coordination #8804's parent was exactly 128 — so the failure reproduces identically on main; this PR's CI run simply carried the merged tree.

Decision and change

Re-calibrated the shared bound instead of shrinking the manifest globs: narrowing .qwen/review-context.json would silently reduce the related-file context every review receives (a policy judgment), while the bound exists to keep payloads bounded and fail-closed, and the provider's own calibration comment says the caps are sized so an honestly scoped manifest never fails a review. The repository honestly grew to 129, so the bound moves to 256 (still bounded/fail-closed; the tripwire test keeps firing if growth ever outgrows the new bound).

Changes (one commit, fix(cli): raise the repository-context array bound to 256 (#8994)):

  • packages/cli/src/commands/review/lib/repository-context.tsMAX_ARRAY_ITEMS 128 → 256.
  • packages/cli/src/commands/review/lib/repository-context.test.ts, manifest-repository-context.test.ts — every hardcoded contract pin re-pinned to the new bound (accept exactly at 256, reject at 257; merged-field, glob-list, resolved-file, static-branch, and dedup-before-cap pins all preserved at full strength, including the dedup pin which now uses three shared patterns so a cap-before-dedup regression still throws).
  • docs/design/review-repository-context.md — the two documented "128 resolved files" references updated to 256.

Also investigated and ruled out as local-environment artifacts (not CI failures): run-qwen-serve-live.test.ts needs packages/web-shell/dist, which CI's prepare builds during npm ci but this agent checkout lacked; the timing-sensitive AuthDialog TUI tests are skipped whenever CI=true; remaining local failures traced to the agent runner's own sandbox environment variables and disappear in a clean CI-equivalent environment. In a full clean-environment run, the single failing test was exactly the manifest tripwire above.

Verification

Commands actually run (trusted project commands), on the final tree:

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • Vitest (packages/cli, touched package):
    • focused: manifest-repository-context.committed.test.ts + manifest-repository-context.test.ts + repository-context.test.ts — 3 files passed, 77 tests passed (the previously failing tripwire now passes)
    • full npm run test:ci for packages/cli in a CI-equivalent environment (CI=true, fresh HOME, auth/sandbox env cleared) — 803/803 test files passed, 19327 passed | 25 skipped
  • Integration tests: not run — the changed behavior (the review repository-context wire bound) is fully exercised by the unit tests above; no integration test references repository context, and the no-AK gate suite does not cover it.
  • Settings schema: no settings source changed, so npm run generate:settings-schema was not needed (the pre-change regeneration check showed the committed schema already up-to-date).

Environment-specific note: the exact GitHub-hosted runner cannot be reproduced here; verification above re-runs the Test job's runnable steps locally, including the full unit suite under the CI job's environment variables.

中文说明

Autofix 审查轮次 — 失败检查修复

反馈点

  • 失败检查:Test (ubuntu-latest, Node 22.x) — 已处理。

本轮反馈中没有审查评论、行内评论或议题级评论;失败检查是唯一可操作的项目。无基线冲突(--conflict false),因此未进行任何合并。

根因

失败的测试是 packages/cli/src/commands/review/lib/manifest-repository-context.committed.test.tscommitted review context manifest > stays under the resolved-file bound when every rule co-matches

证据链:

  1. 本 PR 于 17:39:39 UTC 合并了 main;一分钟前 main 刚合并了 feat(cli): add native multi-agent coordination #8804feat(cli): add native multi-agent coordination),该提交新增了 packages/core/src/skills/bundled/coordinate/SKILL.md
  2. 该文件是 .qwen/review-context.json 的各 relatedPaths glob 解析出的第 129 个文件(packages/core/src/config/** + packages/core/src/skills/** + web-shell 各 glob:12 + 43 + 9 + 2 + 41 + 13 + 3 + 6 = 129)。共享线格式上限 MAX_ARRAY_ITEMS 为 128,provider 在第 129 个时按 fail-closed 原则抛出。
  3. 测试文件、provider、manifest 以及每个 glob 根目录下的被跟踪文件集合,在 origin/main 与本 PR head 之间完全一致(已用 git diffgit ls-tree 验证),且 feat(cli): add native multi-agent coordination #8804 父提交处的计数恰好为 128 —— 因此该失败在 main 上同样复现;本 PR 的 CI 运行只是带上了合并后的树。

决策与改动

选择重新校准共享上限,而不是收缩 manifest 的 glob:收缩 .qwen/review-context.json 会悄悄减少每次审查获得的相关文件上下文(属于策略判断),而该上限的存在是为了让载荷保持有界并 fail-closed,且 provider 自身的校准注释写明这些上限的取值应让诚实范围的 manifest 永不导致审查失败。仓库诚实增长到了 129,因此上限调整为 256(仍然有界 / fail-closed;若未来增长再次超出新上限,绊线测试仍会触发)。

改动(单个提交 fix(cli): raise the repository-context array bound to 256 (#8994)):

  • packages/cli/src/commands/review/lib/repository-context.tsMAX_ARRAY_ITEMS 128 → 256。
  • packages/cli/src/commands/review/lib/repository-context.test.tsmanifest-repository-context.test.ts — 所有硬编码的契约钉扎全部重新钉到新上限(恰好 256 接受、257 拒绝;合并字段、glob 列表、解析文件数、静态分支、去重先于上限等钉扎全部保持原有强度,其中去重钉扎改用三个共享 pattern,保证"上限先于去重"的回归仍会抛错)。
  • docs/design/review-repository-context.md — 文档中两处 "128 resolved files" 引用更新为 256。

另经排查并排除为本地环境因素(非 CI 失败):run-qwen-serve-live.test.ts 需要 packages/web-shell/dist,CI 的 prepare 会在 npm ci 期间构建出来,而本 agent 检出缺少该产物;对时序敏感的 AuthDialog TUI 测试在 CI=true 时一律跳过;其余本地失败均可追溯到 agent 运行器自身的沙箱环境变量,在干净的 CI 等价环境中消失。在完整的干净环境运行中,唯一失败的测试恰好就是上述 manifest 绊线测试。

验证

实际运行的命令(项目可信命令),针对最终代码树:

  • npm run build — 通过(exit 0)
  • npm run typecheck — 通过(exit 0)
  • npm run lint — 通过(exit 0)
  • Vitest(packages/cli,被改动的包):
    • 聚焦:manifest-repository-context.committed.test.ts + manifest-repository-context.test.ts + repository-context.test.ts — 3 个文件通过,77 个测试通过(原先失败的绊线测试现已通过)
    • 在 CI 等价环境(CI=true、全新 HOME、清空鉴权/沙箱环境变量)下完整运行 packages/clinpm run test:ci803/803 个测试文件通过,19327 通过 | 25 跳过
  • 集成测试:未运行 — 被改动的行为(review repository-context 线格式上限)已完全由上述单元测试覆盖;没有任何集成测试引用 repository context,no-AK 门禁套件也不覆盖它。
  • Settings schema:未改动任何 settings 源,因此无需运行 npm run generate:settings-schema(改动前的重新生成检查显示已提交的 schema 本来就是最新的)。

环境特异性说明:此处无法完全复刻 GitHub 托管运行器;以上验证在本地重新运行了 Test 作业中可运行的各步骤,包括在 CI 作业的环境变量下运行完整单元测试套件。

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

wenshao added a commit to wenshao/qwen-code that referenced this pull request Aug 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Aug 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Aug 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Aug 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Aug 13, 2026
@wenshao

wenshao commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Maintainer verification report — real bundles, real GitHub writes

Verdict: every testable claim in the description holds end-to-end — with one inaccuracy: the three settings are NOT in the /settings dialog (all three ship showInDialog: false), contrary to "All three are toggleable in the /settings dialog". Everything else verified. Details below.

Environment

  • Head aa42fc7e in an isolated worktree, npm run build && npm run bundle → real dist/cli.js (v0.21.10).
  • Base = merge-base 4a281f2e (current origin/main), built identically, for before/after A/B.
  • Behavior driven through the real bundle with isolated QWEN_HOME dirs per scenario; the write path exercised with real gh against a disposable target PR (wenshao/qwen-code#6, now closed) whose posted reviews are the artifacts screenshotted below.

Unit tests

npx vitest run src/commands/review src/config src/commands/review.test.ts (the last one pins the qwen review subcommand roster and is easy to miss with directory globs): 3663 passed / 1 failed across 107 files. The one failure — stale-bundle.test.ts › "the SKILL.md quotes and the live notices are prefixes of each other" (expects 6 fixture notices, got 5) — reproduces identically on the base commit in the same environment, so it is a pre-existing local-macOS issue, not this PR; the PR's ubuntu CI Test leg (the only leg that runs tests) is green.

review.effort — real bundle, isolated QWEN_HOME

Scenario (review parse-args "8994") Head result Base result
{"review":{"effort":"Low"}} (case test) low / effortSource: "configured" high / "default" (setting ignored)
same + explicit --effort medium medium / "explicit" (flag wins)
effort: "low" + comment: true, PR target high / "forced-by-comment", warning names review.comment the setting, not a flag
effort: "low" + comment: true, local target low / "configured" (forcing only when comment is effective)
{"review":{"effort":"turbo"}} warns Invalid review.effort value "turbo" in settings; using the default effort. and falls back

review.comment — the authorization gate, live

Refusals (real submit, exit 3, nothing posted):

  • No flag, no setting → REFUSED … `--comment` was not in the review arguments.
  • Setting on but recorded args name PR 9999, submission targets OpenAI API Error: 401 Incorecct API Key provided #6the review arguments authorise pull request #9999, but this submission targets #6 — the setting replaces the flag, not the target binding.
  • Setting written only in the workspace .qwen/settings.json → refused exactly like no setting (operator scopes only); the identical content in the user scope authorizes (control).

Authorized post with no --comment typed: S3 below landed, and the gate's message names the setting — Authorised (`review.comment` is enabled in settings, and the review arguments name #6). On the base bundle the identical invocation is refused.

publish-assets agrees with submit on the same gate: without the setting it refused to publish evidence images for this PR; with it, it published the 5 screenshots this very comment embeds (pr-assets/8994-review @ 38a9d5bc) — so a posting run cannot be refused its evidence.

review.attribution — live posts, byte-checked via the API

  • S1, default (on): review body and inline comment both carry _— qwen3-coder-verify via Qwen Code /review (v0.21.10)_.
  • S2, off: neither carries a footer, and a forged footer planted in the drafted comment was stripped from the posted result.
  • modelId consequence: with attribution off, a payload without state.modelId passes consistency (--dry-run: would post); with attribution on the same payload is refused with compose-review: modelId is required.

S1 (footer on):

S1 attribution on

S2 (footer off, forged footer stripped — body and inline both clean):

S2 attribution off

S3 (posted with no --comment flag, standing setting authorised it):

S3 standing comment

Presubmit stays sighted without the footer — A/B

Live review presubmit against the target PR carrying S2's footer-less comment:

  • Head: existingComments.total: 3 — the footer-less post is recognized (author + finding-shape via severityOf).
  • Base: existingComments.total: 2 — the footer-regex-only filter is blind to it. This is precisely the dedup blindness the PR closes.

Schema and docs

  • packages/vscode-ide-companion/schemas/settings.schema.json: re-ran scripts/generate-settings-schema.ts on head → zero diff (regeneration is faithful).
  • User docs and bundled SKILL.md describe the three keys and the operator-scopes-only rule accurately.

Finding — the /settings dialog claim

The PR description says all three settings are toggleable in the /settings dialog, but all three schema entries set showInDialog: false, and the dialog lists only showInDialog === true keys (getDialogSettingKeys(), packages/cli/src/utils/settingsUtils.ts). Confirmed live on the head TUI — searching the dialog for "review" and "attribution" surfaces only pre-existing entries:

settings dialog search review

The user docs make no dialog claim, so this is description-level only — either flip the three to showInDialog: true (they seem like reasonable dialog citizens) or amend the description before merge.

中文版本(完整对照)

维护者验证报告 —— 真实构建、真实 GitHub 写入

结论:描述中所有可实测的声称端到端全部成立——只有一处不实:三个设置项均不在 /settings 对话框中(三项 schema 全部 showInDialog: false),与"三项均可在 /settings 对话框中切换"矛盾。其余全部通过。

环境

  • Head aa42fc7e 独立 worktree 检出,npm run build && npm run bundle 产出真实 dist/cli.js(v0.21.10)。
  • Base = merge-base 4a281f2e(即当前 origin/main),同样方式构建,用于前后 A/B 对照。
  • 每个场景使用独立 QWEN_HOME;写入路径用真实 gh 对一次性靶场 PR(wenshao/qwen-code#6,已关闭)实际发布,上文截图即其产物。

单元测试

npx vitest run src/commands/review src/config src/commands/review.test.ts(最后一个钉住 qwen review 子命令清单,目录 glob 容易漏):107 个文件 3663 通过 / 1 失败。唯一失败 stale-bundle.test.ts(SKILL.md 引文与通知行数 5≠6)在同环境的 base 提交上逐字节复现——本机 macOS 既有问题,与本 PR 无关;PR 的 ubuntu CI 测试腿(唯一实跑测试的腿)是绿的。

review.effort

场景(review parse-args "8994" Head Base
{"review":{"effort":"Low"}}(大小写) low / "configured" high / "default"(设置被无视)
同上 + 显式 --effort medium medium / "explicit"(flag 优先)
effort: "low" + comment: true,PR 目标 high / "forced-by-comment",警告点名配置项而非 flag
同上,本地目标 low / "configured"(仅 comment 生效时才强制)
{"review":{"effort":"turbo"}} 警告并回落内置默认

review.comment —— 授权门实测

拒绝路径(真实 submit,exit 3,未发布):

  • 无 flag 无设置 → REFUSED … `--comment` was not in the review arguments
  • 设置开启但记录参数指向 PR 9999、提交目标 OpenAI API Error: 401 Incorecct API Key provided #6 → 拒绝并点名两个 PR 号——设置替代的是 flag,不是目标绑定。
  • 设置只写在工作区 .qwen/settings.json → 与无设置完全一样地拒绝(仅操作者作用域);同样内容写用户作用域则授权(对照组)。

未输入 --comment 的授权发布:下方 S3 成功落地,授权信息点名配置项——Authorised (`review.comment` is enabled in settings, and the review arguments name #6)。同样的调用在 base bundle 上被拒绝。

publish-assetssubmit 共享同一授权门:无设置时拒绝为本 PR 发布证据图;有设置时成功发布本评论内嵌的 5 张截图(pr-assets/8994-review @ 38a9d5bc)——发布 review 的运行不会在证据图上被拒。

review.attribution —— 真实发布、API 逐字节核对

  • S1 默认(开):review 正文与 inline 评论均带 _— qwen3-coder-verify via Qwen Code /review (v0.21.10)_
  • S2 关闭:两处均无 footer,且草稿中伪造的 footer 被剥离后才发布。
  • modelId 联动:attribution 关闭时无 state.modelId 的 payload 通过一致性检查(--dry-run 判定可发布);开启时同一 payload 被拒并报 compose-review: modelId is required

(截图见上文英文部分 S1/S2/S3。)

presubmit 无 footer 仍可见 —— A/B

对携带 S2 无 footer 评论的靶场 PR 实跑 review presubmit

  • HeadexistingComments.total: 3 —— 无 footer 的帖子按作者 + finding 形态(severityOf)被识别。
  • BaseexistingComments.total: 2 —— 仅按 footer 正则过滤,对其失明。这正是本 PR 修掉的 dedup 盲区。

Schema 与文档

  • settings.schema.json:head 上重跑 scripts/generate-settings-schema.ts零 diff
  • 用户文档与内置 SKILL.md 对三个键及"仅操作者作用域"规则的描述准确。

Finding —— /settings 对话框声称

PR 描述称三项可在 /settings 对话框切换,但三项 schema 均为 showInDialog: false,而对话框只列出 showInDialog === true 的键(getDialogSettingKeys()packages/cli/src/utils/settingsUtils.ts)。已在 head TUI 实测确认:对话框内搜索 "review" 与 "attribution" 只出现旧条目(截图见上)。用户文档没有该声称,属描述层问题——建议合并前要么把三项改为 showInDialog: true(放进对话框也合理),要么修订描述。

@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. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): PR #8994 adds three operator-scope-only review settings t...: none — all planned checks completed within budget.; PR #8994 adds three operator-scope-only review settings t...: none — the chunk and all implementation cross-checks above completed within budget.; PR #8994 adds three operator-scope-only review settings t...: none — all checks I started were completed within budget.; PR #8994 adds three operator-scope-only review settings t...: none — all checks above completed within budget.; PR #8994 adds three operator-scope-only review settings t...: none — all checks above were completed within budget..

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

中文说明

已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):PR #8994 adds three operator-scope-only review settings t...:none — all planned checks completed within budget.;PR #8994 adds three operator-scope-only review settings t...:none — the chunk and all implementation cross-checks above completed within budget.;PR #8994 adds three operator-scope-only review settings t...:none — all checks I started were completed within budget.;PR #8994 adds three operator-scope-only review settings t...:none — all checks above completed within budget.;PR #8994 adds three operator-scope-only review settings t...:none — all checks above were completed within budget.

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

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

Comment on lines +60 to +64
export function stripReviewFooter(body: string): string {
return body.includes(FOOTER_MARKER)
? body.replace(REVIEW_FOOTER_RE, '')
: body;
}

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.

[Suggestion] The marker guard only skips the regex for marker-less bodies; a body that quotes the full footer shape (_— + via Qwen Code /review) mid-text with no trailing footer still runs the start-unanchored \s*-leading regex — probe-measured quadratic: 169 ms / 674 ms / 2781 ms at 20k / 40k / 80k chars when a long whitespace run follows the quoted footer (~4× per doubling). The added perf test pins only the marker-absent case. The shape pre-existed at both old call sites, so this PR narrows the surface — the finding stands on the new shared function and its linearity-contract docstring. — Failure scenario: a model-drafted comment or bodyCriticals entry quotes the literal footer (findings about this dogfooded subsystem do — the repo's own tests and PR comments carry _— … via Qwen Code /review strings) and contains a long contiguous whitespace run with no footer at the end → includes(FOOTER_MARKER) passes, REVIEW_FOOTER_RE scans unanchored, and the compose/submit step stalls (≈1.8 s per comment at GitHub's ~65k inline-comment cap; the doc comment's own measured shape was ~20 s at 80k).

Suggested fix: narrow the guard's remainder — only run the regex when the footer marker appears in the body's trimmed tail (a strippable trailing footer's marker sits near the end) — or strip via a linear backward walk over footer-shaped lines. A probe applying the tail-window fix took the pathological shape from ~2.8 s to <0.1 ms while every correctness control stayed green.

中文说明

问题: marker 守卫只对不含 marker 的 body 跳过正则;body 在中段引用完整 footer 形态(_— + via Qwen Code /review)且没有尾部 footer 时,仍会执行开头未锚定的 \s* 正则——probe 实测二次方增长:引用 footer 后跟长空白段时,20k / 40k / 80k 字符分别耗时 169 ms / 674 ms / 2781 ms(每翻倍约 4 倍)。新增的性能测试只钉住了无 marker 的场景。该形态在两个旧调用点本就存在,本 PR 收窄了暴露面——此发现针对新的共享函数及其线性契约 docstring。

失败场景: 模型起草的评论或 bodyCriticals 条目在中段引用字面 footer(对这个自举子系统的发现确实会这样做——仓库自己的测试与 PR 评论中都带 _— … via Qwen Code /review 字符串),且包含一段长连续空白、结尾没有 footer → includes(FOOTER_MARKER) 通过,REVIEW_FOOTER_RE 无锚点扫描,compose/submit 步骤因此停滞(按 GitHub 约 65k 的行内评论上限估算约每条评论 1.8 秒;doc 注释自己记录的形态是 80k 时约 20 秒)。

建议修复: 收窄守卫的判定范围——仅当 footer marker 出现在 body 的修剪尾部时才跑正则(可剥离的尾部 footer 的 marker 必然靠近结尾)——或改为对 footer 形态的行做线性向后遍历剥离。probe 验证尾部窗口修复可将病态形态从约 2.8 s 降至 0.1 ms 以下,且所有正确性对照仍为绿。

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

}

const verdict = parseReviewArgs(raw);
const verdict = parseReviewArgs(raw, { comment: req.defaultComment });

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.

[Suggestion] The missing-args-file catch branch immediately above this line (unchanged by the diff) still says the run "cannot show that --comment was requested" and never names the standing review.comment setting, which this PR makes an equal authorising source. The adjacent branch was rewritten for exactly this misdirection (R3-2), but this sibling kept the old single-source wording; it is also unpinned by any test (the gate tests go through the authFor helper, which always writes the args file). The refusal behaviour itself is correct fail-closed — only the diagnostic is stale. — Failure scenario: operator enables review.comment: true and runs /review 123; the session's args record is absent (never written, or a session-id/path mismatch). The gate correctly refuses — but the persisted refusal claims the run failed to show "--comment was requested", when the setting requested posting and the real missing element is any recorded PR target to bind the write to; the operator hunts for a flag they never typed.

Suggested fix: in the catch branch above, when req.defaultComment === true, refuse with text like "no review arguments were recorded at , so there is no pull request for the standing review.comment setting to bind to" (keep the existing wording when the setting is off).

中文说明

问题: 此行上方紧邻的 missing-args-file catch 分支(未被本 diff 修改)仍然声称该运行"无法证明 --comment 被请求过",从未提及本 PR 使其成为同等授权来源的常驻 review.comment 配置。相邻分支正是因为同样的误导被重写(R3-2),但这个兄弟分支保留了旧的单来源措辞;它也没有任何测试钉住(授权门测试都经由 authFor 辅助函数,后者总会写入 args 文件)。拒绝行为本身是正确的 fail-closed——只是诊断信息过时了。

失败场景: 运维者开启 review.comment: true 后运行 /review 123;会话 args 记录缺失(从未写入,或 session-id/路径不匹配)。授权门正确拒绝——但持久化的拒绝信息声称该运行无法证明"--comment 被请求过",而实际上是配置请求了发布、真正缺失的是可供绑定写入的记录 PR 目标;运维者会去寻找一个从未输入过的 flag。

建议修复: 在 catch 分支中,当 req.defaultComment === true 时,以类似"未在 记录到任何 review 参数,因此常驻 review.comment 配置没有可绑定的 pull request"的措辞拒绝(配置关闭时保留现有措辞)。

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

// so both read the same constants instead of keeping lockstep copies that can
// drift.
export const MAX_ARRAY_ITEMS = 128;
export const MAX_ARRAY_ITEMS = 256;

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.

[Suggestion] The 128→256 bound change left two stale bounds citations where the array cap is MAX_ARRAY_ITEMS (now 256) × MAX_NOTE_LENGTH (512): (1) packages/cli/src/commands/review/compose-review.ts:1866-1869 — the MAX_DIMENSIONS rationale comment still says "at the schema bounds (128 x 512 chars)"; (2) packages/cli/src/commands/review/compose-review.test.ts:720 — "The schema admits 128 dimensions x 512 chars". Every other 128 citation was re-pinned in lockstep (design doc, both bound-test files, even the dedup-test prose); these two survivors were found independently by three auditors and the verifier. Behaviour is unaffected (slice(0, MAX_DIMENSIONS) caps output either way) — this is a lockstep-maintenance miss. — Failure scenario: a maintainer sizing the worst-case disclosure paragraph from either comment computes half the real worst case (128 × 512 ≈ 65 KB instead of 256 × 512 ≈ 131 KB); a boundary fixture built from it ("the schema admits 128") composes a schema-legal context — the wire validator accepts up to 256 — so the intended fail-closed pin silently tests nothing.

Suggested fix: update both comments to 256 × 512 (or reference MAX_ARRAY_ITEMS × MAX_NOTE_LENGTH by name so they cannot drift again), in the same edit.

中文说明

问题: 128→256 的边界修改遗留了两处过时的边界引用——这两处的数组上限是 MAX_ARRAY_ITEMS(现为 256)× MAX_NOTE_LENGTH(512):(1) packages/cli/src/commands/review/compose-review.ts:1866-1869 —— MAX_DIMENSIONS 的理由注释仍写 "at the schema bounds (128 x 512 chars)";(2) packages/cli/src/commands/review/compose-review.test.ts:720 —— "The schema admits 128 dimensions x 512 chars"。其余所有 128 引用都已同步更新(设计文档、两个边界测试文件,甚至 dedup 测试的示例文本);这两处遗漏由三个审计 agent 与验证 agent 独立发现。行为不受影响(slice(0, MAX_DIMENSIONS) 两种情况下都限制输出)——这是同步维护的遗漏。

失败场景: 维护者按这两处注释估算最坏情况披露段大小时会算出真实最坏值的一半(128 × 512 ≈ 65 KB,而非 256 × 512 ≈ 131 KB);据此构造的边界夹具("schema 只接受 128 条")实际上是 schema 合法的上下文——wire 校验器最多接受 256 条——意图中的 fail-closed 钉住就静默失效了。

建议修复: 同一次编辑中把两处注释更新为 256 × 512(或直接按名引用 MAX_ARRAY_ITEMS × MAX_NOTE_LENGTH),使其不再漂移。

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

Comment on lines +791 to +795
body: 'we fixed this, thanks',
path: 'a.ts',
line: 12,
commit_id: 'abc123',
in_reply_to_id: 1,

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.

[Suggestion] The "does not author-match replies" fixture never exercises the !c.in_reply_to_id guard: its body ('we fixed this, thanks') fails the severityOf shape gate regardless of the reply flag, so deleting !c.in_reply_to_id && from the presubmit filter keeps the whole suite green (mutation-verified: 55/55 pass with the guard removed). Giving the fixture a finding-shaped body flips the probe — the same mutation then fails this test. — Failure scenario: the guard can be dropped in a later refactor with no red test; a finding-shaped reply by the reviewing account (a body opening **[Critical]**/**[Suggestion]** — the format this skill mandates on finding prose) at the current commit's path:line then lands in the overlap bucket, and blockOnExistingComments deterministically and silently withholds a genuinely new finding at that location — the exact failure the shape gate exists to prevent.

Suggested change
body: 'we fixed this, thanks',
path: 'a.ts',
line: 12,
commit_id: 'abc123',
in_reply_to_id: 1,
body: '**[Critical]** confirmed, thanks',
path: 'a.ts',
line: 12,
commit_id: 'abc123',
in_reply_to_id: 1,

With a finding-shaped body the assertion still expects total 0 — now only the !c.in_reply_to_id term can produce that.

中文说明

问题: "does not author-match replies" 夹具从未真正检验 !c.in_reply_to_id 守卫:其 body('we fixed this, thanks')无论是否有回复标记都会先被 severityOf 形态门拒绝,因此从 presubmit 过滤器中删除 !c.in_reply_to_id && 后整个套件仍然全绿(突变验证:删除守卫后 55/55 通过)。把夹具改为 finding 形态的 body 即可翻转探针——同样的突变会让该测试失败。

失败场景: 该守卫可在后续重构中被删除而没有任何测试变红;审查账号以 finding 形态(以 **[Critical]**/**[Suggestion]** 开头——本 skill 对 finding 文本强制的格式)在当前 commit 的同一 path:line 回复时,该回复会落入 overlap 桶,blockOnExistingComments 将确定性地、静默地扣下该位置真正的新发现——正是形态门要防止的失败。

建议修复: 见 suggestion 块——给回复夹具一个 finding 形态的 body,使形态门不再掩盖守卫(仍期望 total 0——此时只有 !c.in_reply_to_id 项能产生该结果)。

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

Comment on lines +210 to +213
// Wiring leg: dropping `defaultComment` from the handler call leaves the
// direct runPublishAssets test green while production refuses. The
// workspace-polluted mock stands guard on the scope flag at the same
// time — it answers a flag-less call with comment:true.

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.

[Suggestion] The workspace-polluted loadSettings mock this comment advertises as a scope-flag guard can never trip in this file: the only handler-driven test is this success case, which sets reviewSettingsMock to { comment: true } — both mock branches then answer comment:true, so dropping skipWorkspaceSettings on this path changes nothing; and every refusal test goes through run()runPublishAssets directly, which never reads settings (the only operatorReviewSettings call is inside the handler). Mutation-verified: dropping the flag keeps this suite green, while the identical mutation reddens submit.test.ts's live handler-refusal guard. Sibling suites (submit, compose-review, parse-args) all carry a live tripwire; this is the only one whose polluted view coincides with its single handler test's expectation. The scope property itself is pinned at the source (review-settings.test.ts toHaveBeenCalledWith), so this is a vacuous-guard + overclaiming-comment quality issue, not an unguarded hole. — Failure scenario: a future change resolving review.comment without skipWorkspaceSettings on the publish-assets handler path ships with this suite green, while maintainers trust the comment that a second guard exists here.

Suggested fix: add a handler-path refusal test — args without --comment, reviewSettingsMock.mockReturnValue({}), invoke publishAssetsCommand.handler, expect exitCode 3 (with the scope flag dropped the polluted branch answers comment:true, the run publishes, and the test reddens); also correct the two overclaiming comments.

中文说明

问题: 这段注释宣称 workspace 污染的 loadSettings mock 是 scope flag 的守卫,但本文件中没有任何测试能触发它:唯一的 handler 驱动测试就是这个成功用例,它把 reviewSettingsMock 设为 { comment: true }——两个 mock 分支都返回 comment:true,因此在此路径上丢弃 skipWorkspaceSettings 不会有任何变化;而所有拒绝测试都走 run()runPublishAssets 直连,从不读取配置(唯一的 operatorReviewSettings 调用在 handler 内部)。突变验证:丢弃该 flag 本套件仍为绿,而同样的突变会让 submit.test.ts 中有效的 handler 拒绝守卫变红。兄弟套件(submit、compose-review、parse-args)都有能触发的守卫;本文件是唯一一个污染视图与唯一 handler 测试期望重合的。scope 属性本身在源头有钉住(review-settings.test.ts 的 toHaveBeenCalledWith),因此这是空守卫 + 注释夸大,而不是未设防的漏洞。

失败场景: 未来若 publish-assets handler 路径改为不经 skipWorkspaceSettings 解析 review.comment,本套件仍为绿地通过,而维护者相信这里还有第二道守卫。

建议修复: 补一个 handler 路径的拒绝测试——args 不含 --commentreviewSettingsMock.mockReturnValue({})、调用 publishAssetsCommand.handler、期望 exitCode 3(scope flag 被丢弃时污染分支返回 comment:true,运行会发布,测试变红);同时修正两处夸大的注释。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix round summary — PR #8994

One commit: f3484cbb0c fix(cli): show the review settings in the settings dialog (#8994) (3 files, +22/−5).

Feedback points and dispositions

1. [rc:3772321981] Automated reviewer [Suggestion] — presubmit reply fixture masks the reply guard → IMPLEMENTED

The "does not author-match replies" fixture never exercised the !c.in_reply_to_id guard: its body ('we fixed this, thanks') fails the severityOf shape gate regardless of the reply flag, so deleting the guard from the presubmit filter kept the whole suite green. Applied the suggested fix: the fixture body is now finding-shaped ('**[Critical]** confirmed, thanks'), so the shape gate passes and only the reply guard can keep the comment out of the dedup set — the assertion still expects total 0.

Re-verified the mutation locally: with the guard temporarily deleted, exactly this one test fails (1/55); with the guard restored (byte-identical, zero diff), 55/55 pass. The probe now flips as the reviewer described.

2. @wenshao maintainer verification report (issue-level) — the /settings dialog claim → IMPLEMENTED (the flip option)

The report verified every testable claim end-to-end except one: the PR description says all three review settings are toggleable in the /settings dialog, but all three shipped showInDialog: false. The report offered two fixes: flip the three to showInDialog: true, or amend the description before merge.

Decision: flipped the three leaf definitions (review.attribution, review.effort, review.comment) to showInDialog: true. Reasons:

  • Amending the PR description is not available to this workflow round (it owns PR-body writes; address-review emits no PR-body artifact), while the flip was explicitly endorsed ("they seem like reasonable dialog citizens").
  • The dialog renders both needed types natively: booleans as toggles and review.effort as an option-cycling enum.
  • The parent review object stays showInDialog: false (an object row is not dialog-renderable).
  • Added a pin test (review-settings.test.ts) asserting all three keys are in getDialogSettingKeys(), so the claim and the schema cannot drift again.

One consequence, checked and documented: getDialogSettingKeys() also feeds the daemon /workspace/settings allowlist, so the three keys become readable/writable through the Web Shell / desktop settings route (writes land in user or workspace scope). The operator-scopes-only rule is enforced at the READER — operatorReviewSettings() loads with skipWorkspaceSettings: true — so workspace-scoped values remain ignored no matter which UI wrote them; the documented security property ("a repository cannot set review policy for its reviewers") is unchanged. Each setting's description already states the workspace scope is ignored. If the maintainer prefers the other option (amend the description, keep the settings out of the dialog/UI), this commit reverts cleanly.

Also noted from the report: the one unit failure seen on the maintainer's machine (stale-bundle.test.ts) was confirmed pre-existing on base there; it does not reproduce in this environment.

3. Deferred non-Critical feedback (5 items) → NOT TOUCHED, per Critical-only mode

The ci-bot review and inline rc:3772321967 / rc:3772321973 / rc:3772321978 / rc:3772321996 are in the Deferred non-Critical feedback audit section: no code changes, no thread resolutions, no replies this round. They remain open for human follow-up.

4. Failed checks / still-red checks → none reported this round.

Conflict

--conflict false — no merge performed.

Verification

Commands actually run and their results:

  • npm run build — passed (also cleared a pre-existing stale-dist import error in workspace-settings.test.ts unrelated to this change)
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run generate:settings-schema — run because a settings source changed; zero diff in packages/vscode-ide-companion/schemas/settings.schema.json (the artifact does not embed showInDialog)
  • cd packages/cli && npx vitest run src/commands/review/presubmit.test.ts src/commands/review/lib/review-settings.test.ts — 2 files, 64 passed
  • Mutation probe on the reply guard: guard deleted → the hardened reply test fails (1/55); guard restored → 55/55 pass; restored file byte-identical (zero git diff)
  • cd packages/cli && npx vitest run src/commands/review src/commands/review.test.ts — 69 files, 2475 passed / 4 skipped
  • cd packages/cli && npx vitest run src/utils/settingsUtils.test.ts src/ui/components/SettingsDialog.test.tsx src/serve/routes/workspace-settings.test.ts — 3 files, 163 passed
  • cd packages/cli && npx vitest run src/config/settings.test.ts src/config/config.test.ts — 17 environment-caused failures when inheriting this runner's QWEN_HOME / SANDBOX / QWEN_CODE_CUSTOM_SANDBOX_IMAGE variables (injected by the enclosing CLI runtime); with those variables unset: 2 files, 493 passed. Pre-existing environment contamination, unrelated to this change (the schema flip touches no load/merge/sandbox logic)
  • Integration tests after npm run bundle — not run: the touched behavior (dialog membership, presubmit fixture) is fully exercised by the unit suites above, not only through the bundled CLI
中文说明

Autofix 本轮总结 — PR #8994

一个提交:f3484cbb0c fix(cli): show the review settings in the settings dialog (#8994)(3 个文件,+22/−5)。

反馈点与处置

1. [rc:3772321981] 自动审查器 [Suggestion] —— presubmit 回复夹具掩盖了回复守卫 → 已实现

"does not author-match replies" 夹具从未真正检验 !c.in_reply_to_id 守卫:其 body('we fixed this, thanks')无论回复标记是否存在,都会先被 severityOf 形态门拒绝,因此删除 presubmit 过滤器中的该守卫后整个套件仍然全绿。已按建议修复:夹具 body 改为 finding 形态('**[Critical]** confirmed, thanks'),形态门不再掩盖守卫,只有回复守卫能把该评论挡在 dedup 集合之外——断言仍期望 total 为 0。

本地复验了突变:临时删除守卫后,恰好只有该测试失败(1/55);恢复守卫后(逐字节一致,零 diff)55/55 通过。探针现已按审查器所述翻转。

2. @wenshao 维护者验证报告(issue 级)—— /settings 对话框声称 → 已实现(采用翻转方案)

报告端到端验证了描述中所有可实测的声称,仅有一处不实:PR 描述称三个 review 设置均可在 /settings 对话框中切换,但三项 schema 均为 showInDialog: false。报告给出两种修复:把三项翻转为 showInDialog: true,或在合并前修订描述。

决定:把三个叶子定义(review.attributionreview.effortreview.comment)翻转为 showInDialog: true。理由:

  • 修订 PR 描述对本轮工作流不可用(PR 正文写入由工作流负责;address-review 不产出 PR 正文产物),而翻转方案已被明确认可("放进对话框也合理")。
  • 对话框原生支持所需的两种类型:布尔项渲染为开关,review.effort 渲染为可循环切换的枚举。
  • 父级 review 对象保持 showInDialog: false(对象行无法在对话框中渲染)。
  • 新增钉住测试(review-settings.test.ts),断言三个键均在 getDialogSettingKeys() 中,防止声称与 schema 再次静默漂移。

一个已核查并记录的连带影响:getDialogSettingKeys() 同时供守护进程 /workspace/settings 允许列表使用,因此这三个键将在 Web Shell / 桌面端设置路由中可读可写(写入落入 user 或 workspace 作用域)。"仅操作者作用域"规则在读取端强制执行——operatorReviewSettings()skipWorkspaceSettings: true 加载——因此无论哪个 UI 写入,workspace 作用域的值仍被忽略;已记录的安全属性("仓库不能为其审查者设定 review 策略")不变。每个设置项的描述中已说明 workspace 作用域被忽略。若维护者倾向另一方案(修订描述、让设置保持不在对话框/UI 中),本提交可干净回退。

报告中的另一点说明:维护者本机看到的唯一单测失败(stale-bundle.test.ts)在其 base 上已确认为既有问题;本环境不复现。

3. 已延后的非 Critical 反馈(5 项)→ 未触碰,遵循 Critical-only 模式

ci-bot 的 review 及 inline rc:3772321967 / rc:3772321973 / rc:3772321978 / rc:3772321996 位于 Deferred non-Critical feedback 审计区:本轮不改代码、不解决线程、不回复,保持开放留待人工跟进。

4. 失败检查 / 持续失败的检查 → 本轮无报告。

冲突

--conflict false —— 未执行合并。

验证

实际执行的命令及结果:

  • npm run build —— 通过(同时消除了一处与本改动无关的 workspace-settings.test.ts 陈旧 dist 导入错误)
  • npm run typecheck —— 通过
  • npm run lint —— 通过
  • npm run generate:settings-schema —— 因 settings 源变更而执行;packages/vscode-ide-companion/schemas/settings.schema.json 零 diff(该产物不包含 showInDialog
  • cd packages/cli && npx vitest run src/commands/review/presubmit.test.ts src/commands/review/lib/review-settings.test.ts —— 2 个文件,64 通过
  • 回复守卫突变探针:删除守卫 → 强化后的回复测试失败(1/55);恢复守卫 → 55/55 通过;恢复后文件逐字节一致(git diff 为零)
  • cd packages/cli && npx vitest run src/commands/review src/commands/review.test.ts —— 69 个文件,2475 通过 / 4 跳过
  • cd packages/cli && npx vitest run src/utils/settingsUtils.test.ts src/ui/components/SettingsDialog.test.tsx src/serve/routes/workspace-settings.test.ts —— 3 个文件,163 通过
  • cd packages/cli && npx vitest run src/config/settings.test.ts src/config/config.test.ts —— 在继承本运行器的 QWEN_HOME / SANDBOX / QWEN_CODE_CUSTOM_SANDBOX_IMAGE 变量(由外层 CLI 运行时注入)时出现 17 个环境性失败;清除这些变量后:2 个文件,493 通过。属既有环境污染,与本改动无关(schema 翻转不触及任何加载/合并/沙箱逻辑)
  • npm run bundle 后的集成测试 —— 未执行:本次触及的行为(对话框成员资格、presubmit 夹具)已由上述单测套件完整覆盖,并非仅能通过打包后的 CLI 验证

Deferred non-Critical feedback

Critical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

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

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI (merge-queue-only by design) and its suite did not run locally this round.

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — finished well under the tool ceiling.; chunk 2: none — all checks I intended completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — finished within budget; no check left unfinished.; "PR #8994 adds three operator-scope-only user settings to…": none — I completed every check my method mandated within the tool budget., and 4 more.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI (merge-queue-only by design) and its suite did not run locally this round。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — finished well under the tool ceiling.;chunk 2:none — all checks I intended completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — finished within budget; no check left unfinished."PR #8994 adds three operator-scope-only user settings to…"none — I completed every check my method mandated within the tool budget.,另有 4 条。

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

Comment on lines +60 to +61
export function stripReviewFooter(body: string): string {
return body.includes(FOOTER_MARKER)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R5-1: The marker guard only short-circuits marker-LESS bodies; a body that CONTAINS FOOTER_MARKER but carries no trailing footer still runs the unanchored, quadratic REVIEW_FOOTER_RE. — Failure scenario: a model-written body quoting or truncating a forged footer (_— … via Qwen Code /review present mid-text, no valid trailing footer) followed by a long single-line whitespace run → probe-measured at HEAD: 40k chars 738 ms, 80k ≈ 3.0 s, 160k ≈ 10.8 s (~4× per doubling — quadratic), while the marker-less control is ~0.1 ms. Reachable through every strip site (submit strips every non-empty comment body; compose-review strips drafted entries and ledger entries), and the new perf tests pin only the marker-less shape, so this class ships green. No adversary needed: the model-looping shapes this function exists to strip contain the marker by definition. Note: the fix must bound from the tail — bounding at the last marker occurrence was measured and does NOT work (the whitespace run sits after the last marker line and stays inside the bounded suffix).

export function stripReviewFooter(body: string): string {
  if (!body.includes(FOOTER_MARKER)) return body;
  // A match can only live at the tail ($-anchored regex): run the
  // quadratic-prone replace over a bounded slice, not the whole body.
  const tail = body.slice(-STRIP_TAIL_LIMIT); // e.g. 8192: covers any realistic accumulated footer run
  const stripped = tail.replace(REVIEW_FOOTER_RE, '');
  return stripped === tail ? body : body.slice(0, body.length - tail.length) + stripped;
}

plus a timing pin for the marker-present/no-trailing-footer shape next to the existing marker-less one.

中文说明

问题: marker 守卫只对不含 marker 的 body 短路;含 FOOTER_MARKER 但没有尾部 footer 的 body 仍会运行未锚定、二次方复杂度的 REVIEW_FOOTER_RE

失败场景: 模型生成的 body 在中部引用或截断了一个伪造 footer(_— … via Qwen Code /review 存在但没有合法的尾部 footer),其后跟随一长串单行空白 → 在 HEAD 上实测:4 万字符 738 ms、8 万 ≈ 3.0 s、16 万 ≈ 10.8 s(每次翻倍约 4 倍——二次方增长),而无 marker 对照仅 ~0.1 ms。所有 strip 调用点均可触发(submit 对每个非空评论 body 都 strip;compose-review 对草稿条目与 ledger 条目 strip),而新增的性能钉住测试只覆盖无 marker 形态,因此该形态会带病合入。无需攻击者:本函数要剥离的模型死循环形态本身就含 marker。注意:修复必须从尾部界定——实测"以最后一个 marker 出现位置界定"并不可行(空白串位于最后一个 marker 行之后,仍在被界定的后缀内)。

建议修复: 见代码块——把 replace 限制在有界的尾部切片上(regex 以 $ 锚定,匹配只能存在于尾部),并为"含 marker 但无尾部 footer"的形态补一个与现有无 marker 钉住并列的耗时上限测试。

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

Comment on lines +787 to +790
// Finding-shaped on purpose: a hand-written body fails the severityOf
// shape gate first, so the test would stay green with the reply guard
// deleted (mutation-verified) — with this body, only the
// !c.in_reply_to_id term keeps the reply out of the dedup set.

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.

[Suggestion] R6-1: The comment states the opposite of the test's actual mutation property — it claims "the test would stay green with the reply guard deleted", but the fixture body ('**[Critical]** confirmed, thanks') PASSES severityOf, so deleting !c.in_reply_to_id makes the test RED. — Failure scenario: mutation-probed at HEAD: deleting the guard from the presubmit filter fails this test (AssertionError: expected 1 to be +0 at the existingComments.total assertion); restoring it returns to green. The comment even contradicts its own final sentence ("only the !c.in_reply_to_id term keeps the reply out"). A maintainer trusting the stated guarantee holds a wrong mental model of which guard this test protects — e.g. believes the reply guard is untested and safe to drop, or misreads a legitimate red on guard deletion as unrelated breakage.

Suggested change
// Finding-shaped on purpose: a hand-written body fails the severityOf
// shape gate first, so the test would stay green with the reply guard
// deleted (mutation-verified) — with this body, only the
// !c.in_reply_to_id term keeps the reply out of the dedup set.
// Finding-shaped on purpose: the body PASSES the severityOf shape gate,
// so deleting the `!c.in_reply_to_id` reply guard makes this test red
// (mutation-verified) — only that term keeps the reply out of the dedup
// set.
中文说明

问题: 该注释与测试真实的突变性质相反——它声称"删除回复守卫后测试仍保持绿色",但夹具 body('**[Critical]** confirmed, thanks')能通过 severityOf,因此删除 !c.in_reply_to_id 会让测试变红。

失败场景: 在 HEAD 上做突变探针验证:从 presubmit 过滤器中删除该守卫后此测试失败(existingComments.total 断言处 AssertionError: expected 1 to be +0);恢复后重新变绿。注释甚至与其自身最后一句("只有 !c.in_reply_to_id 项能把该回复挡在外面")自相矛盾。信任该注释保证的维护者会对这个测试究竟保护哪个守卫形成错误心智模型——例如以为回复守卫未被测试、可以安全删除,或把删除守卫后合理的变红误读为无关的破坏。

建议修复: 见 suggestion 块——改写为与实测突变性质一致的表述。

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

}

const auth = authorization(args);
const auth = authorization(args, defaultComment);

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.

[Suggestion] R6-4 (with sibling claim R6-5, same advice block): runSubmit's refusal advice unconditionally appends "Re-run with --comment" to every refusal — wrong for the target-binding refusals this diff's review.comment setting path newly reaches. — Failure scenario (probe-verified end-to-end against the real runSubmit): operator sets review.comment: true, and (a) the recorded args name no PR (/review somefile.ts) — the gate fails closed with the diff's own corrected why ("…do not name a pull request…"), or (b) the submission targets a different PR/repo/host than the recorded args authorise ("…authorise pull request #123, but this submission targets #456" — R6-5). In both cases the advice then says "Re-run with --comment": a flag already stood in for by the setting and unable to fix a target binding. The reviewing model/user reads this stderr prose to choose its retry, so the advice buys a futile retry loop (or an unearned --user-authorized escalation) — precisely the misdirection the adjacent added comment in authorization.ts condemns ("blaming a missing --comment flag the operator never typed… misdirects"). The mismatch tests pin only exit 3, not the advice text. Reachability: the orchestrated flow is pre-empted by the parse-args verdict, so direct invocation and "post comments" follow-ups on terminal-only reviews are the window.

// in runSubmit's refusal block: branch the remedy on the refusal reason
const remedy = auth.why.includes('`--comment` was not in the review arguments')
  ? 'Re-run with `--comment`, or pass --user-authorized only after …'
  : 'Re-invoke the review naming the pull request this submission targets, '
    + 'or pass --user-authorized only after …';

(the advice text sits in unchanged lines below the anchored call site, hence a sketch rather than a one-click suggestion)

中文说明

问题: runSubmit 的拒绝建议无条件给每个拒绝追加"Re-run with --comment"——对本 diff 的 review.comment 配置路径新可达的 target-binding 类拒绝而言是错误建议。

失败场景:(已对真实 runSubmit 做端到端探针验证)运维者设置 review.comment: true,且 (a) 记录的参数未点名 PR(/review somefile.ts)——门以本 diff 自己修正过的 why 正确拒绝("…do not name a pull request…");或 (b) 提交指向的 PR/repo/host 与记录参数授权的不同("…authorise pull request #123, but this submission targets #456"——即 R6-5)。两种情况下建议都说"Re-run with --comment":该 flag 已由配置替代,且无法修复 target 绑定。审查模型/用户读这段 stderr 来决定重试,于是建议买来无效的重试循环(或不该有的 --user-authorized 升级)——正是 authorization.ts 中相邻新增注释所谴责的误导("归咎于操作者从未输入过的 --comment flag……是误导")。mismatch 测试只钉住 exit 3,未钉住建议文本。可达性:编排流程会被 parse-args 裁决预先拦下,因此窗口是直接调用与终端-only review 之后的"post comments"跟进。

建议修复: 在 runSubmit 的拒绝块中按拒绝原因分支给出补救建议——仅"未请求 comment"类拒绝保留 --comment 建议;绑定类拒绝改为"重新发起点名目标 PR 的 review"。(建议文本位于锚定行下方的未改动行,故给出示意代码而非一键 suggestion。)

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

Comment on lines +60 to +63
// The production call carries `{ skipWorkspaceSettings: true }` — the
// authorisation default resolves from operator scopes only. A caller
// that forgets the flag reads the workspace-polluted view instead, and
// the refusal assertions redden.

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.

[Suggestion] R5-5: The tripwire comment overclaims — "the refusal assertions redden" is false for this file: no refusal assertion here runs through the handler where operatorReviewSettings() lives. — Failure scenario: mutation-probed: deleting skipWorkspaceSettings: true from review-settings.ts leaves this file 35/35 green — the only publishAssetsCommand.handler invocation (line 217) is a success test whose mock returns comment: true either way, and all 15 refusal assertions go through run()runPublishAssets directly, which never touches settings. The real guards are submit.test.ts's handler-level refusal test and review-settings.test.ts's direct scope assertion (both verified to redden under the same mutation). A maintainer relying on this comment concludes the flag is locally pinned when it is not — same stale/overclaiming-comment class as the confirmed R5-3/R6-1/R6-3. The comment still overclaims at the reviewed commit (the deferred round-5 ledger item).

Suggested change
// The production call carries `{ skipWorkspaceSettings: true }` — the
// authorisation default resolves from operator scopes only. A caller
// that forgets the flag reads the workspace-polluted view instead, and
// the refusal assertions redden.
// The production call carries `{ skipWorkspaceSettings: true }` — the
// authorisation default resolves from operator scopes only. A caller that
// forgets the flag reads the workspace-polluted view instead; the guards
// that redden are submit.test.ts's handler-level refusal test and
// review-settings.test.ts's direct assertion. This file's own refusals
// bypass the handler, so only the wiring leg below exercises the mock.
中文说明

问题: 绊线注释过度声称——"the refusal assertions redden"(拒绝断言会变红)在本文件中不成立:此文件没有任何拒绝断言经过 operatorReviewSettings() 所在的 handler。

失败场景: 突变探针验证:从 review-settings.ts 删除 skipWorkspaceSettings: true 后本文件仍 35/35 全绿——唯一的 publishAssetsCommand.handler 调用(第 217 行)是成功用例,其 mock 两种情况下都返回 comment: true;全部 15 个拒绝断言都经 run()runPublishAssets 直达,从不触碰 settings。真正的守卫是 submit.test.ts 的 handler 级拒绝测试与 review-settings.test.ts 的直接作用域断言(同一突变下均已验证变红)。依赖该注释的维护者会误以为该 flag 在本文件内已被钉住——与已确认的 R5-3/R6-1/R6-3 同属"陈旧/过度声称的注释"类。在受审 commit 上该注释仍未修正(第 5 轮被延后的 ledger 条目)。

建议修复: 见 suggestion 块——改为点名真实守卫的表述。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix round summary — PR #8994

Four inline findings were actionable this round (one Critical, three Suggestions; the Deferred non-Critical feedback section was empty, so everything rendered was in scope). All four were verified against the exact reviewed code — three by mutation probe, one by direct timing probe — and all four are resolved in the code. No finding was declined, deferred, or escalated.

The round-6 review verdict itself (CHANGES_REQUESTED — Partially reviewed — gaps disclosed) is a coverage disclosure, not a finding: the named gap is the merge-queue-only Integration Tests (CLI, No Sandbox) suite, which is skipped by design on PR runs and is not exercised by these changes.

Findings and dispositions

  • [Critical] R5-1 (lib/review-footer.ts:61, rc:3774027604) — Resolved. The marker guard only short-circuited marker-LESS bodies; a body CONTAINING FOOTER_MARKER but with no trailing footer still ran the unanchored, quadratic REVIEW_FOOTER_RE. Reproduced at HEAD by probe: 10k/20k/40k-char whitespace runs took 58/216/870 ms (~4× per doubling). Fix: the replace now runs only over the last 8192 characters — the regex is $-anchored, so a match can only live at the tail; one footer is ~40 chars, so the bound still covers a few hundred accumulated footers. (Bounding at the last marker occurrence does not work — the whitespace run sits after the last marker line — as the finding measured.) Post-fix probe: 80k and 160k shapes both ~29 ms, constant in body length. Added a timing pin for the marker-present/no-trailing-footer shape beside the existing marker-less one, plus a correctness test that a long body still strips through the bounded tail. Both strip sites (submit, compose-review) share the function, so one fix covers all sites.
  • [Suggestion] R6-1 (presubmit.test.ts:790, rc:3774027612) — Resolved. Mutation-probe confirmed the comment stated the opposite of the test's property: deleting the !c.in_reply_to_id guard reddens the test (expected 1 to be +0 at existingComments.total) because the fixture body passes severityOf. The comment now states the true mutation property.
  • [Suggestion] R6-4 (with sibling R6-5) (submit.ts:428, rc:3774027632) — Resolved. runSubmit's refusal advice unconditionally said "Re-run with --comment", which is a futile retry for the target-binding refusals the new review.comment setting path reaches (no PR named; PR/repo/host mismatch). The advice now branches on the refusal class: refusals whose why carries `--comment` was (flag never requested, or no recorded arguments at all) keep the flag remedy; target-binding refusals say the recorded arguments do not bind this target and the remedy is a review invoked naming the pull request. The discriminator is the gate's own backticked prose — the raw-args echo inside why is JSON-quoted and cannot carry that backticked sequence — so a --comment typed on a non-PR target still lands in the target-binding branch, and the missing-args-file refusal gets the flag remedy (one step beyond the finding's sketch, whose version sent it to the target branch). A new test pins both branches and both target-binding shapes; the mismatch tests previously pinned only exit 3.
  • [Suggestion] R5-5 (publish-assets.test.ts:63, rc:3774027638) — Resolved. Mutation-probe confirmed the tripwire overclaimed: deleting skipWorkspaceSettings: true from review-settings.ts left this file 35/35 green (its refusals call runPublishAssets directly and never touch the handler), while submit.test.ts's handler-level tests (3) and review-settings.test.ts's direct scope assertion redden. Rewrote the comment to name the real guards; also corrected the adjacent sentence above the mock, which made the same class of overclaim about "the refusal assertions below".

Changes

  • packages/cli/src/commands/review/lib/review-footer.tsstripReviewFooter runs the replace over a bounded tail slice (STRIP_TAIL_LIMIT = 8192); docstring records both bounds and the rejected marker-occurrence bound.
  • packages/cli/src/commands/review/lib/review-footer.test.ts — timing pin for the marker-present/no-trailing-footer shape (200k whitespace, < 2000 ms ceiling) and a long-body strip test.
  • packages/cli/src/commands/review/submit.ts — refusal advice branched by refusal class.
  • packages/cli/src/commands/review/submit.test.ts — pins the advice class split (flag remedy vs target remedy, including no-PR and PR-mismatch shapes).
  • packages/cli/src/commands/review/presubmit.test.ts, publish-assets.test.ts — tripwire comments corrected to their mutation-verified properties.

Conflict notes: --conflict false; no merge performed.

Commit: f77b44882f fix(cli): bound the footer strip tail and match refusal advice to its class (#8994).

Verification

  • Focused vitest baseline (pre-change): review-footer, presubmit, publish-assets, submit, review-settings — 170 passed.
  • Mutation probes (pre-change): removing the !c.in_reply_to_id guard reddens the presubmit reply test; removing skipWorkspaceSettings: true leaves publish-assets 35/35 green while submit.test.ts (3) and review-settings.test.ts (1) redden; timing probe reproduced the quadratic marker-present strip (58/216/870 ms at 10k/20k/40k).
  • npx vitest run src/commands/review (packages/cli, touched subsystem) — 69 files, 2478 passed, 4 skipped.
  • npx vitest run focused on the six affected files post-change — 396 passed; re-run on the committed tree — 396 passed.
  • Timing probe post-fix: marker-present shape at 80k/160k — ~29 ms each (was ~3.0 s / ~10.8 s extrapolated).
  • npm run build — FAILED at first on packages/core (image-view.ts sharp typing). Evidence: image-view.ts and package-lock.json are byte-identical to origin/main; node_modules carried sharp 0.35.3 while the lockfile pins 0.34.5 — pre-existing environment drift on this shared runner, not caused by this change. Repaired with npm ci --cache /tmp/npm-cache-8994 (plain npm ci could not write the runner's ~/.npm cache); then npm run build — passed.
  • npm run typecheck — passed.
  • npm run lint — passed.
  • Settings schema: not regenerated — no settings source changed this round.
  • Integration tests: not run — the touched behavior (strip performance bound, refusal advice prose) is fully exercised by the unit tests above, not only through the bundled CLI.
中文说明

Autofix 本轮总结 — PR #8994

本轮共有 4 条可执行的内联发现(1 条 Critical、3 条 Suggestion;Deferred non-Critical feedback 区域为空,因此所有渲染出的条目均在范围内)。四条均对照受审 commit 的准确代码做了验证——三条经突变探针、一条经直接耗时探针——并全部在代码中解决。没有拒绝、延后或上升级任何发现。

第 6 轮 review 结论本身(CHANGES_REQUESTED — Partially reviewed — gaps disclosed)是覆盖面披露,不是发现:所指缺口为仅 merge-queue 运行的 Integration Tests (CLI, No Sandbox) 套件,按设计在 PR 运行中被跳过,且本次改动不涉及该路径。

发现与处置

  • [Critical] R5-1lib/review-footer.ts:61,rc:3774027604)— 已解决。 marker 守卫只对不含 marker 的 body 短路;含 FOOTER_MARKER 但没有尾部 footer 的 body 仍会运行未锚定、二次方复杂度的 REVIEW_FOOTER_RE。在 HEAD 上探针复现:1 万/2 万/4 万字符空白串分别耗时 58/216/870 ms(每次翻倍约 4 倍)。修复:replace 现在只在末尾 8192 字符的切片上运行——正则按 $ 锚定,匹配只能存在于尾部;单个 footer 约 40 字符,该上界仍可覆盖数百个累积 footer。(按 finding 实测,以最后一个 marker 出现位置界定不可行——空白串位于最后一个 marker 行之后。)修复后探针:8 万与 16 万字符形态均约 29 ms,与 body 长度无关。在现有无 marker 钉住测试旁新增了"含 marker/无尾部 footer"形态的耗时上限测试,并新增长 body 仍可经尾部切片正常 strip 的正确性测试。两个 strip 调用点(submitcompose-review)共享该函数,一处修复覆盖全部调用点。
  • [Suggestion] R6-1presubmit.test.ts:790,rc:3774027612)— 已解决。 突变探针确认注释与测试真实性质相反:删除 !c.in_reply_to_id 守卫会使测试变红(existingComments.totalexpected 1 to be +0),因为夹具 body 能通过 severityOf。注释已改写为与实测突变性质一致的表述。
  • [Suggestion] R6-4(含姊妹条 R6-5)(submit.ts:428,rc:3774027632)— 已解决。 runSubmit 的拒绝建议无条件说 "Re-run with --comment",对新的 review.comment 配置路径可达的 target-binding 类拒绝(未点名 PR;PR/repo/host 不匹配)是无效重试。建议现按拒绝类别分支:why 中含 `--comment` was 的拒绝(从未请求 comment,或完全没有记录到参数)保留 flag 补救;target-binding 类拒绝改为说明记录参数未绑定本目标、补救是重新发起点名该 PR 的 review。判别子用的是门控自身带反引号的措辞——why 中回显的原始参数经 JSON 引号包裹,不可能含该反引号序列——因此在非 PR 目标上输入 --comment 仍落入 target-binding 分支,而"参数文件缺失"的拒绝得到 flag 补救(比 finding 示意更进一步:示意版本会把该类送入 target 分支)。新增测试钉住两个分支及两种 target-binding 形态;此前 mismatch 测试只钉 exit 3。
  • [Suggestion] R5-5publish-assets.test.ts:63,rc:3774027638)— 已解决。 突变探针确认绊线注释过度声称:从 review-settings.ts 删除 skipWorkspaceSettings: true 后本文件仍 35/35 全绿(其拒绝断言经 runPublishAssets 直达,从不触碰 handler),而 submit.test.ts 的 handler 级测试(3 条)与 review-settings.test.ts 的直接作用域断言变红。注释已改写为点名真实守卫;同时修正了 mock 上方相邻句子——它对"下方拒绝断言"做了同类的过度声称。

改动

  • packages/cli/src/commands/review/lib/review-footer.tsstripReviewFooter 在有界尾部切片(STRIP_TAIL_LIMIT = 8192)上运行 replace;docstring 记录两重界定及被否定的 marker 位置界定。
  • packages/cli/src/commands/review/lib/review-footer.test.ts — "含 marker/无尾部 footer"形态的耗时钉住(20 万空白,< 2000 ms 上限)与长 body strip 测试。
  • packages/cli/src/commands/review/submit.ts — 拒绝建议按拒绝类别分支。
  • packages/cli/src/commands/review/submit.test.ts — 钉住建议类别分支(flag 补救 vs target 补救,含未点名 PR 与 PR 不匹配形态)。
  • packages/cli/src/commands/review/presubmit.test.tspublish-assets.test.ts — 绊线注释修正为经突变验证的真实性质。

冲突说明:--conflict false;未执行合并。

提交:f77b44882f fix(cli): bound the footer strip tail and match refusal advice to its class (#8994)

验证

  • 聚焦 vitest 基线(改动前):review-footerpresubmitpublish-assetssubmitreview-settings — 170 通过。
  • 突变探针(改动前):删除 !c.in_reply_to_id 守卫使 presubmit 回复测试变红;删除 skipWorkspaceSettings: true 后 publish-assets 保持 35/35 全绿,而 submit.test.ts(3 条)与 review-settings.test.ts(1 条)变红;耗时探针复现含 marker 形态的二次方 strip(1 万/2 万/4 万字符分别 58/216/870 ms)。
  • npx vitest run src/commands/review(packages/cli,受影响子系统)— 69 个文件,2478 通过,4 跳过。
  • npx vitest run 聚焦 6 个受影响文件(改动后)— 396 通过;在已提交树上复跑 — 396 通过。
  • 修复后耗时探针:含 marker 形态在 8 万/16 万字符下均约 29 ms(此前外推约 3.0 s / 10.8 s)。
  • npm run build — 首次失败于 packages/coreimage-view.ts 的 sharp 类型错误)。证据:image-view.tspackage-lock.json 和 origin/main 逐字节一致;node_modules 中为 sharp 0.35.3 而 lockfile 钉住 0.34.5 —— 属本共享 runner 上预先存在的环境漂移,非本次改动引入。以 npm ci --cache /tmp/npm-cache-8994 修复(裸 npm ci 无法写入该 runner 的 ~/.npm 缓存);随后 npm run build — 通过。
  • npm run typecheck — 通过。
  • npm run lint — 通过。
  • settings schema:未重新生成 —— 本轮未改动任何 settings 源。
  • 集成测试:未运行 —— 受影响行为(strip 性能上界、拒绝建议措辞)已由上述单元测试完整覆盖,并非仅经打包 CLI 才能验证。

Deferred non-Critical feedback

Critical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

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

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5 (rounds 4 and 5 reported findings; every report was verified, but the loop reached the cap without two consecutive dry rounds).

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI (merge-queue-only by design) and its suite did not run locally this round.

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; "PR #8994 adds three operator-scope-only user settings to…": none — all planned checks completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": (none — all checks I started were completed within budget.); "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget., and 8 more.

中文说明

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

未审查:reverse audit — did not converge within the reverse-audit round cap of 5 (rounds 4 and 5 reported findings; every report was verified, but the loop reached the cap without two consecutive dry rounds)。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI (merge-queue-only by design) and its suite did not run locally this round。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget."PR #8994 adds three operator-scope-only user settings to…"none — all planned checks completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks completed within budget."You are review agent reverse-audit — Reverse audit agent…"(none — all checks I started were completed within budget.)"You are review agent reverse-audit — Reverse audit agent…"none — all planned checks completed within budget.,另有 8 条。

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

// setting already stood in for the flag on exactly those refusals — so
// advising it there buys the futile retry loop authorization.ts's refusal
// wording exists to prevent.
const advice = auth.why.includes('`--comment` was')

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.

[Suggestion] The two-way refusal-advice classifier misroutes the missing-args-file refusal when the standing review.comment setting is on: that refusal ("no review arguments were recorded at …, so this run cannot show that --comment was requested") also contains the substring `--comment` was, so branch 1 fires — "the user did not ask to publish … Re-run with --comment" — even though the operator DID ask via the setting, and the real blocker is the missing argument record, which no flag can recreate. — Failure scenario (probe-verified end-to-end against runSubmit): operator enables review.comment: true, then qwen review submit runs where no session args file exists (a manual retry outside a /review session, or a "post comments" follow-up after clearSkillArgs). The gate fails closed at the readFileSync step before consulting defaultComment; branch 1 fires with a false premise and an unfixable remedy. Nothing posts wrongly — only the guidance misdirects. The fix below flips the probe.

Suggested change
const advice = auth.why.includes('`--comment` was')
const advice = auth.why.includes('`--comment` was') && !defaultComment

(or, more durably, have reviewWriteAuthorization return a refusal kind and branch on that instead of a substring; also add the missing-args-file + defaultComment=true class to the advice-class test.)

中文说明

问题: 双向拒绝建议分类器在常驻 review.comment 配置开启时,会把"缺少参数文件"这一拒绝误路由到 flag 分支:该拒绝文案("no review arguments were recorded at …, so this run cannot show that --comment was requested")同样包含子串 `--comment` was,于是分支 1 触发——"用户没有要求发布……请重跑并带 --comment"——但操作者其实已通过配置提出了请求,真正的阻塞是缺失的参数记录,任何 flag 都无法重建它。

失败场景(已对 runSubmit 端到端探针验证): 运维者启用 review.comment: true 后,在没有会话参数文件的场景运行 qwen review submit(例如在 /review 会话之外手工重试,或 clearSkillArgs 之后的 "post comments" 跟进)。授权门在 readFileSync 步骤就失败关闭,尚未查询 defaultComment;分支 1 以错误前提和无法奏效的补救建议触发。不会发生错误发布——只是引导文案误导。下方修复已被探针验证可翻转行为。

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

Comment on lines +99 to +100
const dialogKeys = getDialogSettingKeys();
expect(dialogKeys).toContain('review.attribution');

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.

[Suggestion] The /settings dialog exposes all three review.* keys regardless of the selected scope; with the Workspace scope selected, toggling persists them into the repository's .qwen/settings.json — exactly the file operatorReviewSettings() loads with skipWorkspaceSettings: true. — Failure scenario (probe-verified end-to-end through the real SettingsDialog save path): selecting Workspace scope and toggling the three keys writes {"review": {"comment": true, "effort": "low", "attribution": false}} into <repo>/.qwen/settings.json; operatorReviewSettings() sees none of it (control arm: the same save into User scope IS honored). The dialog then shows the settings as enabled while every later PR review ignores them, and an inert policy key lands in a committable, repo-controlled file. The two pre-existing operator-only keys both ship showInDialog: false plus a load-time workspace warning — the established convention for operator-only keys — so this diff introduces the divergence rather than inheriting it. The security direction is safe (a repo still cannot ENABLE posting); the cost is silent UX divergence plus repo-file pollution.

Fix sketch (no one-click suggestion — the change lives in the dialog, not this test):

// In SettingsDialog item generation / toggle path: skip (or mark read-only)
// keys honored only from operator scopes when the selected scope is
// Workspace — e.g. a schema flag like operatorScopesOnly checked next to
// the existing toggle-type gate; or route their writes to the User scope
// with a notice. Alternatively add a load-time warning for review.* found
// in a workspace file, matching the two existing operator-only keys.
中文说明

问题: /settings 对话框无论当前选择的作用域都会展示这三个 review.* 键;在选中 Workspace 作用域时切换开关,会把值写入仓库的 .qwen/settings.json——而 operatorReviewSettings() 恰好用 skipWorkspaceSettings: true 忽略这个文件。

失败场景(已用真实 SettingsDialog 保存路径端到端探针验证): 选中 Workspace 作用域并切换这三个键后,{"review": {"comment": true, "effort": "low", "attribution": false}} 被写入 <repo>/.qwen/settings.jsonoperatorReviewSettings() 对它们一律不可见(对照组:同样内容写入 User 作用域则生效)。对话框显示配置已启用,而之后每次 PR review 都无视它们;同时一个无效的策略键落入了可提交、仓库可控的文件。既有的两个仅操作者作用域键都采用 showInDialog: false + 加载期工作区告警的约定——本 diff 引入了这一偏离而非继承它。安全方向是安全的(仓库依然无法启用自动发布);代价是静默的 UX 背离与仓库文件污染。修复示意:在对话框条目生成/切换路径中,当选中作用域为 Workspace 时跳过(或只读化)仅操作者作用域生效的键,或把这类写入路由到 User 作用域并给出提示;也可以像既有两个操作者专属键那样,为工作区文件中出现的 review.* 增加加载期告警。

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

Comment on lines +349 to +351
// A post that was never requested: the remedy names the flag.
runSubmit(args({ skillArgs: file('advice-flag.txt', '6771') }));
expect(advice()).toContain('Re-run with `--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.

[Suggestion] A third refusal class misroutes in the two-way advice split (sibling of the R7-1 finding on submit.ts:441, distinct instance): when the recorded arguments name no PR and comment was never requested (no --comment, setting off), the gate emits the flag-worded refusal `--comment` was not in the review arguments (authorization.ts), whose substring routes to branch-1 advice "Re-run with --comment" — but the real blocker is that the arguments name no pull request, and the flag cannot bind one. — Failure scenario (probe-verified): a file-scoped review's args record ('src/foo.ts', no flag, setting off) → branch-1 advice fires; the prescribed retry ('src/foo.ts --comment') is refused AGAIN with "do not name a pull request" — one full wasted review re-run before branch-2 advice finally says stop. The control arm (PR target) posts correctly on retry, so only the PR-less class misroutes; the gate stays fail-closed throughout. Note the asymmetry visible in this very test: the same PR-less args with the setting ON correctly get class-2 advice below, while setting OFF gets the misdirecting flag advice. Any fix must also update the "Neither source requested it: the original wording stands" pin, which currently asserts the misrouting wording.

Fix sketch (no one-click suggestion — the fix spans authorization.ts and this test):

// authorization.ts: emit the target-binding wording when !commentRequested
// and the parsed target is not PR-shaped — the flag remedy only makes sense
// when the arguments name a PR. Or return a refusal kind from
// reviewWriteAuthorization and branch on it in submit.ts.
// Then pin the third class here:
//   runSubmit(args({ skillArgs: file('advice-nothing.txt', 'src/foo.ts') }));
//   expect(advice()).not.toContain('Re-run with `--comment`');
中文说明

问题: 双向建议分支存在第三类误路由(与 submit.ts:441 的 R7-1 同源但属不同实例):当记录的参数未点名 PR从未请求 comment(无 --comment、配置关闭)时,授权门发出 flag 措辞的拒绝 `--comment` was not in the review arguments,其子串被路由到分支 1 建议"重跑并带 --comment"——但真正的阻塞是参数没有点名 pull request,而 flag 无法绑定目标。

失败场景(已探针验证): 文件级 review 的参数记录('src/foo.ts'、无 flag、配置关闭)→ 分支 1 建议触发;按其建议重跑('src/foo.ts --comment')会再次被拒("do not name a pull request")——在分支 2 建议终于叫停之前,白白浪费一整轮 review 重跑。对照组(PR 目标)重跑即可正常发布,因此只有无 PR 这一类被误导;授权门全程失败关闭。注意本测试自身可见的不对称:同样的无 PR 参数,配置开启时在下方正确拿到第 2 类建议,配置关闭时却拿到误导性的 flag 建议。任何修复还必须更新 "Neither source requested it: the original wording stands" 这个钉住用例——它目前断言的正是被误路由的措辞。修复示意:在 authorization.ts 中,当 !commentRequested 且解析目标不是 PR 形态时改用目标绑定措辞(flag 补救只在参数点名 PR 时有意义),或让 reviewWriteAuthorization 返回拒绝类别、在 submit.ts 按类别分支;然后在此补钉第三类。

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

Comment on lines +773 to +775
// only the marker guard keeps this linear. The attribution-off path
// routes such bodies through the strip; an unguarded replace dies on the
// suite timeout long before the assertion runs.

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.

[Suggestion] This rationale overclaims what the test pins: with STRIP_TAIL_LIMIT in place, deleting ONLY the marker guard (if (!body.includes(FOOTER_MARKER)) return body;) leaves the whole suite green — mutation-probed at the reviewed commit: 297/297 pass with the guard removed; the timeout fires only when the guard AND the tail limit are removed together. The body's _— opener sits ~500 KB before the tail, so slice(-8192) hands the regex 8189 spaces + end — marker-less and opener-less — and the literal prefilter rejects it in microseconds. Worse, the guard is pinned by NO test anywhere: review-footer.test.ts's "no regex, no rewrite" comment makes the same claim and is falsified by the same mutation run. Same stale/overclaiming-comment class as this PR's own prior-round entries R6-1 and R5-5. — Failure scenario: a refactor drops the fast path believing this test holds it; nothing reddens; marker-less bodies (the common case) then pay the slice+replace cost on every strip. Bounded by the tail limit, so no quadratic regression — but the claimed pin never existed.

Suggested change
// only the marker guard keeps this linear. The attribution-off path
// routes such bodies through the strip; an unguarded replace dies on the
// suite timeout long before the assertion runs.
// routes such bodies through the strip. Linearity is the guard plus
// STRIP_TAIL_LIMIT jointly: the guard skips slice+replace entirely on
// marker-less bodies; the tail limit bounds the marker-present worst case.

(Also fix the sibling overclaim in review-footer.test.ts's marker-less test comment; and if the fast path alone is worth pinning, assert the regex never runs on a marker-less body — e.g. a spy — since the guard is output-neutral and cannot be pinned by output.)

中文说明

问题: 该注释夸大了测试实际钉住的内容:在 STRIP_TAIL_LIMIT 存在的前提下,只删除 marker 守卫(if (!body.includes(FOOTER_MARKER)) return body;)整个套件仍然全绿——已在受审 commit 上做突变探针验证:删除守卫后 297/297 通过;只有同时删除守卫与尾部上限才会超时。测试 body 的 _— 起始位于距尾部约 500 KB 处,slice(-8192) 交给正则的只是 8189 个空格 + end——既无 marker 也无起始符——字面量预过滤在微秒内就拒绝了匹配。更糟的是,该守卫在任何地方都没有测试钉住:review-footer.test.ts 中 "no regex, no rewrite" 测试的注释做了同样声称,并被同一次突变运行证伪。与本 PR 自己前几轮的 R6-1、R5-5 同属"陈旧/过度声称的注释"类。

失败场景: 重构者以为本测试钉住了该快速路径而将其删除;没有任何测试变红;无 marker 的 body(常见情形)从此每次 strip 都要付出 slice+replace 的代价。有尾部上限兜底,不会二次方退化——但声称中的钉住从来不存在。建议修复:把注释改写为"线性由守卫 + STRIP_TAIL_LIMIT 共同保证"(守卫让无 marker body 完全跳过 slice+replace;尾部上限界定含 marker 的最坏情形),并同步修正 review-footer.test.ts 中同类的过度声称;若快速路径本身值得钉住,可断言正则不会在无 marker body 上运行(例如用 spy)——守卫对输出无影响,无法用输出断言钉住。

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

@wenshao

wenshao commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

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

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

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

Verification report

Verification report — PR #8994

Verdict: merge-ready — 167/167 scripted assertions passed (0 unexpected failures), mutation matrix 6/6 killed with correct attribution, targeted gates green. Verified head: f77b44882f4f2e1ca92c758de71f3c9dd6fb8793 (merge-ref HEAD = 734042a, base tip HEAD^1 = 7f458b4). First round (no previous-report.md).

中文摘要
  • 裁决merge-ready。167/167 脚本断言通过,0 个意外失败;6/6 突变全部被对应测试杀死。
  • A/B 结论:三个配置项(review.attribution / review.effort / review.comment)在 head 构建上按文档生效(含优先级链:显式 flag > 配置 > 内置;--comment/--fix 强制规则不变;review.comment 授权发布但不替代目标绑定),base 构建对同一份 settings.json 完全无感;无配置时 head 与 base 的裁决逐字节一致(6 个相同单元格)。工作区 .qwen/settings.json 无法影响这三个键(真实配置加载下的 S7/S8 单元格 + M1 突变证明)。footer 剥离的 8192 尾窗在含 marker 的敌意 body 上保持线性(base 二次方:2→4→11→169→2704→10873 ms,head 恒定 29 ms)。
  • Findings:两条信息级边界观察(>200 个累积 footer 的链在尾窗外留下头部残片;尾窗前 >8k 空白在剥离 footer 后残留空白)——均为代码注释中已声明的权衡,视觉级影响,不阻塞。
  • 未覆盖:逐 commit 归因(浅克隆仅可达 1/13 commits,验证的是聚合 diff);真实 GitHub 发布动作(沙箱无凭据,发布侧接缝已由编译产物直接驱动验证);settings.test.ts 的 12 个失败为环境预存(base 上逐字节相同,A/A 证明与本 PR 无关)。

Central claim and A/B proof

Central claim: the three operator-scoped settings change /review behavior as documented — configured effort applies under the precedence chain, the standing review.comment authorises posting without replacing the target binding, review.attribution gates the footer and its modelId validation — while defaults reproduce the base behavior exactly and a repository's .qwen/settings.json cannot set any of the three.

Method: each cell runs the compiled dist of both builds through the real production seams — the parse-args --stdin handler with the real settings loader (temp HOME + temp workspace .qwen/settings.json, no mocks), the shared authorisation gate, composeReview, and runSubmit's refusal path. Base control = scratch worktree at HEAD^1 with only packages/cli rebuilt (tsc --build + asset copy). Control purity asserted: git diff HEAD^1..HEAD touches no package.json/lockfile, and the only internal-dependency change is packages/core/src/skills/bundled/review/SKILL.md (a markdown asset, no runtime code), so the base cells resolving @qwen-code/qwen-code-core via the root node_modules link load code-identical core; readlink -f node_modules/@qwen-code/qwen-code-core and the per-package node_modules symlinks used for the vitest control are quoted in Methodology.

Table 1 — settings through the real handler (head vs base). Witness: 01-settings-ab-head-vs-base.png

Cell user settings.json workspace settings.json raw head base
S1 {} {} 6711 high/default, comment f/f identical (defaults preserved)
S2 effort medium {} 6711 medium/configured high/default (no consumer)
S3 effort Low {} src/foo.ts --fix medium/forced-by-fix (case-normalized, floored) medium/default
S4 comment true {} 6711 high/default, comment f/t (PR default already high — nothing to force) high/default, f/f
S5 comment true, effort low {} 6711 high/forced-by-comment, warning names review.comment high/default
S6 comment true {} src/foo.ts medium/default, f/f, no warnings (inert on local) identical
S7 {} comment/attribution/effort set 6711 high/default, f/f — workspace ignored identical
S8 effort medium effort low 6711 medium/configured (user scope wins; workspace skipped) high/default
S9 effort auto {} 6711 high/default, no warning identical
S10 effort bogus {} 6711 high/default + Invalid review.effort value "bogus" in settings high/default, no warning
S11 {} {} 6711 --effort low --comment high/forced-by-comment identical
S12 effort medium {} 6711 --effort low low/explicit (flag beats configured) identical
S13 effort HIGH {} '' high/configured (case-insensitive) medium/default

81/81 assertions; 6 cells byte-identical head==base (the default-preservation oracle). Two mid-round harness-expectation corrections (S4's effortSource, and the ladder rung, below) were my misreadings of by-design behavior that the PR's own tests pin; the corrected expectations are the ones tabled.

Table 2 — the shared authorisation gate. Witness: 02-gate-ab-setting-authorises.png

Cell recorded args request head base
G1 6771 pr 6771, setting on ok, why names the setting + #6771 refused (no consumer)
G2 6771 --comment pr 6771 ok identical (flag path preserved)
G3 9999 pr 6771, setting on refused — binding holds (#9999#6771) refused
G4 src/foo.ts pr 6771, setting on refused — "do not name a pull request" (real blocker) refused, blames a missing --comment (the misdirection head fixes)
G5 6771 pr 6771, no setting refused identical
G6 PR URL repo matches, setting on ok refused
G7 PR URL repo mismatch, setting on refused — repo binding holds refused
G8 (missing file) setting on refused, fail-closed identical
G9 6771 --user-authorized ok identical

28/28. The setting replaces the flag, never the binding (G3/G7), and the refusal wording names the real blocker (G4).

Table 3 — compose/attribution and the refusal advice

  • C1 (18/18 with C2/C3): attribution off composes with an empty modelId (its only consumer is gated off) and no footer; attribution on still refuses an empty modelId exactly like base; default-on output is byte-identical to base across three input shapes.
  • C3: forged footers in drafted bodyCriticals are stripped under both attribution arms; attribution on leaves exactly one (canonical) footer.
  • R1–R3 (12/12): runSubmit's refusal advice matches the refusal class on head ("invoked naming it" for binding refusals, flag advice only for never-requested); base advised Re-run with --comment for binding refusals too — the futile-retry misdirection the head commit fixes, demonstrated on the control arm.

Table 4 — footer-strip scaling ladder (the head commit). Witness: 03-strip-ladder-base-quadratic-head-bounded.png

Marker-carrying body, long whitespace run, no trailing footer (a quoted/truncated forged footer — the natural output of the loop the strip exists for):

N (whitespace run) head ms base ms (the unbounded replace)
2 000 2 2
3 000 4 4
5 000 11 11
20 000 29 169
80 000 29 2 704
160 000 29 10 873

Base grows ~16× per 4× input (quadratic, matching the PR's "~4× per doubling" probe); head is flat at 29 ms — the 8192-char tail bound holds. Marker-less bodies: 0 ms on both arms at 200k (the pre-existing marker guard is intact on head). Trailing footer after a 20k/80k run: stripped, fast, on head.

Mutation matrix (vacuity)

Each mutant applied in the working tree, targeted suite run, tree restored byte-identical (git status clean after). Unmutated control green (69 review files, 2478 pass).

Mutant Suite Result
M0 attribution fallback true→false (positive control) review-settings.test killed — 3 failed / 6 passed, exactly the default tests
M1 delete skipWorkspaceSettings: true review-settings + parse-args + submit killed — 10 failed / 141 passed (scope assertion + all wiring tests)
M2 delete the tail bound (tail = body) review-footer.test killed — 1 failed / 9 passed, the bounding test at 17 851 ms vs its 2 000 ms ceiling; the marker-guard test stayed green (correct fine attribution)
M3 delete severityOf(c) !== null from presubmit's authorship fallback presubmit.test killed — 1 failed / 54 passed, exactly "does not author-match hand-written top-level comments"
M4 delete defaultComment: review.comment in the submit handler submit.test killed — 1 failed / 63 passed, exactly the handler wiring test
M5 delete the configuredEffort branch parse-args.test killed — 9 failed / 69 passed, all in the configured-effort area

No survivors. Every kill is attributable to the test the PR says pins that guard.

Targeted gates

  • packages/cli src/commands/review: 69 files, 2478 passed, 4 skipped, 0 failed.
  • src/commands/review src/config: 107 files, 3652 passed, 4 skipped, 12 failed — all in src/config/settings.test.ts, a file this PR does not touch, and byte-identical on the base build (same 12 test names, A/A control). Pre-existing environment-dependent failures (home-dir/${VAR}-from-.env resolution in this container), not attributable to the PR; listed in Not covered.
  • npx tsc --noEmit (packages/cli): exit 0.
  • npm run generate:settings-schema: regenerated settings.schema.json is byte-identical to the committed file (0-line diff) — the "regenerated" claim verified.

Findings

F1 (informational, non-blocking) — the tail bound trades completeness for linearity past ~200 accumulated footers. A 300-footer chain (11 698 chars > the 8 192 tail) strips to 3 508 chars: the leading fragment of the chain survives, whereas the base's unbounded replace stripped all of it. The code comment names exactly this tradeoff ("far past any real re-compose loop"), and the unbounded alternative is the measured quadratic hazard (Table 4). A re-compose loop emitting 200+ footers would post a visible fragment of stale footers — cosmetic, and the canonical-footer safety properties still hold (the trailing footer is stripped/appended per the rules in every probe).

F2 (informational, non-blocking) — residual trailing whitespace when a >8k whitespace run precedes a trailing footer. a finding + 20 000 spaces + footer strips the footer but leaves 11 845 trailing spaces (a full-body $-anchored \s* would have left 0). GitHub renders trailing whitespace as nothing; the safety property (no footer survives) holds. Both F1/F2 require adversarial whitespace/loop shapes in model-drafted text and are bounded by construction at the 8192 window.

Not covered

  • Per-commit attribution: the checkout is depth-2; git rev-list HEAD^1..HEAD^2 reaches 1 of the 13 commits in the metadata snapshot (shallow boundary). The aggregate HEAD^1..HEAD diff is what was verified.
  • Real GitHub posting: no credentials by design. The posting-side seams (gate verdicts, composed bodies, strip behavior, refusal advice) were driven from the compiled dist; the gh write path itself is covered by the PR's own mocked unit tests, not by this round.
  • /settings dialog UI: not exercised interactively (no TUI change in the PR); dialog membership of the three keys is pinned by the suite (getDialogSettingKeys test, green).
  • 12 pre-existing settings.test.ts failures: environment-dependent (real-HOME/.env resolution in this container). Proven unrelated by the A/A control; the PR's CI on GitHub runners presumably exercises them in a compatible environment.
  • Reviewer Test Plan step 2 (manual post against a scratch PR): cannot be performed here (no token); its observable seams are the harness cells above.

Methodology

Environment: the CI verify container (node:22-bookworm), npm ci + npm run build pre-run at head; base control rebuilt only packages/cli in a scratch worktree at HEAD^1 (removed after the captures). Purity checks quoted: git diff HEAD^1..HEAD --name-only shows no package.json/lockfile change and only packages/core/src/skills/bundled/review/SKILL.md among internal deps; readlink -f of node_modules/@qwen-code/qwen-code-core from the base tree points at the head tree's core, whose runtime code is unchanged, and the vitest base control additionally received per-package node_modules symlinks (deps identical, lockfile untouched). Harnesses (harness/*.mjs, rerunnable) drive compiled dist through real seams — the yargs parse-args --stdin handler with temp HOME/workspace settings files and no mocks, reviewWriteAuthorization with session id cleared, composeReview, runSubmit's refusal path, and stripReviewFooter vs a faithful reconstruction of base's private strip from base's own exported constants; every timing rung ran in an isolated child under a 30 s cap. Raw logs: logs-0*.txt; captures: evidence/*.png. Assertion counts come only from the five harness runs (81+28+18+12+28 = 167); mutation and gate numbers are vitest's own, quoted per run above.

Evidence images

01-settings-ab-head-vs-base

02-gate-ab-setting-authorises

03-strip-ladder-base-quadratic-head-bounded

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

Qwen Code · sandboxed verification

@wenshao
wenshao enabled auto-merge August 13, 2026 13:16

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

Approved at f77b448. Verified independently, not just from prior rounds:

  • Schema & validation: settingsSchema.ts + regenerated settings.schema.json agree (types, defaults, effort enum). operatorReviewSettings() re-validates hand-edited values (non-boolean attribution → default true, non-boolean comment never enables posting, non-string effort dropped) — invalid settings fail safe instead of misbehaving. All three carry the operator-scope-only note per repo convention.
  • Precedence: code reads explicit --effort > configured > built-in, with --comment-forces-high and --fix-floors-medium overriding the configured value; tests pin every leg including handler wiring, case normalisation ("Low"), invalid-configured warning, and 'auto' → built-in without warning.
  • Back-compat: defaults (attribution: true, comment: false, effort auto→built-in rule, defaults = {}) reproduce today's behaviour exactly when no settings are present; pinned by the resolver and parse-args suites.
  • Security: settings resolve with skipWorkspaceSettings: true, so a repo cannot set review policy for its reviewers; the review.comment setting substitutes the flag in the gate only — target binding is untouched, and both gate callers (submit, publish-assets) resolve it identically.
  • Prior Criticals closed at head: R5-1's quadratic footer strip is now stripReviewFooter with marker guard + STRIP_TAIL_LIMIT tail bound (the $-anchored regex can only match in the tail, covering the marker-present/no-trailing-footer class); presubmit dedup stays sighted attribution-off via the trimmed severityOf authorship fallback (me defined at presubmit.ts:466, ahead of the filter).

Non-blocking nits (all post-head, round-7 Suggestion-level, none gate this):

  1. The /settings dialog toggles persist at Workspace scope into the repo .qwen/settings.json, where they are silently ignored — consider the showInDialog: false convention of the pre-existing operator-only keys (or a load-time warning). Security direction is safe as-is.
  2. The two-way refusal-advice split in runSubmit still misroutes the missing-args-file refusal when review.comment is on, and the PR-less/setting-off refusal — advice-only, fail-closed behaviour is correct.
  3. Two test-rationale comments overclaim what their tests pin (footer marker guard; publish-assets tripwire) — comment hygiene only.

CI on head sha: green — all checks success/skipped (Integration Tests (CLI, No Sandbox) skipped by design, merge-queue-only).

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.12.

samuelhsin pushed a commit to samuelhsin/qwen-code that referenced this pull request Aug 18, 2026
…ew.attribution (QwenLM#9027)

* feat(cli): Add review settings for attribution, default effort, and default comment

* fix(cli): resolve review settings from operator scopes and close gate gaps (QwenLM#8994)

Address review feedback on the review settings:

- Resolve review.attribution/effort/comment from operator-controlled
  settings scopes only (system defaults, user, system); a repository's
  .qwen/settings.json is content under review and must not control
  whether findings publish, whether the review names its model, or how
  deeply the pipeline verifies.
- Normalize the configured review.effort through the same case-
  insensitive validation as the --effort flag, so "Low" cannot miss the
  exact comparisons the forcings run and invalid values cannot leak into
  the verdict.
- Gate the modelId requirement and footer-safety validation on
  attribution: with the footer gated off, the field has no consumer and
  must not refuse the run.
- Pass the standing review.comment setting into publish-assets' call of
  the shared authorisation gate, so both callers agree on what
  authorises a run.
- Make presubmit's self-comment detection footer-independent by also
  matching the reviewing account's own top-level comments, so
  attribution-off posts still dedup.
- Align SKILL.md's Step 7 gate and every --comment branch on
  comment.effective, and add handler-level wiring tests for all
  configured defaults.

* test(cli): pin the review-settings operator defaults with unit tests (QwenLM#8994)

* fix(cli): share the guarded footer strip and pin the gate audit text (QwenLM#8994)

* fix(cli): raise the repository-context array bound to 256 (QwenLM#8994)

* fix(cli): validate review setting values and tighten the review gates (QwenLM#8994)

* feat(cli): drop the AI template tells from unattributed /review posts

review.attribution: false already drops the footer; the posted text still
read as machine output. With attribution off, inline comments now post
without the **[Critical]**/**[Suggestion]** prefixes and are written as
plain reviewer prose, the review body loses its fixed template markers
(LGTM! ✅, the ⚠️ glyph, the **[Critical]** bullets in body lists), and
the Step 1 verdict carries the attribution flag so the orchestrator can
pick its register. The severity strip happens in the final post object
only — counting, the unmarked gate, and the ledger all still run on the
marked payload, so verdict semantics are unchanged and the default mode
is byte-identical to before.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): align presubmit dedup with severityOf and normalize auto effort (QwenLM#8994)

* feat(cli): make plain prose the only register for posted /review text

The template voice is worse prose for every audience, not just the
unattributed one, so the phrasing now goes plain unconditionally: comment
bodies drop the '— Failure scenario: trigger → outcome' label and arrow
notation (the evidence rule is unchanged — trigger and wrong outcome must
be in the sentences), and the fixed review-body copy loses LGTM! ✅ and
the ⚠️ glyph in both modes. What still follows review.attribution is the
machine-readable layer — the severity prefixes and the footer — because
qwen-autofix.yml's Critical-only mode greps posted bodies for the literal
**[Critical]** marker. With prose unconditional there is no register to
branch on, so the parse-args verdict's attribution field goes away again;
submit keeps stripping prefix and footer at post time when the operator
turned attribution off.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): keep the copy humans actually write — restore LGTM! and the warning glyph

Readability, not concealment, is the criterion: reviewers type LGTM! and
reach for the ⚠️ glyph on a warning line every day, and both aid scanning.
The earlier commits stripped them along with the real scaffolding, which
overshot. What goes is only the labelled failure-scenario template; the
fixed review-body copy is now byte-identical to before in both attribution
modes, and the pr-context LGTM filter needs no change after all.

* fix(cli): close the review-found gaps in the unattributed post path

CI review on the PR found three real defects and four test/doc gaps in the
first two commits; all addressed:

- presubmit dedup went blind to attribution-off posts: the authorship
  fallback gated on severityOf, but submit strips exactly that prefix, so a
  later round re-posted its own findings as duplicates. Attribution-off
  comments now carry an invisible <!-- qwen-review --> marker and presubmit
  matches on it — from any account, which also closes 8994's documented
  other-accounts gap.
- The attribution-off body-Critical branch quoted entries verbatim, leaking
  a model-written **[Critical]** marker into the posted body; it now strips
  like the inline path does.
- The SKILL.md payload example still showed the labelled template the
  rewritten body-format paragraph forbids; both examples now show plain
  prose.
- A comment that is nothing but its severity marker is refused at the
  consistency gate instead of posting the bare marker.
- Forged footers followed by text survived the trailing-anchored strip and
  were the only attribution an unattributed post carried; the off leg now
  strips footer-shaped lines regardless of position.
- The setting's description now names both stripping consequences (autofix
  Critical-only deferral; the invisible marker), and the loosened
  clean-approve test assertions are exact again.

* test(cli): pin the reply guard with a finding-shaped reply fixture

The unmarked reply body was excluded by the severityOf gate even with the
reply guard deleted, so nothing pinned the guard itself (mutation-verified
by CI review).

* fix(cli): close the round-3 review findings on the unattributed post path

Five Criticals and twelve Suggestions from the third CI review round, all
addressed:

- presubmit dedup: the invisible-marker branch was ungated — any account
  could plant the public marker string on a line expecting a blocker and
  have the next round silently withhold it. The branch now requires
  authorship by the reviewing account plus the exact trailing shape submit
  posts; adversarial and quote-reply fixtures pin both guards. The
  'other accounts escape dedup' disclosure from 8994 stands again.
- The marker-only gate was dead under attribution ON (the canonical footer
  was appended before the check) and stacked markers bypassed it: the
  strip is now iterative, delegates the classification to severityOf, and
  the gate refuses when the footer-and-marker-stripped remainder is empty
  or still marker-led.
- bodyCriticals and cannot-tell entries now get the forged-footer strip on
  the unattributed leg (a surviving mid-entry footer was the post's only
  attribution), the cannot-tell parse trims before matching, and ledger
  titles strip the marker (the ledger rides the body as an HTML comment
  the autofix grep reads).
- stripForgedFooterLines rewritten line-based: closing underscore optional
  (looping-model truncation), CRLF tolerated, 400-char line bound, fence-
  and indented-code aware, and byte-identical when nothing matches.
- The comment marker now carries severity (<!-- qwen-review critical -->);
  pr-context's blocker promotion reads it, so an unresolved unattributed
  Critical re-enters the re-check section every round — including past the
  ledger's horizon.
- Tests: stripForgedFooterLines unit coverage, grouped cannot-tell strip,
  ledger leg under a prNumber plan, and the adversarial presubmit shapes;
  loosened assertions re-tightened. Docs and the settings description now
  match the shipped behavior.

* fix(cli): close the round-4 review findings — marker read/write hardening

Seven Criticals and four Suggestions from the fourth CI review round:

- commentMarkerSeverity now reads only the trailing posted shape, and
  submit strips pre-existing bare marker lines before appending the
  canonical marker — a marker string quoted or planted in a reviewed file
  can no longer choose the severity the classifiers see.
- The marker disjunct in the blocker classification is gated on the
  reviewing account, via one shared predicate (isBlockerBody) now used by
  BOTH pr-context and comment-status — an empty planted 'critical' comment
  no longer becomes a permanent irrefutable blocker, and the two consumers
  can no longer diverge on the posted shape.
- The ledger's drafted-comments leg strips like the bodyCriticals leg
  (iterative markers, forged footer lines first, footer spans off the
  title), and stripSeverityPrefix now strips to empty for marker-only
  bodies — the submit gate refuses exactly that shape, in both modes.
- The fence scanner is a faithful model now: ~~~ fences count, a fence
  opener indented 4+ spaces does not open one, and lines inside a simple
  HTML block never toggle fence state.
- Producer/consumer roundtrip tests pin the marker shape (the drift class
  the module header exists to prevent); the iterative strip, the
  attribution-on marker-only gate, and the strip order each carry the
  assertion the mutations showed missing.

* fix(cli): make the unattributed strip a fixpoint, closing the round-5 escapes

Seven Criticals from the fifth CI review round, all probe-verified escape
hatches in the strip chain, closed by restructuring it:

- One shared stripForUnattributedPost iterated to a fixpoint now serves
  every attribution-off leg (submit's post transform and gate, compose's
  body lists, both ledger legs), so the sites cannot drift on order:
  forged footer lines, severity prefixes (leading AND paragraph-initial,
  via a new fence-aware stripParagraphMarkers), bare marker lines, and
  footer spans interleave arbitrarily in a looping draft and only the
  fixpoint posts none of them.
- The marker-only gate runs the full chain: a prefix over a bare marker
  line no longer posts an empty visible comment carrying a live marker.
- Marker-only body Criticals and cannot-tell entries are refused at
  compose (both modes), mirroring submit's gate — an empty-stripped entry
  no longer counts toward REQUEST_CHANGES while rendering nothing.
- The version-parens truncation (the natural mid-character cut) is
  admitted by all three footer regexes; blockquoted forged footer lines
  strip; HTML blocks stop shielding footer lines (their content renders
  visibly) while still not toggling fence state.
- The design doc's definitional line now says what ships: no VISIBLE
  attribution — the machine contract moves to the invisible severity
  marker.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): drop the dead bare-marker arm and tolerate whitespace before the colon

- submit's post transform no longer references COMMENT_MARKER: the
  unmarked gate runs first, so every body reaching the transform has a
  known severity and posts the severity-carrying marker unconditionally.
- stripSeverityPrefix tolerates whitespace before the colon after a
  marker and treats a whitespace-only remainder as empty (a trailing
  newline no longer survives as a phantom non-empty body).

* fix(cli): close the round-6 findings — faithful fences, render-nothing gates

Seven Criticals from the sixth CI review round:

- mapLinesAware now applies the map inside HTML blocks (the round-5 fix
  updated the comment but not the code — the shield stood), tracks the
  opening fence by delimiter character and run length with no info string
  on the closer (CommonMark), and the chain's final span pass is line-aware
  so fenced quotations survive the full strip.
- The emptiness gates (submit + both compose lists) project through a new
  rendersAsNothing — Cf characters, HTML comments, hollowed fences, and
  forged-footer residue are not content — and run the full post-transform
  chain, so a scaffolded-but-invisible comment can no longer post, count
  toward REQUEST_CHANGES, and re-promote as an unanswerable blocker.
- stripCommentMarkerLines admits blockquoted marker lines, matching its
  sibling regexes.
- buildLedger keeps the carried id and title when the finding text starts
  on the line after the severity marker (trimStart before titleOf) — a
  regression from routing titles through the new chain.

* fix(cli): close the round-7 findings — faithful quotes, code spans, render-nothing classes

* fix(cli): close the round-8 findings — one displayed projection for every strip

* fix(cli): close the round-9 findings — fail-closed identity, one shape per leg

* fix(cli): close the round-10 findings — rendered-text signals, bounded spans, fence-safe entries

- blocker prose scan reads only rendered text: an HTML comment renders as
  nothing, so a planted `<!-- [critical] -->` can no longer promote an
  invisible, irrefutable blocker through the ungated channel
- the footer-span version group admits only the version shape
  footerVersion validates — a span truncated inside the parens can no
  longer swallow the prose after it
- the marker strips admit the full-width colon, closing the marker-only
  refusal's ASCII-only hole in bilingual drafts
- entries containing a code-fence delimiter line are refused for redraft:
  the one-line collapse turns them into an unclosed fence that swallows
  the rest of the posted body
- the identity fail-closed trigger narrows to what identity actually
  gates — critical markers on root comments — so a planted reply cannot
  convert a transient identity blip into a repeating refusal
- the ledger's carried-id anchor reads through render-nothing residue
  left between the marker and the id, ending the silent renumbering
- the marker-only contract covers trailing Cf/comment residue

* fix(cli): close the round-12 Criticals — bounded footer version, drop-scoped blank cleanup, quote-preserving markers, gate-matched post leg

* fix(cli): close seven review sanitation entrances from round-20 review (QwenLM#9027)

- CR-aware line model: scanLines and rendersAsNothing split on CR/CRLF;
  entry lists normalize line endings on ingest, so a bare CR can no
  longer hide a forged footer, a hollow fence, or a fence delimiter
  from the refusal and emptiness gates (R20-1)
- empty-login identity lookups fail closed like thrown ones in both
  pr-context and comment-status while a critical marker is posted (R20-2)
- whitespace-only body-list entries fail the renders-nothing gates
  instead of vanishing before them; the dead raw sha check drops (R20-4)
- drop-collapse never touches blank runs around an HTML-block content
  drop — quotation blanks render and survive (R20-6)
- attribution-off posts refuse drafts whose post-strip shape leaves a
  fence open at the appended invisible marker (R20-9)
- carriedClaimLine slices on the classifier's projection and both
  colon widths; presubmit reads carried ids off the attribution-off
  posted shape (R18-1)
- duplicates disclosure routes through the attribution-off fixpoint
  chain like every other body leg (R15-1)

---------

Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
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.

5 participants