Skip to content

feat(review): Add Maven multi-module verification - #8394

Closed
wenshao wants to merge 31 commits into
QwenLM:mainfrom
wenshao:refactor/review-toolchain-adapter
Closed

feat(review): Add Maven multi-module verification#8394
wenshao wants to merge 31 commits into
QwenLM:mainfrom
wenshao:refactor/review-toolchain-adapter

Conversation

@wenshao

@wenshao wenshao commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds deterministic Maven multi-module verification to /review on top of the internal toolchain adapter boundary introduced by the first commit. review build-test now recognizes root Maven reactors, maps changed files to the deepest default-reactor module, prefers the root Maven Wrapper, runs scoped test or build-only test-compile commands from the reactor root with Maven upstream/downstream expansion, and emits fresh module-qualified Surefire/Failsafe evidence. Successful Maven build-only results are accepted by base-tree preparation, Test Plan checks understand Maven commands and report counts, and Agent 7 handles Maven infrastructure failures without routing them through the npm-only test-delta path.

The implementation fails closed for ambiguous npm+Maven roots, unsafe aggregation paths, profile/plugin module declarations, and standalone or profile-inactive Maven projects outside the root reactor. It does not claim full Maven effective-model or CI-matrix parity.

Why it's needed

The existing /review build-and-test path produced deterministic evidence for npm projects but handed Maven projects back to an agent for ad hoc command selection. That made Java verification inconsistent and provided no reliable module ownership, downstream-consumer selection, timeout classification, or fresh test-report evidence. This work is driven by active use in Alibaba Fastjson2 and Druid: both are real multi-module Maven reactors where core changes must be checked through the root wrapper and where nested, profile-activated, and standalone POMs make naive POM scanning unsafe.

Reviewer Test Plan

How to verify

  1. Run cd packages/cli && npx vitest run src/commands/review/build-test.test.ts src/commands/review/base-tree.test.ts src/commands/review/test-plan.test.ts src/commands/review/agent-prompt.test.ts src/commands/review/lib/npm-toolchain.test.ts src/commands/review/lib/maven-toolchain.test.ts; expect all 326 tests to pass.
  2. Confirm a changed module produces one root-cwd Maven command such as ./mvnw --batch-mode --no-transfer-progress -pl core -am -amd test, while root Maven configuration changes run the full reactor and build-only mode uses test-compile.
  3. Confirm profile/plugin <module> elements are excluded from default-reactor discovery, nested direct modules are discovered recursively, and a changed standalone or profile-inactive Maven project returns toolchain: "unsupported" without running a root command, including a profile-only child project nested under an active reactor module.
  4. Confirm only newly created or updated Surefire/Failsafe XML is appended as module-qualified evidence; stale XML is ignored, dependency/plugin acquisition remains infrastructure even if an earlier module emitted a fresh report, and a changed wrapper permission failure is not hidden as infrastructure.
  5. Confirm Test Plan matching allows a bare ./mvnw test claim to be settled by the scoped command actually run, but does not equate claims carrying different -P profiles or -pl scopes.
  6. Inspect the Fastjson2 and Druid acceptance smoke evidence: default reactors parse without profile-only modules, core maps to the root wrapper command, and Druid's standalone druid-admin project fails closed.

Evidence (Before & After)

Before: the installed baseline returned toolchain: "unsupported" for a two-module Maven fixture and did not execute its wrapper.

After: the current implementation returned toolchain: "maven", selected app, and executed the fixture wrapper from the reactor root with -pl app -am -amd test. Read-only acceptance smoke checks parsed the default Fastjson2 and Druid reactors and generated the expected root-wrapper command for each repository's core module.

Tested on

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

Environment (optional)

macOS, Node.js 22.22.2. Focused result: 6 test files passed, 326 tests passed. Prettier, ESLint, commit hooks, and git diff --check passed. A workspace-owned fake-wrapper fixture exercised the real runBuildTest execution path without network access. Fastjson2, Druid, Trino, and Flink were used for read-only reactor parsing and injected command-generation acceptance checks; their real Maven suites were not executed. The Flink smoke check exposed a profile-only child project nested under an active reactor module; a reproduce-first fixture confirmed the parent-only command could pass without compiling broken child source, and the adapter now fails closed before running Maven. The source CLI entry was blocked by an unrelated unbuilt ACP Bridge dist file. Repository-wide npm run build and CLI npm run typecheck were attempted but remain blocked by unrelated existing dependency and cross-package type drift, including the missing @octokit/rest dependency and existing ACP/Ink API mismatches; no reported error referenced the files changed by this PR.

Risk & Scope

  • Main risk or tradeoff: Maven reactor discovery intentionally models only literal direct <project>/<modules>/<module> aggregation. Maven remains responsible for dependency ordering and -am/-amd expansion; the report does not claim a recursively computed complete dependency graph.
  • Not validated / out of scope: Gradle, Maven effective-model evaluation, automatic profile activation, JDK/OS matrices, Failsafe verify execution, JaCoCo, mutation testing, Maven-aware base-side test-delta, real network-dependent Fastjson2/Druid builds, and mixed-toolchain aggregation.
  • Breaking changes / migration notes: BuildTestReport.toolchain adds the "maven" discriminant. Existing npm CLI behavior and report fields remain compatible; roots where both npm and Maven apply now fail closed instead of choosing one implicitly.

Linked Issues

N/A

中文说明

此 PR 的改动

此 PR 在首个 commit 引入的内部 toolchain adapter 边界之上,为 /review 增加确定性的 Maven 多模块验证。review build-test 现在可以识别根 Maven reactor,将变更文件映射到默认 reactor 中最深的模块,优先使用根 Maven Wrapper,从 reactor 根目录执行带 Maven 上游/下游扩展的 scoped test 或仅构建 test-compile 命令,并输出仅属于本次执行的、带模块路径的 Surefire/Failsafe 证据。base-tree 可以接受成功的 Maven build-only 结果,Test Plan 可以识别 Maven 命令和测试计数,Agent 7 会正确处理 Maven 基础设施失败,同时不会把 Maven 失败送入仅支持 npm 的 test-delta 路径。

对于 npm 与 Maven 同时适用的根目录、不安全的聚合路径、profile/plugin 中的 module 声明,以及不属于根 reactor 的独立或当前 profile 未激活 Maven 项目,实现都会 fail closed。此 PR 不声称实现完整 Maven effective model 或完整 CI matrix 对齐。

为什么需要

现有 /review build-and-test 路径能为 npm 项目生成确定性证据,但 Maven 项目只能交还给 agent 临时选择命令。这使 Java 验证不一致,也缺少可靠的模块归属、下游消费者选择、超时分类和新鲜测试报告证据。本工作由 Alibaba Fastjson2 和 Druid 的实际使用驱动:两者都是真实 Maven 多模块 reactor,core 变更必须通过根 wrapper 验证,而嵌套模块、profile 激活模块和独立 POM 使简单扫描所有 POM 的方案不安全。

Reviewer Test Plan

验证方式

  1. 运行 cd packages/cli && npx vitest run src/commands/review/build-test.test.ts src/commands/review/base-tree.test.ts src/commands/review/test-plan.test.ts src/commands/review/agent-prompt.test.ts src/commands/review/lib/npm-toolchain.test.ts src/commands/review/lib/maven-toolchain.test.ts,预期 326 个测试全部通过。
  2. 确认模块变更只生成一个从 reactor 根目录执行的 Maven 命令,例如 ./mvnw --batch-mode --no-transfer-progress -pl core -am -amd test;根 Maven 配置变更运行完整 reactor,build-only 模式使用 test-compile
  3. 确认 profile/plugin 中的 <module> 不会进入默认 reactor,嵌套的直接 module 会递归发现,而变更独立或 profile 未激活 Maven 项目时返回 toolchain: "unsupported",且不会执行根命令,包括嵌套在活跃 reactor module 目录下的 profile-only 子项目。
  4. 确认只有本次新建或更新的 Surefire/Failsafe XML 会作为带模块路径的证据追加;旧 XML 被忽略;即使前序模块已生成新报告,依赖/plugin 获取失败仍归类为基础设施;wrapper 权限变更导致的失败不会被隐藏成基础设施问题。
  5. 确认 Test Plan 中裸 ./mvnw test 可以由实际执行的 scoped 命令结算,但带不同 -P profile 或 -pl scope 的命令不会被错误视为同一次执行。
  6. 检查 Fastjson2 和 Druid 的验收 smoke 证据:默认 reactor 可解析且不包含 profile-only 模块,core 映射到根 wrapper 命令,Druid 的独立 druid-admin 项目正确 fail closed。

证据(改动前后)

改动前:已安装的基线版本对双模块 Maven fixture 返回 toolchain: "unsupported",且没有执行 wrapper。

改动后:当前实现返回 toolchain: "maven",选择 app,并从 reactor 根目录执行 fixture wrapper,参数为 -pl app -am -amd test。只读验收 smoke 检查成功解析 Fastjson2 和 Druid 的默认 reactor,并为两个仓库的 core 模块生成预期的根 wrapper 命令。

测试平台

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

环境(可选)

macOS,Node.js 22.22.2。聚焦验证结果:6 个测试文件通过,326 个测试通过;Prettier、ESLint、commit hooks 和 git diff --check 通过。workspace 内的假 wrapper fixture 在不访问网络的情况下执行了真实 runBuildTest 路径。Fastjson2、Druid、Trino 和 Flink 用于只读 reactor 解析及注入执行器的命令生成验收,未实际运行其 Maven 测试套件。Flink smoke 检查发现了嵌套在活跃 reactor module 下的 profile-only 子项目;reproduce-first fixture 证明父模块命令可以在完全未编译损坏子项目源码的情况下成功,adapter 现在会在执行 Maven 前 fail closed。源码 CLI 入口被无关的 ACP Bridge dist 未构建问题阻断。已尝试运行全仓 npm run build 和 CLI npm run typecheck,但仍被无关的既有依赖和跨包类型漂移阻断,包括缺少 @octokit/rest 和现有 ACP/Ink API 不匹配;错误输出中没有指向此 PR 修改的文件。

风险与范围

  • 主要风险或取舍:Maven reactor discovery 只建模字面量的直接 <project>/<modules>/<module> 聚合。依赖顺序和 -am/-amd 扩展仍由 Maven 负责;report 不声称已递归计算完整依赖图。
  • 未验证或范围外:Gradle、Maven effective model、自动 profile 激活、JDK/OS matrix、Failsafe verify 执行、JaCoCo、mutation testing、Maven-aware base-side test-delta、依赖网络的 Fastjson2/Druid 真实构建,以及混合 toolchain 聚合。
  • 破坏性变更或迁移说明:BuildTestReport.toolchain 新增 "maven" 判别值。现有 npm CLI 行为和 report 字段保持兼容;根目录同时适用 npm 与 Maven 时现在会 fail closed,而不是隐式选择一个。

关联 Issue

N/A

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

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

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: this is a structural refactoring — build-test.ts currently combines CLI routing, npm detection, and the full npm verification algorithm in one ~700-line module. The separation of concerns is observable. The stated motivation is future Maven/Gradle support, though no issue or concrete plan for that exists yet.

Direction: the extraction itself is reasonable — the file is long and the npm algorithm is self-contained. The adapter interface (28 lines) is minimal. That said, the design doc's "Future phases" (Maven, Gradle, coverage artifacts, multi-toolchain orchestration) is speculative — AGENTS.md's simplicity principle says "no abstractions for single-use code" and "no flexibility that wasn't requested." Since you're the maintainer this is your architectural call, but worth noting the tension.

Size: 1022 production logic lines (build-test.ts: 496, npm-toolchain.ts: 498, toolchain.ts: 28), 179 test lines, 233 docs. Above the 1000-line advisory — though the bulk is a verbatim move of existing code, not new logic.

Approach: the scope feels right for an extraction. One question: could this have been just "move npm logic to npm-toolchain.ts as a function export" without the ReviewToolchainAdapter interface and selectToolchainAdapter? The interface adds a layer of indirection for exactly one implementation. The answer may well be "the interface is the point" — but it's the one thing I'd push back on if this were a smaller PR.

Risk: no elevated risk signals (no high-risk paths matched).

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:这是一个结构性重构——build-test.ts 目前在一个约 700 行的模块中混合了 CLI 路由、npm 检测和完整的 npm 验证算法。关注点分离是可观察到的。所述动机是未来 Maven/Gradle 支持,但目前没有相关 issue 或具体计划。

方向:抽取本身合理——文件较长且 npm 算法是自包含的。adapter 接口(28 行)很精简。不过设计文档的"未来阶段"(Maven、Gradle、覆盖率产物、多 toolchain 编排)是推测性的——AGENTS.md 的简洁原则说"不为单次使用的代码创建抽象"、"不添加未被要求的灵活性"。作为维护者这是你的架构决策,但值得注意这个张力。

规模:1022 行生产逻辑(build-test.ts: 496, npm-toolchain.ts: 498, toolchain.ts: 28),179 行测试,233 行文档。超过 1000 行建议阈值——但大部分是现有代码的逐字移动,不是新逻辑。

方案:对于抽取来说范围合理。一个问题:是否可以只做"将 npm 逻辑移到 npm-toolchain.ts 作为函数导出",而不需要 ReviewToolchainAdapter 接口和 selectToolchainAdapter?接口为恰好一个实现增加了一层间接。答案可能是"接口就是重点"——但如果这是更小的 PR,我会在这点上推回。

风险:无升级风险信号(未匹配高风险路径)。

进入代码审查 🔍

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: given "build-test.ts is too long and combines multiple concerns," I would extract the npm verification algorithm into its own module as a plain function export — runNpmToolchain(args): BuildTestReport — and have runBuildTest call it after a simple existsSync(join(root, 'package.json')) check. No interface, no adapter registry, no selection function. One file, one function, one call site.

Comparison with the diff: the PR does exactly that extraction, then wraps it in a 28-line adapter interface (ReviewToolchainAdapter + selectToolchainAdapter). The extraction itself is clean — the npm algorithm is moved verbatim, all comments preserved, the public API (runBuildTest, trimOutput, buildRunEnv, spawnTimedOut, unresolvedWorkspaceDeps) maintained via re-exports. The test changes strengthen assertions from partial shape checks to exact toEqual on the full report, which is strictly better.

No correctness bugs, security holes, or regressions found. The behavioral contract is preserved:

  • No package.json → generic unsupported report (now from build-test.ts directly, previously from the inline check)
  • Has package.json → npm adapter handles all finer-grained cases (unmodeled globs, no scripts, workspace selection, install policy, build widening, timeouts, disk preflights)
  • Report shape unchanged, CLI arguments unchanged

The only structural concern (already raised in Stage 1) is the adapter interface for a single implementation. It's 28 lines and doesn't add runtime complexity — selectToolchainAdapter is a one-liner .find(). Not a blocker.

No findings that block the merge.

Testing

This is an internal refactor with no user-visible behavioral changes — real-scenario testing: N/A.

CI evidence from the PR's own checks on 8320ddac:

Final CI results for 8320dda (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
route ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

The main test suite (ubuntu) is still running. macOS/Windows/Integration are skipped (typical for fork PRs). No failures so far. The author reports 42 focused tests passing locally (2 test files: build-test.test.ts + npm-toolchain.test.ts); this is the author's claim, not independently re-run here.

中文说明

代码审查

独立方案: 给定"build-test.ts 太长且混合了多个关注点",我会将 npm 验证算法提取到自己的模块中作为纯函数导出——runNpmToolchain(args): BuildTestReport——然后让 runBuildTest 在简单的 existsSync 检查后调用它。不需要接口、adapter 注册表或选择函数。一个文件、一个函数、一个调用点。

与 diff 的比较: PR 做了完全相同的提取,然后用 28 行 adapter 接口(ReviewToolchainAdapter + selectToolchainAdapter)包装。提取本身很干净——npm 算法逐字移动,所有注释保留,公共 API 通过 re-export 维持。测试变更将断言从部分形状检查加强为完整 report 的精确 toEqual,这严格更好。

未发现正确性 bug、安全漏洞或回归。行为契约保持不变。唯一的结构性顾虑(已在 Stage 1 提出)是为单一实现添加 adapter 接口。这是 28 行且不增加运行时复杂度。不是阻塞项。

测试

内部重构,无用户可见行为变化——真实场景测试:N/A。

CI 证据:主测试套件(ubuntu)仍在运行中。macOS/Windows/集成测试被跳过(fork PR 典型情况)。目前无失败。作者报告本地 42 个聚焦测试通过;这是作者的声明,未在此独立重新运行。

Qwen Code · qwen3.8-max-preview

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review, but the fork-refactor guardrail needs a maintainer's sign-off.

Stepping back: this is a well-executed mechanical extraction. The npm algorithm moves verbatim, comments and all, into its own module. The public API is preserved through re-exports. Tests are strengthened from partial shape checks to exact report assertions. No behavioral drift, no correctness issues, no regressions found.

The one genuine question is whether the adapter interface earns its place. My independent proposal would have been a plain function export — no interface, no selection function. The PR adds 28 lines of abstraction for one implementation, justified by a design doc describing Maven/Gradle adapters that don't exist yet. AGENTS.md's simplicity principle ("no abstractions for single-use code") points the other way. But the abstraction is tiny, doesn't add runtime complexity, and the author is the maintainer making the architectural call. Not a blocker — a genuine disagreement about taste.

The design doc is well-written and carefully scoped (explicit non-goals, "P0 does not…"), but its existence for a single-adapter extraction is the strongest signal that this is building for a hypothetical future rather than solving a present problem. If Maven/Gradle never materialize, the interface is dead weight; if they do, it's a reasonable foundation. That's a bet, not a defect.

⏸️ Deferring to @wenshao — the fork-refactor approval guardrail prevents auto-approval here (cross-repository PR with a refactor title). Since you're the author and have admin access, this is yours to self-approve and merge once CI lands green. No blocking issues found; the only reservation is the simplicity question above, which is your call to make.

中文说明

置信度:3/5 — review 干净,但 fork-refactor 保护规则需要维护者签字。

退一步看:这是一次执行良好的机械性提取。npm 算法逐字移动(包括所有注释)到独立模块。公共 API 通过 re-export 保持。测试从部分形状检查加强为精确 report 断言。未发现行为漂移、正确性问题或回归。

唯一真正的问题是 adapter 接口是否值得存在。我的独立方案是纯函数导出——不要接口、不要选择函数。PR 为一个实现增加了 28 行抽象,由一份描述尚不存在的 Maven/Gradle adapter 的设计文档来证明。AGENTS.md 的简洁原则("不为单次使用的代码创建抽象")指向另一个方向。但抽象很小,不增加运行时复杂度,且作者是维护者在做架构决策。不是阻塞项——是关于品味的真诚分歧。

设计文档写得很好且范围谨慎(明确的非目标、"P0 不做……"),但为单一 adapter 提取写设计文档本身是最强的信号:这是为假设性的未来构建,而非解决当前问题。如果 Maven/Gradle 永远不出现,接口就是死重;如果出现,它是合理的基础。这是一个赌注,不是缺陷。

⏸️ 转交 @wenshao — fork-refactor 审批保护规则阻止了此处的自动批准(跨仓库 PR 且标题为 refactor 类型)。由于你是作者且有 admin 权限,CI 全绿后可以自行批准合并。未发现阻塞问题;唯一的保留意见是上面的简洁性问题,由你决定。

Qwen Code · qwen3.8-max-preview

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

@wenshao

wenshao commented Aug 2, 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 2, 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-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 reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Test Plan (not a blocker): 42 tests to pass — this review observed 16965 passed.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 Test Plan(非阻断):42 tests to pass — this review observed 16965 passed

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

Comment thread packages/cli/src/commands/review/build-test.test.ts
Comment thread packages/cli/src/commands/review/build-test.ts Outdated
Comment thread packages/cli/src/commands/review/lib/npm-toolchain.ts Outdated
@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

⚠️ AutoFix round 11 ended without publishing a reportview run.

中文说明

⚠️ AutoFix 第 11 轮结束但未发布报告 —— 查看运行

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@wenshao wenshao changed the title refactor(review): Extract npm toolchain adapter feat(review): Add Maven multi-module verification Aug 2, 2026
wenshao and others added 2 commits August 2, 2026 19:01
Three test-efficacy gaps the review found, each a surviving mutant:

- runBuildTest's delegation to the selected adapter's run() was asserted
  only by report shape, so reverting the facade to the old inline
  implementation kept every test green. A spy on the adapter boundary now
  reddens on a revert and pins the forwarded arguments.
- The `?? run` exec fallback had zero coverage because every test injects
  a fake exec. A test that injects nothing now pins that the adapter
  receives a callable default.
- The single-root guard's negative side was untested: existing workspace
  fixtures use script-less roots, so forcing the guard on survived. A
  workspace repo whose root has build/test scripts now pins that the diff
  maps to the workspace, not the root package.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@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 轮)。改动内容与我反驳保留之处如下:

Autofix review round — PR #8394

Addressed all three inline suggestions from the automated reviewer. Each was a test-efficacy finding: a mutant that survived because the refactor's own tests asserted the report shape but not the behavior the refactor introduced. All three are early-round (round 1) suggestions, are valuable, are consistent with the suite's existing exec-seam / tmpdir conventions, and are in scope — so each is now pinned by one focused new test, and each new test was proven load-bearing by applying the exact mutant the reviewer named and confirming it turns red.

Feedback points

[rc:3699994608] build-test.test.ts — the refactor is inert to its own test file

  • Decision: Addressed.
  • Change: Added routes the run through the selected toolchain adapter (pins the delegation) in build-test.test.ts. It spies on npmToolchainAdapter.run, asserts it is called exactly once with the forwarded arguments (root, changedFiles, timeout, install, and a callable exec), and that the report runBuildTest returns IS the adapter's own return value (reference equality). Reverting the facade to the old inline implementation leaves the spy uncalled and turns this test red (verified).

[rc:3699994612] build-test.ts — the ?? run exec fallback has zero coverage

  • Decision: Addressed.
  • Change: Added defaults the adapter exec to the real runner when none is injected in build-test.test.ts. It mocks the adapter's run to capture the arguments it receives (so no real npm spawns), calls runBuildTest with no exec, and asserts the adapter receives a function. Dropping the ?? run fallback hands the adapter undefined and turns this test red (verified).

[rc:3699994615] npm-toolchain.ts — the single-root guard's negative side is untested

  • Decision: Addressed.
  • Change: Added does not treat a workspace repo as single-root when the root has scripts in npm-toolchain.test.ts. It builds a workspace monorepo whose root package.json also has build/test scripts (this repo's shape), changes a file inside a workspace, and asserts the diff maps to the workspace (affected = ['packages/a'], workspace-scoped build and test commands) rather than the root package. Forcing the guard to if (true) sets singleRoot, scopes the build to ., and turns this test red (verified).

Conflict notes

None — --conflict false; origin/main was not merged.

Verification

  • npx prettier --check on the two edited test files — passed ("All matched files use Prettier code style").
  • npm run lint — passed (exit 0).
  • npm run typecheck — passed (exit 0).
  • npm run build — passed (exit 0).
  • vitest run src/commands/review/build-test.test.ts src/commands/review/lib/npm-toolchain.test.ts (touched files) — 45 passed (build-test 40, npm-toolchain 5).
  • vitest run src/commands/review/ (whole review folder, touched package) — 51 files, 1613 passed | 3 skipped, 0 failed.
  • Test-efficacy (mutant) probes, each applied to the source and then reverted from HEAD:
    • Drop the ?? run fallback → defaults the adapter exec… failed (1 failed) — load-bearing.
    • Force the single-root guard to if (true)does not treat a workspace repo… failed (1 failed) — load-bearing.
    • Pass an empty adapter list (no delegation) → routes the run through the selected toolchain adapter… failed (1 failed) — load-bearing.
  • Integration tests after npm run bundle: not run — the change is unit-test-only and is not exercised through the bundled CLI or the integration harness.
  • npm run generate:settings-schema: not run — no settings source changed.
中文说明

Autofix 审查轮次 — PR #8394

已处理自动审查器提出的全部三条行内建议。每一条都是“测试有效性”发现:之所以有突变体存活,是因为本次重构自身的测试只断言了 report 的形状,却没有断言重构所引入的行为本身。这三条都属于早期轮次(第 1 轮)建议,都有价值、都与本套件既有的 exec 接缝 / tmpdir 约定一致、且都在范围内——因此每一条现在都由一个聚焦的新测试固定下来,并且每个新测试都通过“施加审查器所点名的那个确切突变体并确认其变红”证明了其有效性(load-bearing)。

反馈点

[rc:3699994608] build-test.test.ts — 此重构对其自身测试文件是惰性的

  • 决定: 已处理。
  • 改动:build-test.test.ts 中新增 routes the run through the selected toolchain adapter (pins the delegation)。它对 npmToolchainAdapter.run 打 spy,断言其恰好被调用一次、且传入的参数被原样转发(rootchangedFilestimeoutinstall,以及一个可调用的 exec),并断言 runBuildTest 返回的 report 正是 adapter 自身的返回值(引用相等)。若把 facade 回退为旧的内联实现,该 spy 将不会被调用,从而使本测试变红(已验证)。

[rc:3699994612] build-test.ts — ?? run 这个 exec 兜底零覆盖

  • 决定: 已处理。
  • 改动:build-test.test.ts 中新增 defaults the adapter exec to the real runner when none is injected。它 mock 掉 adapter 的 run 以捕获其收到的参数(从而不会真正 spawn npm),在不传 exec 的情况下调用 runBuildTest,并断言 adapter 收到的是一个函数。若删掉 ?? run 兜底,则会把 undefined 交给 adapter,使本测试变红(已验证)。

[rc:3699994615] npm-toolchain.ts — 单根(single-root)守卫的否定侧未被测试

  • 决定: 已处理。
  • 改动:npm-toolchain.test.ts 中新增 does not treat a workspace repo as single-root when the root has scripts。它构造一个根 package.json 同时带有 build/test 脚本的 workspace monorepo(正是本仓库的形态),在某个 workspace 内改动一个文件,并断言 diff 被映射到该 workspace(affected = ['packages/a']、带 workspace 限定的 build 与 test 命令),而不是根包。若把该守卫强制改为 if (true),会令 singleRoot 置真、把 build 限定到 .,从而使本测试变红(已验证)。

冲突说明

无 —— --conflict false;未合并 origin/main

验证

  • 对两个被修改的测试文件运行 npx prettier --check —— 通过(“All matched files use Prettier code style”)。
  • npm run lint —— 通过(exit 0)。
  • npm run typecheck —— 通过(exit 0)。
  • npm run build —— 通过(exit 0)。
  • vitest run src/commands/review/build-test.test.ts src/commands/review/lib/npm-toolchain.test.ts(被改动文件)—— 45 通过(build-test 40,npm-toolchain 5)。
  • vitest run src/commands/review/(整个 review 目录,被改动包)—— 51 个文件,1613 通过 | 3 跳过,0 失败。
  • 测试有效性(突变体)探针,每次都对源码施加突变、随后从 HEAD 还原:
    • 删掉 ?? run 兜底 → defaults the adapter exec… 失败(1 failed)—— 有效。
    • 把单根守卫强制改为 if (true)does not treat a workspace repo… 失败(1 failed)—— 有效。
    • 传入空的 adapter 列表(不委托)→ routes the run through the selected toolchain adapter… 失败(1 failed)—— 有效。
  • npm run bundle 之后的集成测试:未运行 —— 本次改动仅为单元测试,且不经由打包后的 CLI 或集成 harness 执行。
  • npm run generate:settings-schema:未运行 —— 未改动任何 settings 源。

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

⚠️ The branch received new commits while this round ran; they were merged into this push, but this round's verification predates that merge — re-check anything that landed mid-run. · 本轮运行期间分支收到了新的提交;本次推送已将其合并,但本轮验证在合并之前完成——请复查运行期间落地的改动。

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


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

…wenLM#8394)

The toolchain-adapter refactor rewrote Agent 7's brief to be npm/Maven agnostic, replacing the literal "npm run build" wording with a pointer to `qwen review build-test`. Update the stale assertion that still pinned the old string, which broke the Test CI job.
@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 轮)。改动内容与我反驳保留之处如下:

Autofix review round — PR #8394

Feedback triaged

The only actionable item this round was a failed required check:

  • Test (ubuntu-latest Node 22.x): FAILURE

There were no review bodies, inline comments, or issue-level comments to address. --conflict was false, so no base merge was performed.

Root cause

The CI Test job runs the full Vitest suite (npm run test:ci), not just the files this PR touches. Reproducing locally, the single failing test was packages/cli/src/commands/review/agent-prompt.test.ts:

expect(p).toContain('npm run build');   // agent-prompt.test.ts:1863

This PR's toolchain-adapter refactor deliberately rewrote Agent 7's brief in packages/cli/src/commands/review/lib/agent-briefs.ts to be npm/or Maven agnostic. The old brief told Agent 7 not to substitute `npm run build` / `npm test` by hand; the new brief replaces that wording with "hand-written npm or Maven commands" and points Agent 7 at `qwen review build-test` instead. The literal string npm run build no longer appears in the brief, so the pre-existing assertion — written against the old wording — failed. The test file itself was not updated in the PR, which is why CI went red.

This is a PR-caused breakage, not a pre-existing one: on main the brief still contained npm run build, so the assertion passed there; the diff is what removed the string.

Decision & change

Required (broken test) — fixed minimally. Updated the stale assertion to pin the brief's new, toolchain-agnostic command string, which preserves the test's stated intent ("gives Agent 7 no diff — its evidence is the commands it ran"):

-    expect(p).toContain('npm run build');
+    expect(p).toContain('qwen review build-test');

qwen review build-test is the exact command the rewritten brief instructs Agent 7 to run, so the assertion still verifies that the brief carries the build command. One line changed in one test file; no production code touched, no new assertions added, no bloat.

Conflict notes

None — --conflict false; the base was not merged.

Verification

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

  • npx vitest run src/commands/review/agent-prompt.test.ts (from packages/cli, touched file) — passed, 150/150 tests
  • npx vitest run src/commands/review/ --exclude '**/*.integration.test.ts' (from packages/cli) — passed, 47 files, 1557 passed | 3 skipped (the previously failing test now passes)
  • npm run buildpassed (exit 0)
  • npm run typecheckpassed (exit 0, all workspaces)
  • node scripts/lint.js --eslint (CI's ESLint step, eslint . --max-warnings 0) — passed
  • npx prettier --check packages/cli/src/commands/review/agent-prompt.test.tspassed

Committed as 187a272e6 on refactor/review-toolchain-adapter; the pre-commit hook passed and the working tree is clean.

中文说明

Autofix 审查轮次 — PR #8394

分类的反馈

本轮唯一可处理的事项是一个失败的必需检查:

  • Test (ubuntu-latest Node 22.x):FAILURE(失败)

没有需要处理的审查正文、行内评论或 issue 级评论。--conflictfalse,因此未执行 base 合并。

根因

CI 的 Test 任务运行的是完整 Vitest 套件(npm run test:ci),而不仅仅是本 PR 改动的文件。在本地复现时,唯一失败的测试是 packages/cli/src/commands/review/agent-prompt.test.ts

expect(p).toContain('npm run build');   // agent-prompt.test.ts:1863

本 PR 的工具链适配器重构刻意将 packages/cli/src/commands/review/lib/agent-briefs.ts 中 Agent 7 的简报改写为同时支持 npm/ Maven。旧简报告诉 Agent 7 不要手动替换 `npm run build` / `npm test`;新简报将该措辞替换为“手写的 npm 或 Maven 命令”,并改为指引 Agent 7 使用 `qwen review build-test`。字面字符串 npm run build 不再出现在简报中,因此这条针对旧措辞编写的既有断言失败了。该测试文件本身在本 PR 中未被更新,这正是 CI 变红的原因。

这是由本 PR 造成的破坏,而非既有问题:在 main 上简报仍包含 npm run build,所以该断言在那里通过;正是本 diff 删除了该字符串。

决定与改动

必需项(测试损坏)——以最小改动修复。 将该过时的断言更新为固定简报中新的、与工具链无关的命令字符串,从而保留该测试所声明的意图(“给 Agent 7 不提供 diff —— 它的证据是它运行过的命令”):

-    expect(p).toContain('npm run build');
+    expect(p).toContain('qwen review build-test');

qwen review build-test 正是改写后的简报指示 Agent 7 去运行的命令,因此该断言仍然验证了简报携带了构建命令。仅在一个测试文件中改动一行;未触碰生产代码,未新增断言,无任何冗余。

冲突说明

无 —— --conflict false;未合并 base。

验证

本轮实际运行的命令(除非另有说明,均在仓库根目录执行):

  • npx vitest run src/commands/review/agent-prompt.test.ts(在 packages/cli 中,改动文件)—— 通过,150/150 个测试
  • npx vitest run src/commands/review/ --exclude '**/*.integration.test.ts'(在 packages/cli 中)—— 通过,47 个文件,1557 通过 | 3 跳过(先前失败的测试现已通过)
  • npm run build —— 通过(退出码 0)
  • npm run typecheck —— 通过(退出码 0,所有 workspace)
  • node scripts/lint.js --eslint(CI 的 ESLint 步骤,eslint . --max-warnings 0)—— 通过
  • npx prettier --check packages/cli/src/commands/review/agent-prompt.test.ts —— 通过

已在 refactor/review-toolchain-adapter 分支上提交为 187a272e6;pre-commit 钩子通过,工作树干净。

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

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


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

@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 reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Test Plan (not a blocker): ./mvnwno such file or directory.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 Test Plan(非阻断):./mvnwno such file or directory

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

Comment thread packages/cli/src/commands/review/lib/maven-toolchain.ts Outdated
Comment thread packages/cli/src/commands/review/lib/maven-toolchain.ts Outdated
Comment thread packages/cli/src/commands/review/lib/maven-toolchain.ts Outdated
Comment thread packages/cli/src/commands/review/lib/maven-toolchain.ts Outdated
Comment thread packages/cli/src/commands/review/test-plan.ts Outdated
Comment thread packages/cli/src/commands/review/test-plan.ts Outdated
Comment thread packages/cli/src/commands/review/test-plan.ts Outdated
Comment thread packages/cli/src/commands/review/build-test.ts Outdated
Comment thread packages/cli/src/commands/review/agent-prompt.test.ts Outdated
wenshao and others added 3 commits August 3, 2026 07:35
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…er' into refactor/review-toolchain-adapter

# Conflicts:
#	packages/cli/src/commands/review/agent-prompt.test.ts
#	packages/cli/src/commands/review/build-test.test.ts
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind main, so it merged current main in via update-branch and will retry on the next scan. A stale base (a dependency or symbol main already changed) can fail the build without being the fix's fault; if it still fails once current, it hands off to a human.

What I found before stopping:

Autofix round summary — PR #8394 (address-review)

Outcome: no new code change was required this round. All nine round-2 suggestions from the automated reviewer were verified and are already resolved in the current head (the fixes landed in the earlier commits ad4bab592, 187a272e6, and ab6103705), and the twelve maintainer "Addressed" confirmations were re-verified as accurate. This round re-checked every finding against the exact code at HEAD and ran the full verification suite. No conflict resolution was needed (--conflict false, no merge performed).

Automated reviewer suggestions (round 2) — all resolved in code

  1. Fresh test failures misclassified as infrastructure (maven-toolchain.ts) — Resolved. The infrastructure-note cascade is now guarded by !hasFreshTestFailure(summaries), so fresh Surefire/Failsafe reports with failures or errors always win over infrastructure keywords in stdout. Regression test: "keeps fresh failing tests as source evidence despite infrastructure words" (a failing fresh report plus Connection refused in output stays source-correlating evidence).
  2. Unmatched environmental failures steered at the PR (maven-toolchain.ts:450) — Resolved with the maintainer's recorded design. Dash-style mvn: not found and No space left on device were added to the infrastructure patterns; an unchanged-wrapper launch failure (exit 126 with an explicit ./mvnw: Permission denied launch signature) is classified as infrastr

Run log: https://github.com/QwenLM/qwen-code/actions/runs/30775286030


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

@wenshao

wenshao commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Review: feat(review): Add Maven multi-module verification

Overview

Two things in one PR:

  1. Adapter extractionbuild-test.ts (479 lines removed) is split behind lib/toolchain.ts's ReviewToolchainAdapter contract, with the npm implementation moved verbatim to lib/npm-toolchain.ts. I diffed the removed hunk against the added file: apart from imports and the inlined unsupported report literal, the moved npm code is byte-identical. That is a clean, low-risk refactor.
  2. Maven adapter — reactor discovery from literal <project>/<modules>/<module>, deepest-module ownership, root-wrapper-preferring -pl … -am -amd test / test-compile, mtime-diffed Surefire/Failsafe evidence, plus downstream integration in base-tree, test-plan, and Agent 7's brief.

The fail-closed instincts are the best part of this: the ordered-token POM parser bails on anything it cannot prove literal, comments are rejected rather than guessed at, profile/plugin <modules> are excluded, stale XML is filtered by mtime, and ambiguous npm+Maven roots refuse to pick. Test coverage (23 new cases across 6 files) is thorough for the paths it covers. Below are the things I'd want addressed.


Correctness

1. Evidence output is unbounded and bypasses trimOutput — high.
appendTestSummaries runs after run() has already trimmed, so the appended lines are never subject to the 8 000-char contract:

// maven-toolchain.ts — one line per Surefire XML, i.e. per test class
return { ...result, output: `${result.output}\n${lines.join('\n')}`.trim() };

Surefire writes one XML per test class. On the repos this PR names as its driver (Fastjson2, Druid), a full-reactor run yields thousands of report files → thousands of [maven-test-report] lines (~90 chars each) in result.output, which is what gets serialized into the JSON report and into Agent 7's context. trimOutput already carries RESCUE_MAX = 40 for exactly this failure mode, with a comment about a measured 1.6 MB in / 1.6 MB out. Suggest: emit all [maven-test-failure] lines, roll passing reports up per module ([maven-module-summary] core: 412 reports, 8 931 tests, 0 failures), and cap the per-report lines.

2. -am -amd on the motivating repos is effectively "build the whole reactor" — high (design).
For a change in Fastjson2's or Druid's core, -amd selects every downstream module, i.e. the entire reactor. With --timeout defaulting to 300 s, that run times out, and the adapter then reports it as "infrastructure result, not a defect… report it as informational." The file's own preamble is a 30-line argument that a timing-out command means the review "spent two minutes proving nothing" — this path reproduces that on exactly the repos cited as motivation. Options: give Maven its own longer default deadline, make -amd opt-in (upstream-only by default is still sound for compile-correctness), or measure a real core run and document the expected outcome. Right now Tested on: Linux ⚠️ / Windows ⚠️ plus "real Maven suites were not executed" means nobody has seen an end-to-end Maven run finish.

3. Windows picks a wrapper it cannot execute — medium.

const executable = existsSync(join(args.root, 'mvnw')) ? './mvnw' : 'mvn';

Every Maven wrapper repo ships mvnw and mvnw.cmd. spawnSync(..., { shell: true }) on win32 goes through cmd.exe, where ./mvnw is not runnable. The code already knows mvnw.cmd exists (REACTOR_WIDE_FILES, and test-plan.ts's new MAVEN_RUNNER_RE), but never selects it. The resulting failure is not matched by isInfrastructureFailure, so it lands in the !ok branch as "Correlate compiler or test errors with the changed files" — i.e. a plausible false Critical on Windows. Same for shellSelector's POSIX '…' quoting, which is not a quoting form cmd.exe recognizes.

4. isInfrastructureFailure can suppress genuine test failures — medium.
The guard !hasFreshTestFailure(summaries) only holds when Surefire actually flushed a report. A forked-VM crash, a failure before the report is written, or a module that aborts the reactor leaves summaries empty — and then any output containing Connection refused, Unknown host, Connection timed out, or status code: 503 flips the whole run to "infrastructure evidence, not a source finding." Those strings are routine in a failing Druid JDBC/pool suite, which is one of the two named target repos. Suggest anchoring the pattern to Maven's own log framing ([ERROR] Failed to execute goal … (Could not resolve dependencies…), [ERROR] Non-resolvable parent POM) rather than free-text matching over test stdout.

5. Any non-module root file triggers a full-reactor run — medium.
detectMavenOwnership's trailing else sets reactorWide = true for anything under the root project that is neither a declared module, a doc, nor a listed reactor file. I confirmed by executing the predicates: .github/workflows/ci.yml, .gitignore, and LICENSE all reach that branch. So a PR that only touches CI YAML runs the entire reactor's test suite — the most expensive outcome for the cheapest diff, compounding (2). Suggest narrowing reactor-wide to genuinely build-relevant root paths (pom.xml, .mvn/, mvnw*, settings.xml, toolchains.xml) and routing VCS/CI metadata to unowned.

6. A nested standalone project is silently charged to its enclosing module — medium.
Module ownership (path === module || path.startsWith(module + '/')) is checked before the inactive-project check, so a POM that is not a declared module but lives inside one — core/samples/pom.xml, a profile-gated child, an integration-test project — is attributed to core, and -pl core never compiles it. The PR's stated fail-closed guarantee for standalone/profile-inactive projects therefore only holds when such a project sits outside every declared module directory, which is why the druid-admin acceptance case passes while a nested one would not. Suggest computing nearestMavenProject first and failing closed when it disagrees with the owning module.

7. base-tree now pays for a Maven base build nothing can consume — medium.
The relaxation from toolchain !== 'npm' to toolchain === 'unsupported' means a Maven merge base runs a full test-compile. But test-delta's RERUNNABLE_COMMAND_RE is /^npm test(?: --workspace="[\w@./-]+")?$/, so every Maven command falls into skippedUnrecognised — and Agent 7's own new brief says "Do not run test-delta for Maven in this release." That is a second full reactor compile bought for zero attribution. Consider gating base-tree on npm (or on an adapter capability flag) until Maven delta exists.


Lower severity / polish

  • shellSelector duplicates lib/shell-quote.ts. shellQuotePath already implements the '\'' dance with a comment explaining why; the new helper reimplements it as '"'"'. Reuse the existing one.
  • ToolchainRunArgs.install is accepted and ignored by the Maven adapter, so --install=false silently does nothing there. Either honor it or note the npm-only scope in the option's describe.
  • maxBuffer: 64 * 1024 * 1024 is plausible to exceed on a full reactor test run. ENOBUFS yields exitCode: null, timedOut: false, which routes to the "correlate compiler or test errors" note with no output left to correlate. Worth classifying alongside the timeout case.
  • mtime freshness at 1 s filesystem granularity (HFS+, some network mounts) can drop a report rewritten inside the same tick. It degrades in the safe direction ("no fresh XML"), but the tradeoff deserves a comment next to snapshotReports.
  • REPORT_DIRS is hardcoded to target/{surefire,failsafe}-reports. A <reportsDirectory> or non-target <build><directory> override yields no evidence; the "no fresh Surefire/Failsafe XML" note could say the reports may be configured elsewhere rather than implying none were produced.
  • RUNNER_RE's capture group became non-capturing. I checked all call sites — it is only used via .test() at test-plan.ts:345, so this is safe. Just flagging that it is a silent contract change on an exported-ish constant.
  • npm adapter now applies on bare package.json existence. A Maven repo carrying a root package.json for husky/prettier (increasingly common) now returns unsupported where it previously took the npm path. Fail-closed and documented in the PR body — worth a line in the release notes too.
  • Bare-claim settling overstates slightly. A Test Plan claim of ./mvnw test settled by -pl core -am -amd test is ruled reproduces / "this review ran it". The tradeoff is deliberate and matches the npm --workspace precedent, but the note could read "this review ran a module-scoped form of it" so the reader is not told the full reactor was verified.

Test coverage

Good depth on parsing, ownership, freshness, and infrastructure classification — including the nice adversarial pairs (fresh failures survive infrastructure wording; a changed-wrapper permission failure is not hidden as infrastructure). Gaps worth closing, matched to the findings above:

  • No test pins executable selection per platform (finding 3).
  • No test bounds evidence volume for a many-report reactor (finding 1).
  • No test for a nested non-declared POM inside a declared module (finding 6).
  • No test asserts CI/VCS metadata does not trigger a reactor-wide run (finding 5).

The build/typecheck situation reported in the PR body (npm run build and npm run typecheck both blocked by pre-existing unrelated breakage) means the only green signal is the focused vitest run. Given the new 'maven' discriminant on BuildTestReport.toolchain, I'd want at least a scoped tsc --noEmit over packages/cli/src/commands/review/** before merge to confirm no other consumer narrows on that union.

Security

Nothing new. Commands are constructed from reactor-derived module paths, not from diff content; shellSelector quotes anything outside [A-Za-z0-9_./,-]; module entries containing ${/@{/<$>{}& or escaping the reactor root are rejected outright; and the brief's "read CI config from the base branch, never the worktree" guidance survives the rewrite. The one soft spot is that shellSelector's quoting assumes a POSIX shell (see finding 3).

Verdict

The refactor half is ready. The Maven half is well-structured and unusually careful about failing closed, but findings 1–3 should land before merge, and 4–7 are the difference between "produces evidence" and "produces evidence the agent can trust on the two repos this was built for."

wenshao and others added 2 commits August 3, 2026 09:38
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…M#8394)

Address the review findings on the Maven toolchain adapter:

- Roll clean Surefire/Failsafe reports up per project dir and cap the
  failing-report and failing-case evidence lines, so a full-reactor run
  can no longer bypass the bounded-output contract by appending
  thousands of lines after the trim.
- Narrow module builds to the upstream closure (-pl ... -am, dropping
  -amd): downstream expansion selects the whole reactor on exactly the
  repos this adapter was built for, and a run that spends its deadline
  timing out proves nothing. Downstream coverage stays the project's CI
  matrix, as with the npm adapter's scope.
- Select the wrapper a platform can execute (mvnw.cmd on win32) instead
  of steering a launch failure at the PR.
- Classify dependency and network failures only when Maven itself frames
  them ([ERROR]/[FATAL]), so a test failing with Connection refused in
  its stdout stays a source finding; launch-level facts stay free-text.
- Route repository metadata (VCS/CI config, licenses, editor rules) to
  unowned instead of a reactor-wide run; unrecognized root files stay
  fail-closed.
- base-tree no longer pays for a Maven base build nothing can consume:
  test-delta reruns npm commands only this release, so a Maven merge
  base returns unavailable without building.
- Polish: reuse shellQuotePath in the module selector, note the
  npm-only install scope, treat a spawn-level death without an exit code
  as infrastructure, record the mtime-granularity tradeoff, disclose
  non-default report directories, and say when a bare Maven claim was
  settled by a module-scoped run.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@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 round summary — PR #8394

This round addresses the issue-level review findings on the Maven toolchain
adapter and re-verifies the inline findings already fixed by earlier commits.

Fixed in this commit (issue-level review findings)

  1. Unbounded evidence output (high). Surefire writes one XML per test
    class, so a green full-reactor run produced thousands of
    [maven-test-report] lines appended after the 8 000-char trim — the
    exact contract violation the finding names. Clean reports now roll up per
    project dir in the same parseable shape test-plan already reads
    ([maven-test-report] core (150 report(s)): tests=300, failures=0, …),
    failing reports keep per-report identity capped at 100 lines, and failing
    case lines are capped at 200, each with an explicit omission marker. A
    rollup-shape count test was added to test-plan so the parser contract
    stays pinned.
  2. -am -amd times out on the motivating repos (high). The command now
    narrows to the upstream closure only (-pl … -am, no -amd): downstream
    expansion selects the whole reactor on exactly the repositories this
    adapter was built for, and a run that spends its entire deadline timing
    out proves nothing — the preamble of build-test itself. A longer
    Maven-specific deadline was not viable: the documented timeout contract
    keeps every command strictly under the 600 s outer tool timeout for the
    whole call. Downstream coverage stays with the project's CI matrix, which
    is also what the npm adapter's scope does. Design doc updated.
  3. Windows picks a wrapper it cannot execute (medium). Executable
    selection is now platform-aware (mvnw.cmd on win32, ./mvnw elsewhere,
    system mvn when the wrapper is absent) behind an exported, testable
    mavenExecutable; the launch failure can no longer be steered at the PR
    as a false Critical on Windows. Remaining caveat, unchanged from before:
    the selector quoting stays POSIX; real-world module paths take the
    unquoted safe-charset branch, and the review harness itself requires a
    POSIX shell.
  4. Infrastructure classification could suppress genuine failures (medium).
    Dependency/network/plugin phrases now count only when Maven itself frames
    them ([ERROR]/[FATAL] line prefix); shell/JVM launch facts (command not found, ENOSPC, JAVA_HOME) remain free-text. A test that fails
    printing Connection refused in its stdout with no fresh report is now
    source evidence, while [ERROR] … Could not transfer artifact … Connection refused stays infrastructure. Both directions are
    regression-tested.
  5. Any root metadata file triggered a full-reactor run (medium). VCS/CI
    metadata (.git*, .github/, LICENSE*, NOTICE*, CODEOWNERS,
    .editorconfig) is now unowned — no Maven target — while any
    unrecognized root file still runs the reactor (fail-closed, so a root
    checkstyle.xml or build script keeps its coverage). Tested.
  6. Nested standalone projects — already fixed at HEAD (f7602eadd,
    inactive-project check inside module ownership), re-verified against the
    code this round.
  7. base-tree paid for a Maven base build nothing can consume (medium).
    base-tree now checks the base tree's toolchain before building and
    returns unavailable — without running the build — when it is Maven: the
    A/B consumer (test-delta) reruns npm commands only this release, and
    Agent 7's Maven branch already discloses that base-side attribution is
    unavailable. The post-build availability check is restored to npm-only as
    defense in depth. Tests updated; design doc updated.

Polish items from the same review

  • shellSelector now reuses shellQuotePath from lib/shell-quote.ts
    instead of reimplementing the '\'' dance.
  • --install describe (build-test and base-tree) and ToolchainRunArgs
    state that install is npm-only; Maven resolves dependencies inside its
    lifecycle command.
  • A spawn-level death with no exit code (maxBuffer exceeded, outside
    signal) is classified as infrastructure like a timeout, instead of
    reaching "Correlate compiler or test errors" with nothing to correlate.
    Regression-tested.
  • A comment next to snapshotReports records the 1 s mtime-granularity
    tradeoff (degrades to absent evidence, never a wrong verdict).
  • The "no fresh Surefire/Failsafe XML" note now says reports written to a
    non-default directory are not seen.
  • A bare Maven claim settled by a scoped run now reads "this review ran a
    module-scoped form of it" instead of overstating that the full reactor
    was verified.
  • RUNNER_RE's non-capturing group: no action — the review confirmed it is
    only used via .test().
  • Bare package.json making mixed repos unsupported: a release-notes
    line, not code; recorded here for the release notes.

Inline findings (automated reviewer round 2 + prior rounds)

All nine round-2 suggestions were already fixed in code by earlier commits
on this branch; each was re-verified against HEAD this round:

  • Fresh failing Surefire/Failsafe summaries take precedence over
    infrastructure keywords (guarded branch + regression test).
  • Unchanged-wrapper launch failures (exit 126 with the ./mvnw: Permission denied signature), missing mvn in both bash and dash wording, and
    ENOSPC are infrastructure; a changed wrapper or an inner Permission denied is not. The suggested stat-mode executable-bit fallback was
    deliberately not adopted: classification closes the mis-steering defect
    the finding names while keeping the wrapper's Maven version pin, and the
    wrapper is now skipped only when a platform cannot run it at all.
  • src/ paths are exempt from the documentation extension rule; the
    fixture runs the reactor.
  • Surefire/Failsafe directory reads are guarded (regular file at
    target/surefire-reports covered by a regression test).
  • The lifecycle is read as the final command token, so a module selector
    named validate/test cannot hijack attribution.
  • mvn, mvnw, mvnw.cmd, ./mvnw, ./mvnw.cmd, and the .\mvnw
    Windows spellings are all extracted and matched.
  • A matching Maven timeout is unchecked with an explicit "ran but timed
    out" note.
  • A pom-only facade test spies on mavenToolchainAdapter.run and pins the
    forwarded arguments.
  • The Agent 7 brief assertion pins the Maven branch (toolchain: "maven",
    no test-delta for Maven) instead of the pre-existing opening sentence.

The twelve "Addressed" confirmations were each checked against the code;
every described fix is present at HEAD. Their threads are listed for
resolution.

Not actionable here

  • The reviewer's Test Plan note (./mvnw — no such file or directory):
    this repository is npm-based and ships no wrapper; the reviewer marked it
    not a blocker.
  • Integration tests: the changed behavior is exercised through the unit
    suites above (the adapters run through an injected executor seam); no
    bundled-CLI-only path was touched.

Conflict

No base conflict (--conflict false); origin/main was not merged.

Verification

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0), before and after the change; the
    PR body's claim of pre-existing typecheck breakage is stale — the merge
    from main cleared it, so a full-workspace tsc is green
  • npm run lint — passed (exit 0); targeted npx eslint over the nine
    changed files also clean
  • vitest (packages/cli, touched review suites: maven-toolchain,
    npm-toolchain, build-test, base-tree, test-plan, agent-prompt) — 6 files,
    334 tests passed (326 before this round; 8 new)
中文说明

Autofix 本轮总结 — PR #8394

本轮处理针对 Maven 工具链 adapter 的 issue 级评审发现,并复核此前提交已修复的行内发现。

本次提交修复的问题(issue 级评审发现)

  1. 证据输出无上限(高)。 Surefire 每个测试类写一个 XML,因此全 reactor 绿色运行会产生数千行 [maven-test-report],而且是在 8000 字符裁剪之后追加的——正是该发现指出的契约违规。现在:干净的报告按项目目录聚合,仍保持 test-plan 已在解析的同构格式([maven-test-report] core (150 report(s)): tests=300, failures=0, …);失败报告保留逐报告明细但上限 100 行;失败用例行上限 200 行,超限均附带明确的省略标记。test-plan 新增了聚合格式的计数测试,继续钉住解析契约。
  2. -am -amd 在目标仓库上必然超时(高)。 命令现在只收窄到上游闭包(-pl … -am,去掉 -amd):下游扩展在本 adapter 面向的仓库上恰好选中整个 reactor,而一次把整个时限耗在超时上的运行什么也证明不了——这正是 build-test 开篇所述的反面教材。更长的 Maven 专属时限不可行:既定时限契约要求每条命令严格低于整个调用的 600 秒外层工具时限。下游覆盖交给项目自身的 CI 矩阵——npm adapter 的范围界定也是如此。设计文档已同步更新。
  3. Windows 选中无法执行的 wrapper(中)。 可执行文件选择现在区分平台(win32 用 mvnw.cmd,其他平台用 ./mvnw,无 wrapper 时回退系统 mvn),收敛到可测试的导出函数 mavenExecutable;Windows 上不会再把启动失败引导成指向 PR 的假 Critical。遗留说明(与之前一致):选择器引号仍为 POSIX 形式;现实中的模块路径都走免引号的安全字符分支,且评审流程本身要求 POSIX shell。
  4. 基础设施分类可能压制真实失败(中)。 依赖/网络/插件类短语现在只在 Maven 自身以 [ERROR]/[FATAL] 行前缀框定时才算数;shell/JVM 层事实(command not found、ENOSPC、JAVA_HOME)保持自由文本匹配。一个测试失败时 stdout 打印 Connection refused 且无新报告时,现在归为源码证据;而 [ERROR] … Could not transfer artifact … Connection refused 仍归基础设施。两个方向均有回归测试。
  5. 任意根级元数据文件都触发全 reactor 运行(中)。 VCS/CI 元数据(.git*.github/LICENSE*NOTICE*CODEOWNERS.editorconfig)现在归为 unowned——无 Maven 目标;任何无法识别的根级文件仍会运行 reactor(失败时保守,保证根级 checkstyle.xml 或构建脚本不丢覆盖)。已有测试。
  6. 嵌套独立项目 —— HEAD 已修复(f7602eadd,模块归属内的 inactive-project 检查),本轮已对照代码复核。
  7. base-tree 为无人消费的 Maven 基础构建买单(中)。 base-tree 现在在构建前检查基础树的工具链:若是 Maven,则直接返回 unavailable——不运行构建——因为本版本的 A/B 消费者(test-delta)只重放 npm 命令,且 Agent 7 的 Maven 分支已声明基础侧归因不可用。构建后的可用性检查恢复为仅 npm,作为纵深防御。测试与设计文档已同步更新。

同一评审中的打磨项

  • shellSelector 改为复用 lib/shell-quote.tsshellQuotePath,不再重复实现 '\'' 转义。
  • --install 的 describe(build-test 与 base-tree)及 ToolchainRunArgs 均注明 install 仅限 npm;Maven 在生命周期命令内自行解析依赖。
  • 无退出码的进程级死亡(超过 maxBuffer、外部信号)与超时一样归为基础设施,不再带着空输出进入 "Correlate compiler or test errors"。已有回归测试。
  • snapshotReports 旁新增注释,记录 1 秒 mtime 粒度的取舍(退化为证据缺失,绝不产生错误结论)。
  • "无新 Surefire/Failsafe XML" 的提示现在说明:写入非默认目录的报告不会被看到。
  • 被收窄运行裁决的裸 Maven 声明,提示语改为 "this review ran a module-scoped form of it",不再夸大为整个 reactor 已验证。
  • RUNNER_RE 的非捕获组:不处理——评审已确认它只通过 .test() 使用。
  • package.json 使混合仓库变为 unsupported:属于发布说明条目而非代码改动,已在此记录以备发布说明。

行内发现(自动评审第 2 轮 + 此前各轮)

第 2 轮的全部九条建议此前已由本分支的提交修复;本轮逐条对照 HEAD 复核:

  • 新的失败 Surefire/Failsafe 摘要优先于基础设施关键词(守卫分支 + 回归测试)。
  • 未改动 wrapper 的启动失败(退出码 126 且带 ./mvnw: Permission denied 签名)、bash 与 dash 两种措辞的 mvn 缺失、ENOSPC 均归基础设施;改动了 wrapper 或内部 Permission denied 则不归基础设施。建议中的 stat 可执行位回退方案被有意不采纳:分类方案已闭合该发现指出的误引导缺陷,同时保留 wrapper 的 Maven 版本锁定;只有当平台根本无法运行 wrapper 时才跳过 wrapper。
  • src/ 路径不受文档扩展名规则影响;该夹具会运行 reactor。
  • Surefire/Failsafe 目录读取已加守卫(target/surefire-reports 为普通文件的场景有回归测试)。
  • 生命周期取命令最后一个 token,名为 validate/test 的模块选择器无法劫持归因。
  • mvnmvnwmvnw.cmd./mvnw./mvnw.cmd.\mvnw Windows 写法均可提取并参与匹配。
  • 匹配的 Maven 超时判为 unchecked 并附明确的 "ran but timed out" 提示。
  • 仅 pom 的门面测试对 mavenToolchainAdapter.run 打桩,钉住转发参数。
  • Agent 7 brief 断言钉住 Maven 分支(toolchain: "maven"、Maven 不走 test-delta),而非 PR 之前就已存在的开篇句。

十二条 "Addressed" 确认均已逐条对照代码核实,所述修复全部存在于 HEAD,其线程已列入待解决清单。

本轮不处理

  • 评审者的 Test Plan 备注(./mvnw — no such file or directory):本仓库基于 npm,不带 wrapper;评审者已标注非阻断。
  • 集成测试:本次改动的行为由上述单元测试覆盖(adapter 通过注入的 executor 接缝运行);未涉及仅捆绑 CLI 才走的路径。

冲突

无基线冲突(--conflict false);未合并 origin/main

验证

  • npm run build — 通过(退出码 0)
  • npm run typecheck — 通过(退出码 0),改动前后均通过;PR 描述中"既有 typecheck 损坏"的说法已过时——从 main 合并后已消除,全工作区 tsc 为绿
  • npm run lint — 通过(退出码 0);对九个改动文件的定向 npx eslint 亦无告警
  • vitest(packages/cli 涉及评审的套件:maven-toolchain、npm-toolchain、build-test、base-tree、test-plan、agent-prompt)— 6 个文件、334 个测试全部通过(本轮前为 326 个,新增 8 个)

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

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


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

@wenshao

wenshao commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Review: feat(review): Add Maven multi-module verification

Scope reviewed: the PR diff only (14 files, +3065/−492), against f308f0bd.

Verification done for this review: ran the PR's focused suite in a worktree at the PR head — 6 files / 334 tests passed (the body says 326; it drifted with the last commits). Three of the findings below were confirmed by probes against the PR's own modules, not by reading alone.


Overview

Splits build-test.ts into a thin facade over a ReviewToolchainAdapter boundary (lib/toolchain.ts), moves the existing npm behavior into lib/npm-toolchain.ts unchanged, and adds lib/maven-toolchain.ts: literal <project>/<modules>/<module> reactor discovery, deepest-module ownership for changed files, root-wrapper-preferred -pl … -am test / test-compile from the reactor root, and mtime-fresh module-qualified Surefire/Failsafe evidence. base-tree refuses to build a Maven merge base, test-plan learns Maven commands and counts, and Agent 7's brief gets a toolchain: "maven" arm.

The shape is right: the adapter contract is small, npm behavior is preserved byte-for-byte, and the fail-closed instinct throughout is the correct default for a command whose output an agent treats as ground truth. Comment quality is unusually high and explains why with measured evidence.


Findings

1. A nested test-fixture POM under an active module abandons verification for the whole PR — Medium-High

detectMavenOwnershipnearestMavenProject: any pom.xml nested under an active module that is not itself a reactor module is classed as standalone/profile-inactive, and runMavenToolchain then returns unsupported for the entire diff.

Probed against maven-toolchain.ts directly (root reactor with one core module, plus core/src/test/resources/projects/sample/pom.xml):

changedFiles = [ 'core/src/main/java/Core.java',                      // real source
                 'core/src/test/resources/projects/sample/App.java' ] // a fixture

ownership => {"reactorWide":false,"modules":["core"],
              "inactiveProjects":["core/src/test/resources/projects/sample"]}
report   => unsupported | "The diff changes Maven project(s) outside the root reactor: …"

exec was never called — the real core source change lost its verification too.

Test-resource POMs are not an edge case in Maven repos: maven-plugin ITs, maven-invoker-plugin projects, archetype fixtures, and src/test/resources/projects/* trees are the standard layout. Maven never builds them as reactor modules and no profile can activate them — they are test data. The Flink case that motivated this guard (a profile-only child under an active module dir) is real, but the guard as written cannot tell the two apart and pays for it with the whole diff.

Suggestion: don't treat a POM under an active module's src/ as a project at all; or, failing that, degrade only the fixture file (push it to unowned) instead of returning unsupported for changes the reactor can verify.

2. The POM parser fails closed on two constructs that occur in real poms — Medium

Isolated by probe (each returns Cannot safely parse literal Maven modules from pom.xml):

construct result
<x><![CDATA[a --> b]]></x> ❌ error
<x y="a > b"/> (> in an attribute value) ❌ error
CDATA with a bare >, comments containing <module>, DOCTYPE, namespaces ✅ parses

The first is the cheap one: moduleEntries strips comments and then rejects the file if any <!--/--> survives, so a --> inside CDATA (antrun/checkstyle/xml-generation config) trips it. Stripping <![CDATA[…]]> before that guard fixes it without weakening the guard. Blast radius is the same as #1 — one unparseable POM anywhere in the reactor and the whole repo drops to unsupported, and the brief (correctly) tells the agent not to substitute an ad hoc Maven command.

3. Agent 7's brief overclaims Maven scoping — -amd was dropped, the prompt was not updated — Medium

lib/agent-briefs.ts:448 still says build-test "scopes the run to the changed workspaces/modules plus their required dependency and dependent closure". The Maven command emits -am only — confirmed:

mvn --batch-mode --no-transfer-progress -pl core -am test

docs/design/review-toolchain-adapters.md:207 was updated correctly (-amd selects the whole reactor, so downstream coverage stays with CI). The brief and the PR body's Test Plan step 2 (-pl core -am -amd test) were not. Agent 7 reads that brief as fact, so it can report dependent modules as verified when they were never built.

Related and worth a line in the report note: a change to core/pom.xml is scoped to -pl core -am, and a POM change is precisely the kind that can break dependents. The tradeoff is defensible; it should be stated in the evidence the agent reports, not only in a source comment.

4. toolchainAdapters is documented as precedence-ordered, but selection ignores order — Low-Medium

/** The root toolchains build-test can select, in precedence order. */
export const toolchainAdapters = [npmToolchainAdapter, mavenToolchainAdapter];

selectToolchainAdapter returns a value only when exactly one adapter applies; order is never consulted. Drop the phrase, or make selection use it.

The related risk is npmToolchainAdapter.applies = existsSync(package.json). That fires on a root package.json that exists only for a docs site, husky, or a lint config — a shape many Java repos have — and the repo now returns unsupported where the npm path previously at least ran. The body flags mixed roots as an intentional breaking change, but the trigger is broader than "both toolchains genuinely apply". Consider requiring workspaces or a build/test script in applies, which is the condition runNpmToolchain already checks internally.

5. base-tree gates Maven after git worktree add — Low

The early return sits below the worktree add, so a full base checkout of a large Java reactor is materialized and then never built (path: tree is still returned). git cat-file -e <baseSha>:pom.xml would answer the same question before paying for the checkout.

6. Maven infra-vs-source classification reads already-trimmed output — Low

isInfrastructureFailure(result.output) runs on the output the real run passed through trimOutput (head 2k / tail 6k). trimOutput rescues Cannot find module and runner-summary lines from the omitted middle; it has no rescue for [ERROR] Could not resolve dependencies and friends. Maven normally prints its error summary last, so the tail usually holds it — but when it doesn't, the failure mode is the exact one this command exists to prevent: a dependency/network outage filed as a Critical against the PR. Adding the isDependencyFailure markers to trimOutput's rescue predicate closes it cheaply.

7. The wrapper-permission guard uses a raw path comparison — Low

executable === './mvnw' && !args.changedFiles.includes('mvnw') && result.exitCode === 126

Every other path in this module goes through normalizedChangedPath. A plan that lists ./mvnw or an absolute path defeats the guard, and a PR that breaks the wrapper's mode bits is then hidden as infrastructure — the case the guard was added for. Normalize first.

8. Nits

  • shellSelector falls back to shellQuotePath, which is POSIX '…' quoting; the command runs through cmd.exe on Windows, where those quotes are literal. Only reachable for exotic module paths, but mavenExecutable has an explicit win32 branch, so Windows is in scope.
  • MavenOwnership.unowned is computed and never surfaced — the "no Maven target" note reports args.changedFiles.length instead. Report it or drop the field.
  • PR body drift: 326 → 334 tests; Test Plan step 2 still shows -amd.

What's good

  • Test coverage is the strongest part. 23 focused Maven cases, and they test the right things: stale-XML exclusion, dependency failure still classed as infrastructure after an earlier module emitted a fresh report, unframed network words in test stdout staying source evidence, profile/plugin <module> exclusion, report-line caps, spawn death without an exit code. The two adapter-delegation tests (routes the run through the selected toolchain adapter, defaults the adapter exec to the real runner) pin exactly what a revert would silently unpin — that is the note the extraction needed.
  • Correct defaults on the execution side: --batch-mode --no-transfer-progress, always run from the reactor root, mtime-based freshness with the 1s-granularity degradation documented and argued as safe-direction.
  • base-tree refusing a base-side Maven build rather than producing evidence test-delta cannot consume is the right call, and the brief says the same thing — the two stay consistent.

Security

No new exposure class. Maven executes the worktree's mvnw and pom.xml, both PR-controlled, but npm run build already ran PR-authored scripts on the same tree; the brief's existing "read CI config from the base branch, never the worktree" rule still holds. Reactor discovery correctly rejects ..-escaping and ${…}-interpolated <module> paths.

Performance

reportPaths walks every project's target/{surefire,failsafe}-reports twice per run (snapshot + fresh scan) and statSyncs each file. On a green full-reactor run of a large project that's thousands of stats — acceptable next to the Maven run itself, but worth knowing it scales with reactor size, not with what was tested.


Verdict: the boundary and the npm-preservation work are solid and well-tested, and I'd merge that half as-is. #1 and #3 are the two I'd want addressed before this ships: #1 because the fail-closed trigger is common enough to make Maven verification unavailable on a class of repos where it should work, and #3 because a brief that overclaims what ran turns a scoping tradeoff into a false attribution.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Review feedback addressed (round on f308f0bd)

All findings from the issue-level review were verified against the exact code and addressed in one commit (fix(review): Address Maven verification review feedback (#8394)). No conflicts (--conflict false); no merge performed.

Dispositions

#1 — Fixture POM under an active module abandons the whole diff (Medium-High): FIXED.
nearestMavenProject now skips POMs that sit under a known reactor project's src/ tree (isUnderTestSourceTree): maven-invoker ITs, archetype fixtures, and src/test/resources/projects/* trees are test data — Maven never builds them as reactor modules and no profile activates one. The changed file stays owned by the enclosing project, so the probe scenario (core/src/main/java/Core.java + core/src/test/resources/projects/sample/App.java) now runs -pl core -am test instead of returning unsupported for the whole PR. Fail-closed behavior is preserved for genuine inactive projects outside src/ (the existing nested-parent/profile-child and standalone-project tests still pass). Covered by two new regression tests.

#2 — Parser fails closed on CDATA with --> and > in attribute values (Medium): FIXED.
moduleEntries strips <![CDATA[…]]> sections before the comment strip/guard, and both the tokenizer and the opening-tag regex are now quote-aware, so > inside an attribute value no longer splits a tag. The leftover-marker guard is unchanged (a new test pins that an unbalanced --> still fails closed).

#3 — Agent 7 brief overclaims Maven scoping; POM-change caveat absent from evidence (Medium): FIXED.
The brief no longer claims a "dependency and dependent closure" for both toolchains: it now states npm builds changed workspaces + dependencies + dependents, while Maven runs -pl <changed modules> -am (upstream dependency closure only), and that downstream consumers are NOT verified by a Maven run. Additionally, every narrowed Maven run appends a scope sentence to the report note — the evidence Agent 7 reports — stating that downstream dependents were not built and that a POM or API change can break modules the run never compiled. Note: the PR body's Test Plan step 2 still shows -amd and the test count drifted (see "PR body" below).

#4toolchainAdapters "precedence order" doc + over-broad npm applies (Low-Medium): FIXED.
The misleading "in precedence order" phrase is dropped (selection requires exactly one applicable adapter; order is never consulted). npmToolchainAdapter.applies now requires workspaces or a root build/test script — exactly what runNpmToolchain already checks internally — so a docs-site/husky/lint-config package.json in a Java repo no longer collides with the Maven adapter into unsupported; the Maven adapter runs instead. Design doc updated to match. Genuinely mixed roots (npm project + pom.xml) still fail closed. New tests pin both directions.

#5base-tree gates Maven after git worktree add (Low): FIXED.
The Maven gate now answers from the object store — git cat-file -e <baseSha>:pom.xml — before any sweep/checkout, so a large Java reactor never materializes a base tree that will not be built. The post-checkout gate and its now-unused imports are removed. The existing Maven-base test now also pins that no tree directory is created.

#6 — Infrastructure classification reads already-trimmed output (Low): FIXED.
The Maven dependency-failure matcher is exported in line-level form (isDependencyFailureLine) and added to trimOutput's rescue predicate (ANSI-stripped, under the existing 40-line cap), so a [ERROR] Could not resolve dependencies… line in the omitted middle survives the trim and keeps a network outage classified as infrastructure. The omission marker text was updated accordingly. New regression test added.

#7 — Wrapper-permission guard uses a raw path comparison (Low): FIXED.
The guard now compares normalizedChangedPath(...) === 'mvnw', so ./mvnw or an absolute path in the plan is recognized as the diff touching the wrapper. New regression test added.

#8 — Nits:

  • Windows selector quoting: FIXED. shellSelector now takes a platform parameter and double-quotes exotic selectors on win32 (POSIX '…' quoting is literal in cmd.exe); POSIX quoting is unchanged elsewhere. Exported and tested, following the mavenExecutable pattern.
  • MavenOwnership.unowned never surfaced: FIXED by removing the field (the "no Maven target" note's count was already accurate); tests updated.
  • PR body drift (326 → 334 tests; Test Plan step 2 shows -pl core -am -amd test): NOT FIXED HERE — this mode has no GitHub write access, so the PR body cannot be edited from this round. The -amd reference is obsolete (the command is -pl … -am, as the brief and design doc now state); a maintainer or the workflow needs to refresh the PR body.

Checks actually run

See the Verification section below. All findings were implemented in code and covered by new or updated focused Vitest tests; no finding was declined.

Verification

Commands actually run in this round (post-fix, on the committed tree unless noted):

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • npx vitest run src/commands/review (packages/cli, all 53 review test files) — 1698 passed | 3 skipped, exit 0 (re-run after formatting and again on the committed tree)
  • npx prettier --check on all changed files — passed
  • No integration harness needed: the touched behavior is fully exercised by the unit suites above; settings sources were not changed, so npm run generate:settings-schema was not applicable.
中文说明

已处理的评审反馈(基于 f308f0bd 的本轮)

issue 级评审中的全部发现均已对照具体代码核实,并在一个提交中处理完毕(fix(review): Address Maven verification review feedback (#8394))。无冲突(--conflict false),未执行合并。

各项处理结果

#1 — 活跃模块下的测试夹具 POM 导致整个 PR 放弃验证(中-高):已修复。
nearestMavenProject 现在会跳过位于已知 reactor 项目 src/ 目录树下的 POM(isUnderTestSourceTree):maven-invoker 集成测试、archetype 夹具以及 src/test/resources/projects/* 目录都是测试数据——Maven 从不把它们作为 reactor 模块构建,也没有任何 profile 能激活它们。被修改的文件仍归属于外层项目,因此探针场景(core/src/main/java/Core.java + core/src/test/resources/projects/sample/App.java)现在会执行 -pl core -am test,而不是对整个 PR 返回 unsupported。对 src/ 之外真实存在的未激活项目仍保持失败即关闭(现有的 nested-parent/profile-child 与独立项目测试依然通过)。新增两个回归测试覆盖。

#2 — 解析器对含 --> 的 CDATA 和属性值中的 > 失败即关闭(中):已修复。
moduleEntries 现在先剥离 <![CDATA[…]]> 段,再做注释剥离与守卫;分词器和开标签正则均已支持引号,属性值中的 > 不再把标签切断。残留标记守卫保持不变(新增测试钉住不成对的 --> 仍然失败即关闭)。

#3 — Agent 7 简报对 Maven 作用范围表述过度;POM 修改的风险提示未出现在证据中(中):已修复。
简报不再对两种工具链笼统声称"依赖与被依赖闭包":现在明确 npm 会构建被改动的 workspace 及其依赖与被依赖方,而 Maven 执行 -pl <changed modules> -am(仅上游依赖闭包),并且 Maven 运行不会验证下游消费模块。此外,每一次收窄的 Maven 运行都会在报告 note(即 Agent 7 上报的证据)中追加一句作用范围说明:未构建下游被依赖模块,POM 或 API 改动可能破坏本次运行从未编译的模块。注意:PR 正文 Test Plan 第 2 步仍写着 -amd、测试数量也已漂移(见下方"PR 正文")。

#4toolchainAdapters 的"优先级顺序"文档 + npm applies 过宽(低-中):已修复。
删除了误导性的"in precedence order"表述(选择逻辑要求恰好一个适配器适用,从不查阅顺序)。npmToolchainAdapter.applies 现在要求存在 workspaces 或根级 build/test 脚本——与 runNpmToolchain 内部已有的检查完全一致——因此 Java 仓库中仅用于文档站/husky/lint 配置的 package.json 不会再与 Maven 适配器相撞而整体落入 unsupported,Maven 适配器可以正常接管。设计文档已同步更新。真正的混合根(npm 项目 + pom.xml)仍然失败即关闭。新增测试钉住两个方向。

#5base-treegit worktree add 之后才拦截 Maven(低):已修复。
Maven 拦截现在直接从对象存储回答——git cat-file -e <baseSha>:pom.xml——在任何清理/检出之前完成,因此大型 Java reactor 不会再物化一棵根本不会构建的 base 树。检出后的拦截及其不再使用的 import 已移除。现有 Maven base 测试还额外钉住:不会创建任何树目录。

#6 — 基础设施分类读取的是已裁剪的输出(低):已修复。
Maven 依赖失败匹配器以行级形式导出(isDependencyFailureLine),并加入 trimOutput 的保留谓词(先去除 ANSI、受现有 40 行上限约束),因此落在被省略中段的 [ERROR] Could not resolve dependencies… 行能够幸存,网络故障继续被归类为基础设施问题。省略标记文案同步更新。新增回归测试。

#7 — wrapper 权限守卫使用原始路径比较(低):已修复。
守卫现在比较 normalizedChangedPath(...) === 'mvnw',因此计划中写 ./mvnw 或绝对路径也能被识别为 diff 触及了 wrapper。新增回归测试。

#8 — 细节问题:

  • Windows 下的选择器引号:已修复。shellSelector 增加平台参数,win32 下对特殊选择器使用双引号(POSIX 的 '…' 引号在 cmd.exe 中是字面量);其他平台保持 POSIX 引号不变。仿照 mavenExecutable 的模式导出并测试。
  • MavenOwnership.unowned 从未被使用:已删除该字段("无 Maven 目标"备注中的文件计数本来就是准确的),测试同步更新。
  • PR 正文漂移(326 → 334 个测试;Test Plan 第 2 步仍写 -pl core -am -amd test):本轮未修复——本模式没有 GitHub 写权限,无法在此编辑 PR 正文。-amd 引用已过时(实际命令是 -pl … -am,简报与设计文档已按此表述),需要有维护者或工作流刷新 PR 正文。

实际执行过的检查

见下方 Verification 小节。所有发现均以代码实现,并由新增或更新的 Vitest 聚焦测试覆盖;没有任何发现被拒绝。

Verification(验证)

本轮实际执行的命令(修复后;除特别说明外均在已提交的树上运行):

  • npm run build — 通过(exit 0)
  • npm run typecheck — 通过(exit 0)
  • npm run lint — 通过(exit 0)
  • npx vitest run src/commands/review(packages/cli,全部 53 个 review 测试文件)— 1698 通过 | 3 跳过,exit 0(格式化之后重跑一次,提交后的树上再跑一次)
  • npx prettier --check 检查全部改动文件 — 通过
  • 无需集成测试:上述单元测试套件已完整覆盖受影响的行为;未改动 settings 源文件,因此不适用 npm run generate:settings-schema

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

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

Round-8 review feedback — address summary (PR #8394)

Commit: fix(review): Close round-8 Maven routing, settlement, and evidence gaps (#8394) on refactor/review-toolchain-adapter.

All 8 Critical and all 21 Suggestion findings were verified against the code, reproduced, and fixed. No finding was declined or escalated.

Critical findings

  • R7-7 — unbounded POM reads (maven-toolchain.ts): POM reads are now size-capped at 2 MiB via statSync before each readFileSync (both the reactor visit() walk and named-parent-file reads), returning the same fail-closed reactor error string as the other unreadable shapes. Pinned by fails closed on a POM larger than the read cap.
  • R8-11 — named-parent inheritance chains dropped: inheritance edge resolution is now a worklist. When a named parent FILE is resolved, its own <parent> declaration is parsed and enqueued with the SAME heir, resolved from the file's own directory, so app → base/parent.xml → grand/parent2.xml records edges for BOTH directories and registers both files in parentPomFiles. A per-(heir, file) cycle guard keeps hostile ring chains terminating. Pinned by walks a parent chain that passes through named parent files.
  • R8-26 — doc/metadata exemptions matched by path prefix: README now matches /^README(?:\.[^/]*)?$/i and LICENSE/NOTICE use (?:\.[^/]*)?, anchoring every exemption to the final segment. README.md/checkstyle.xml, LICENSE.md/settings.xml, and NOTICE.txt/build.sh now build (reactor-wide at the root, module-scoped under a module). Pinned by does not exempt subtrees of directories named after doc or metadata files.
  • R8-10 — named-parent branch short-circuited owner routing: the named-parent branch no longer continues; after adding the inheritor closure it falls through to the owner/inactive-project routing. A named parent living in another module's tree now builds that module too, and an out-of-reactor named parent hits the inactive-project abort like any sibling file. Pinned by adds the owning module of a changed named parent file to the scope and keeps the inactive-project abort for an out-of-reactor named parent.
  • R8-25 — closure walk conflated the visited guard with the result set: both POM-change closure walks now share one helper with a seen set separate from modules, so a descendant already in the result set through an earlier changed file is still expanded. Pinned by expands a descendant already in the scope through an earlier changed file (the finding's exact reactor: parent aggregates app via ../app and is its <parent>; app aggregates app/it).
  • R8-1 — fail-never distrust missed launch-class failures: swallowedFailure now also checks isLaunchFailure, so a fail-never exit 0 over a mid-command ENOSPC reads as a failure and keeps the launch-class infrastructure attribution instead of passing green. Pinned by classifies a fail-never launch failure as infrastructure, not a pass.
  • R8-13 — spawn deaths shadowed by green siblings: the ran preference chain now ranks a spawn-level death (exitCode null, no deadline kill, non-Maven claims) ABOVE a green finished run and below an actual failed run, so a green package can no longer shadow a dead sibling and read the claim reproduces while the report says ok: false. Pinned by ranks a spawn-level death above a green finished sibling run; all pre-existing settlement pins stay green.
  • R8-35 — mavenLifecycle read the last string token: lifecycle extraction is now a flag-value-aware token walk (mavenPositionalTokens with a MAVEN_VALUE_FLAGS set, quote-aware like mavenPlModules). -pl test (a phase-named module dir) no longer reads as the lifecycle, and claimPhases uses the same walk so a -pl value is never counted as a claimed phase. Pinned by does not read a phase-named -pl value as the claimed lifecycle with an ordinary-module control case.

Suggestions

  • R8-5 — duplicated closure traversals: extracted into the shared addDescendantClosure helper (see R8-25).
  • R8-2 (three sites) — timeout notes quoted the --timeout flag: a deadlineSecs helper (Math.round((deadlineMs ?? perCommandMs) / 1000), mirroring the npm adapter) now feeds the warm-up outcome, the interrupted-run cause line, and the lifecycle timeout note. Tests record the deadline through the exec signature and pin the fired numbers (60s warm-up, 15s lifecycle under a budget of 60s with timeout 300s).
  • R8-28 — chained local repositories untracked: mavenConfigDependencyInputs now parses -Dmaven.repo.local.tail= (checked before the shorter -Dmaven.repo.local= prefix), splits the value on , and |, and adds each entry as a dependency input. Pinned by treats chained local repositories from -Dmaven.repo.local.tail as dependency inputs.
  • R8-3 — wrong comment on the member-POM branch: the comment no longer claims the branch is reachable only for deleted POMs; it now states that a changed member POM counts exactly like the root pom.xml, with fixture POMs under src/ and out-of-reactor POMs as the excluded shapes.
  • R8-38 — budget note claimed "spent" when nothing ran: when spentMs === 0 the note now says the granted budget is below the attempt-floor minimum instead of asserting a consumption with no consumer. The pin was updated accordingly.
  • R8-22 — exit-0 warm-up left unnamed in the budget-exhausted note: the warm-up suffix is now keyed on install alone, so an exit-0 warm-up that consumed the budget is named (Dependency warm-up (…) exited 0 — the budget it consumed is what stopped the lifecycle command).
  • R8-33 — wrapper spellings stopped at one parent level: RUNNER_RE, MAVEN_RUNNER_RE, and bareMavenLifecycle now share one MAVEN_RUNNER_SOURCE that accepts ./mvnw, .\mvnw, and any number of ..//..\ hops. ../../mvnw test is extracted as a command; bare ../../mvnw.cmd no longer mis-extracts as a path claim. Pinned in the extended spelling test.
  • R8-31 — Maven count parser ungated: observedTestCounts gates the marker loop on MAVEN_RUNNER_RE.test(cmd.command). The four Maven pins now run under ./mvnw test, and a new pin asserts fabricated markers inside a non-Maven command are not counted.
  • R8-23 — mavenHasAlsoMake not quote-aware: the scan now consumes a quoted -pl/--projects selector as one token, so -am inside a module dir name is not read as the flag. Pinned by does not read a quoted -am inside a -pl selector as the flag.
  • R8-32 (both sites) — -am carve-out unconditionally excluded failed runs: a failureInsideClaim helper attributes each run's [maven-test-failure] markers to <module>/target/ report paths; when any resolves inside the claimed -pl set, the exclusion yields and the run contradicts the claim. Applied to both the finished-run filter and the interrupted-run branch. Pinned by keeps a failed -am run contradicting… and keeps an interrupted -am run contradicting…; the upstream-only pins stay green.
  • R8-7 — phaseReduced only keyed on settledReduced: it now also keys on settledBySameScope, so a -pl-scoped multi-phase claim settled by a single-phase same-scope run discloses the phase reduction. Pinned by discloses the phase reduction of a -pl-scoped multi-phase claim too.
  • R8-39 — phase-first spellings never settled: covered by the mavenLifecycle token walk — ./mvnw test -pl core and mvn test -B now settle on a same-scope/phase run instead of the false "different scope or phase" fallback. Pinned by settles a phase-first Maven claim whose flags follow the phase.
  • R8-9 — negation exclusion untested: two new base-tree pins: a negation that excludes the only workspace member keeps the nested-pom probe active (unavailable, zero builds, Maven note), and its positive twin where the negation excludes nothing stays npm-applicable.
  • R8-41 — polyglot asymmetry untested: new base-tree pin: a base carrying both a root pom.xml and an npm-applicable package.json is Maven — unavailable, zero builds, and the never-a-finding note.
  • R8-43 — fixture commits via git add -A: all twelve fixture commits now add explicit paths, so the review worktree inside the fixture repo is no longer committed as an embedded-repo gitlink (verified: git ls-tree of the fixtures no longer lists .qwen/tmp/review-pr-1).
  • R8-6 — Maven-gate tests duplicated boilerplate: all twelve Maven-gate tests now use the shared run({ plan: { mergeBaseSha } }, build) helpers instead of hand-writing plan.json + runBaseTree.
  • R8-42 — failed marker unpinned: the build-handoff test now also asserts .qwen-review-base-failed exists.
  • R8-40 — design-doc oracle overstated: the "shell-active module names fail closed" item is rewritten to match the implemented model — only names carrying [<$>{}&%,:] fail closed; any other module name reaching a -pl selector is quoted for the platform shell rather than interpolated bare.

The review-level note also listed "Not reviewed" CI legs (macOS/Windows, integration tests, reverse audit round). Those are review-process coverage observations with no code-level action available in this checkout; they are recorded here for transparency.

Verification

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0; note: a first run before npm run build showed pre-existing packages/webui errors caused by unbuilt acp-bridge generated artifacts — clean after build, unrelated to this diff)
  • npm run lint — passed (exit 0)
  • npx prettier --check on the six changed files — passed (after --write on three)
  • Focused Vitest in packages/cli: src/commands/review/build-test.test.ts, lib/npm-toolchain.test.ts, lib/maven-toolchain.test.ts, test-plan.test.ts, base-tree.test.ts, agent-prompt.test.ts, test-delta.test.ts — 7 files, 588 tests passed
  • Integration tests after npm run bundle — not run: the changed behavior is fully exercised by the focused unit suites, not only through the bundled CLI
  • npm run generate:settings-schema — not applicable: no settings source changed
中文说明

第 8 轮 review 反馈处理总结(PR #8394

提交:refactor/review-toolchain-adapter 分支上的 fix(review): Close round-8 Maven routing, settlement, and evidence gaps (#8394)

全部 8 条 Critical 与 21 条 Suggestion 均已对照代码核实、复现并修复。没有任何一条被拒绝(decline)或升级给人工(escalate)。

Critical 问题

  • R7-7 — POM 读取无大小上限(maven-toolchain.ts):POM 读取现在在每次 readFileSync 之前通过 statSync 检查 2 MiB 上限(reactor 的 visit() 递归与命名父文件读取两处都覆盖),超限时返回与其他不可读形状一致的 fail-closed reactor 错误串。由 fails closed on a POM larger than the read cap 测试钉住。
  • R8-11 — 命名父文件继承链被丢弃:继承边解析改为工作列表(worklist)。解析到一个命名父文件时,会解析它自己的 <parent> 声明并以同一个 heir 入队、从该文件自身目录继续解析,因此 app → base/parent.xml → grand/parent2.xml 会为两个目录都记录边,并把两个文件都登记进 parentPomFiles。按 (heir, 文件) 的循环守卫保证恶意环形链能够终止。由 walks a parent chain that passes through named parent files 钉住。
  • R8-26 — 文档/元数据豁免按路径前缀匹配README 改为 /^README(?:\.[^/]*)?$/iLICENSE/NOTICE 改为 (?:\.[^/]*)?,所有豁免都锚定到最后一个路径段。README.md/checkstyle.xmlLICENSE.md/settings.xmlNOTICE.txt/build.sh 现在都会构建(根目录为全 reactor,模块下为模块范围)。由 does not exempt subtrees of directories named after doc or metadata files 钉住。
  • R8-10 — 命名父文件分支绕过了 owner 路由:命名父文件分支不再 continue;在加入继承闭包后继续走 owner/inactive-project 路由。位于其他模块树内的命名父文件现在也会构建并测试那个模块;reactor 之外的命名父文件则与同目录其他文件一样触发 inactive-project 中止。由 adds the owning module of a changed named parent file to the scopekeeps the inactive-project abort for an out-of-reactor named parent 钉住。
  • R8-25 — 闭包遍历把"访问守卫"与"结果集"混为一谈:两处 POM 变更闭包遍历共用一个辅助函数,seen 集合与 modules 分离,因此即使某个后代已经因更早的变更文件进入结果集,它仍会被展开。由 expands a descendant already in the scope through an earlier changed file 钉住(使用 finding 中的确切 reactor:parent 通过 ../app 聚合 app 且是它的 <parent>app 聚合 app/it)。
  • R8-1 — fail-never 的防骗检查漏掉启动类失败swallowedFailure 现在也检查 isLaunchFailure,因此 fail-never 下命令中途 ENOSPC 导致的 exit 0 会读作失败,并保持启动类的基础设施归因,而不是显示为通过。由 classifies a fail-never launch failure as infrastructure, not a pass 钉住。
  • R8-13 — spawn 死亡被绿色兄弟运行遮蔽ran 优先链现在把 spawn 级死亡(exitCode 为 null、非 deadline 杀死、非 Maven claim)排在绿色 finished 运行之上、真实失败运行之下,绿色的 package 不再能遮蔽死掉的兄弟运行,使 claim 在 report 为 ok: false 时仍被读作 reproduces。由 ranks a spawn-level death above a green finished sibling run 钉住;所有既有判定钉全部保持绿色。
  • R8-35 — mavenLifecycle 读取字符串最后一个 token:生命周期提取改为感知 flag 值的 token 遍历(mavenPositionalTokens + MAVEN_VALUE_FLAGS 集合,与 mavenPlModules 一样感知引号)。-pl test(以 phase 命名的模块目录)不再被读作生命周期;claimPhases 也改用同一遍历,-pl 的值永远不会被计为 claim 的 phase。由 does not read a phase-named -pl value as the claimed lifecycle 钉住,并附普通模块名的对照用例。

Suggestion 问题

  • R8-5 — 重复的后代闭包遍历:抽成共享的 addDescendantClosure 辅助函数(见 R8-25)。
  • R8-2(三处)— 超时备注引用的是 --timeout 标志:新增 deadlineSecs 辅助函数(Math.round((deadlineMs ?? perCommandMs) / 1000),与 npm adapter 同构),warm-up 结果、中断运行原因行、生命周期超时备注都改用它。测试通过 exec 签名记录 deadline 并钉住实际触发的秒数(budget 60s 下 warm-up 为 60s;budget 60s、timeout 300s 时生命周期为 15s)。
  • R8-28 — 链式本地仓库未被追踪mavenConfigDependencyInputs 现在解析 -Dmaven.repo.local.tail=(放在更短的 -Dmaven.repo.local= 前缀之前检查),按 ,| 拆分取值,并把每个条目作为依赖输入。由 treats chained local repositories from -Dmaven.repo.local.tail as dependency inputs 钉住。
  • R8-3 — 成员 POM 分支上的错误注释:注释不再声称该分支只对已删除的 POM 可达;现在准确表述:变更的成员 POM 与根 pom.xml 一样计入解析输入,src/ 下的 fixture POM 与 reactor 之外项目的 POM 才是被排除的形状。
  • R8-38 — 预算备注在什么都没跑时声称"was spent"spentMs === 0 时备注改为"授予的预算低于单次尝试下限",不再虚构一次没有消费者的消耗。对应测试断言已同步更新。
  • R8-22 — 预算耗尽备注对 exit-0 warm-up 不点名:warm-up 后缀现在只以 install 是否存在为条件,exit 0 但消耗了预算的 warm-up 也会被点名(Dependency warm-up (…) exited 0 — the budget it consumed is what stopped the lifecycle command)。
  • R8-33 — wrapper 拼法止步于单层父目录RUNNER_REMAVEN_RUNNER_REbareMavenLifecycle 现在共享同一个 MAVEN_RUNNER_SOURCE,接受 ./mvnw.\mvnw 以及任意层数的 ..//..\ 跳转。../../mvnw test 会被提取为命令;裸 ../../mvnw.cmd 不再被误提取为路径 claim。由扩展后的拼法测试钉住。
  • R8-31 — Maven 计数解析没有 runner 门控observedTestCounts 对标记循环加了 MAVEN_RUNNER_RE.test(cmd.command) 门控。四个 Maven 钉改为在 ./mvnw test 命令名下运行,并新增钉:非 Maven 命令输出里伪造的标记不会被计数。
  • R8-23 — mavenHasAlsoMake 不感知引号:扫描现在把带引号的 -pl/--projects 选择器整体消费为一个 token,模块目录名内部的 -am 不会被读作 flag。由 does not read a quoted -am inside a -pl selector as the flag 钉住。
  • R8-32(两处)— -am 例外无条件丢弃失败运行:新增 failureInsideClaim 辅助函数,把运行的 [maven-test-failure] 标记按报告路径 <module>/target/ 归因;任一路径落在 claimed -pl 集合内时例外让位,该运行可以反驳 claim。finished 运行过滤与中断运行分支同时应用。由 keeps a failed -am run contradicting…keeps an interrupted -am run contradicting… 钉住;失败仅在上游模块的既有钉保持绿色。
  • R8-7 — phaseReduced 只挂在 settledReduced:现在也挂在 settledBySameScope 上,-pl 作用域的多 phase claim 被单 phase 同 scope 运行判定时会披露 phase 缩减。由 discloses the phase reduction of a -pl-scoped multi-phase claim too 钉住。
  • R8-39 — phase 前置拼法永远无法判定:由 mavenLifecycle 的 token 遍历一并修复——./mvnw test -pl coremvn test -B 现在会在同 scope/phase 的运行上判定,而不再落入错误的"different scope or phase"兜底。由 settles a phase-first Maven claim whose flags follow the phase 钉住。
  • R8-9 — 否定式 glob 排除没有测试:新增两个 base-tree 钉:否定式排除掉唯一 workspace 成员时,嵌套 pom 探针仍然生效(unavailable、零构建、Maven 备注);其正向对照(否定式没排除任何成员)保持 npm 适用。
  • R8-41 — polyglot 不对称没有测试:新增 base-tree 钉:base 同时带根 pom.xml 与 npm 适用 package.json 时按 Maven 处理——unavailable、零构建,并保留 never-a-finding 备注。
  • R8-43 — fixture 提交使用 git add -A:十二个 fixture 提交全部改为显式路径 add,fixture 仓库内的 review worktree 不再作为内嵌仓库 gitlink 被提交(已验证:fixture 的 git ls-tree 不再列出 .qwen/tmp/review-pr-1)。
  • R8-6 — Maven gate 测试重复样板:十二个 Maven gate 测试全部改用共享的 run({ plan: { mergeBaseSha } }, build) 辅助,不再手写 plan.json + runBaseTree
  • R8-42 — 失败 marker 未钉住:build 交接测试现在同时断言 .qwen-review-base-failed 存在。
  • R8-40 — 设计文档 oracle 夸大:把"shell-active 模块名 fail closed"改写为与实现一致的表述——只有含 [<$>{}&%,:] 的名字 fail closed;其他到达 -pl 选择器的模块名按平台 shell 引用(POSIX 单引号包裹、win32 "…"% 拒绝与文件名门控),而不是裸插值进 shell: true 命令行。

review 级别的备注中还列出了"Not reviewed"的 CI 腿(macOS/Windows、集成测试、反向审计轮次)。这些是 review 流程的覆盖面观察,在本 checkout 中没有可执行的代码动作;在此记录以保持透明。

验证(Verification)

  • npm run build — 通过(exit 0)
  • npm run typecheck — 通过(exit 0;说明:首次在 npm run build 之前运行时出现的 packages/webui 报错,是 acp-bridge 生成产物未构建所致——build 后即干净,与本 diff 无关)
  • npm run lint — 通过(exit 0)
  • 对六个改动文件运行 npx prettier --check — 通过(其中三个先经 --write 修正)
  • packages/cli 内聚焦 Vitest:src/commands/review/build-test.test.tslib/npm-toolchain.test.tslib/maven-toolchain.test.tstest-plan.test.tsbase-tree.test.tsagent-prompt.test.tstest-delta.test.ts — 7 个文件、588 个测试全部通过
  • npm run bundle 后的集成测试 — 未运行:改动行为完全由聚焦单元测试覆盖,并非只能通过打包后的 CLI 执行
  • npm run generate:settings-schema — 不适用:未改动任何 settings 源

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.

Not reviewed: build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) CI legs were skipped at this commit; the platform-dependent branches (win32 arms in maven-toolchain.ts, platform guards in the new test files) were exercised only on Linux.

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

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

Test Plan (not a blocker): ./mvnwno such file or directory; 326 tests to pass — this review observed 18298 passed.

中文说明

未审查:build-and-test — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) CI legs were skipped at this commit; the platform-dependent branches (win32 arms in maven-toolchain.ts, platform guards in the new test files) were exercised only on Linux。

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

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

Test Plan(非阻断):./mvnwno such file or directory; 326 tests to pass — this review observed 18298 passed

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

Comment on lines +874 to +875
const freshTestFailures = (c: CommandResult): boolean =>
/^\[maven-test-failure\] /m.test(c.output ?? '');

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] [maven-test-failure] marker mining in ruleCommand is not gated on the command being a Maven run — unlike the sibling [maven-test-report] count mining in observedTestCounts, which this same diff explicitly runner-gates (MAVEN_RUNNER_RE.test(cmd.command), with the comment "the same text mined from a non-Maven command's stdout is a fabricated count"). — Failure scenario: in an npm repo, a GREEN (exitCode: 0) npm test run whose stdout merely contains a line beginning with [maven-test-failure] (any test can console.log it; this repo's own maven-toolchain/test-plan suites print those literals) makes ranFailed true; the run is selected via finished(c) && ranFailed(c), and the author's `npm test` Test Plan claim is ruled contradicted with observed "exit 0, but fresh Surefire/Failsafe reports record failures". Before this diff, a green exit-0 match ruled reproduces. Probe-verified at the reviewed commit: gating flips the verdict back to reproduces with all 130 existing tests still green. The marker is only ever emitted by the Maven adapter, and a report is single-toolchain, so in an npm result it can never be legitimate evidence.

Suggested change
const freshTestFailures = (c: CommandResult): boolean =>
/^\[maven-test-failure\] /m.test(c.output ?? '');
const freshTestFailures = (c: CommandResult): boolean =>
MAVEN_RUNNER_RE.test(c.command) &&
/^\[maven-test-failure\] /m.test(c.output ?? '');
中文说明

ruleCommand 中对 [maven-test-failure] 标记的挖掘没有按 Maven 命令进行门控——而同一 diff 里 observedTestCounts 中对 [maven-test-report] 计数的挖掘却明确做了 runner 门控。失败场景:在 npm 仓库中,一次绿色(exitCode 0)的 npm test 运行,只要其 stdout 中出现以 [maven-test-failure] 开头的行(任何测试都可以打印它;本仓库自己的 maven-toolchain/test-plan 测试套件就包含这些字面量),ranFailed 即为 true,该运行会被选中,作者的 `npm test` Test Plan 声明会被判为 contradicted(observed 为 "exit 0, but fresh Surefire/Failsafe reports record failures")。此 diff 之前,绿色的 exit-0 匹配判为 reproduces。已在被审提交上探针验证:加上门控后裁决翻转为 reproduces,且现有 130 个测试全部仍通过。该标记只会由 Maven adapter 发出,且一次报告只会是单一工具链,所以在 npm 结果中它绝不可能是合法证据。

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

Comment on lines +500 to +502
let namedStructure: PomStructure | null = null;
if (basename(parentPom) === 'pom.xml') {
targetArtifactId = structures.get(parentPath)?.artifactId ?? null;

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] Inheritance chains that pass through a pom.xml in a NON-aggregated directory are silently dropped: only named (non-pom.xml) parent files get worklist chain continuations, so the edge dies here at structures.get(parentPath)?.artifactId for a pom.xml outside the visited reactor projects. — Failure scenario (probe-verified): root aggregates app and parent-bom; app/pom.xml declares <parent>../shared/parent/pom.xml (aggregated nowhere); shared/parent/pom.xml declares its own <parent>../../parent-bom/pom.xml. readMavenReactor returns inheritors: {}; a diff changing parent-bom/pom.xml yields modules: ['parent-bom'] and runs -pl parent-bom -am testapp, which inherits the changed parent through the chain, is never compiled or tested, and the run reports green. The twin shape (changing the intermediate file itself) correctly fails closed via inactiveProjects; this residual is the pom.xml-spelled intermediate. Residual of the round-8 R8-11 fix family.

Suggested fix: when basename(parentPom) === 'pom.xml' but parentPath is not in structures and the file exists, parse it like the named-file branch (record it and continue its own <parent> chain for the same heir), or fail the reactor closed with a reportable error.

中文说明

经过“非聚合目录中的 pom.xml”的继承链会被静默丢弃:只有非 pom.xml 命名的父文件才会获得工作列表的链式延续,因此对于已访问 reactor 项目之外的 pom.xml,继承边在这里死于 structures.get(parentPath)?.artifactId。失败场景(已探针验证):根聚合 appparent-bomapp/pom.xml 声明 <parent>../shared/parent/pom.xml(该目录未被任何聚合包含);shared/parent/pom.xml 又声明自己的 <parent>../../parent-bom/pom.xmlreadMavenReactor 返回 inheritors: {};修改 parent-bom/pom.xml 的 diff 得到 modules: ['parent-bom'] 并运行 -pl parent-bom -am test —— 通过该链继承了被修改父 POM 的 app 从未被编译或测试,而运行报告为绿色。孪生形态(直接修改中间文件本身)会经由 inactiveProjects 正确 fail closed;此残留是拼写为 pom.xml 的中间文件形态。属于第 8 轮 R8-11 修复家族的残留。建议修复:当 basename(parentPom) === 'pom.xml'parentPath 不在 structures 中且文件存在时,按命名文件分支处理(记录它并为同一继承者继续其自身的 <parent> 链),或以可报告的错误让 reactor fail closed。

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

Comment on lines +694 to +696
const path = normalizedChangedPath(root, changedFile);
if (path === null) continue;
if (path === otherPlatformWrapper) continue;

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] The other-platform-wrapper skip runs before the named-parent lookup and the REACTOR_WIDE_FILES routing, so a recorded parent-config file spelled mvnw.cmd (POSIX) or mvnw (win32) is ignored entirely — no closure, no reactor-wide escalation, no target. — Failure scenario (probe-verified): heir app declares <parent> with relativePath ../mvnw.cmd (a root-level named parent POM literally named mvnw.cmd); readMavenReactor records the edge (inheritors: {'.': ['app']}, parentPomFiles: ['mvnw.cmd']). A diff changing mvnw.cmd on POSIX hits this skip; ownership returns empty modules, and runMavenToolchain reports "There is no Maven target to run — this is a complete answer" with zero commands — a green no-op for a parent-config change app inherits. The skip exists so a change confined to the other platform's wrapper doesn't run the reactor, but it must not swallow a file that is simultaneously a recorded build input.

Suggested change
const path = normalizedChangedPath(root, changedFile);
if (path === null) continue;
if (path === otherPlatformWrapper) continue;
const path = normalizedChangedPath(root, changedFile);
if (path === null) continue;
if (path === otherPlatformWrapper && !namedParentDirs.has(path)) continue;
中文说明

“另一平台 wrapper”的跳过逻辑先于命名父文件查找与 REACTOR_WIDE_FILES 路由执行,因此一个被记录为父配置文件、但拼写恰好是 mvnw.cmd(POSIX)或 mvnw(win32)的文件会被完全忽略——没有闭包、没有 reactor-wide 升级、没有任何目标。失败场景(已探针验证):继承者 app 声明 <parent>relativePath../mvnw.cmd(根目录下一个字面命名为 mvnw.cmd 的命名父 POM);readMavenReactor 记录了该边。在 POSIX 上修改 mvnw.cmd 的 diff 命中此跳过;ownership 返回空模块集,runMavenToolchain 报告 "没有需要运行的 Maven 目标——这是一个完整答案",零命令——对一个 app 所继承的父配置变更给出了绿色的无操作。该跳过的存在是为了让仅修改另一平台 wrapper 的变更不触发 reactor 运行,但它不应吞掉同时又是已记录构建输入的文件。

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

Comment on lines +619 to +620
const src = projectDir === '.' ? 'src' : `${projectDir}/src`;
return rel === src || rel.startsWith(`${src}/`);

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] isUnderTestSourceTree's rel === src equality clause classifies a real Maven project located exactly at a project's src path as test-data fixture, defeating the out-of-reactor fail-closed rule (ownership rule 5) for that shape. — Failure scenario (probe-verified with flip): a standalone, non-aggregated project at <root>/src/pom.xml; the PR changes src/main/java/App.java inside it. nearestMavenProject's upward walk finds src/pom.xml, but isUnderTestSourceTree('src') returns true via the equality half, so the walk returns .; no inactiveProjects entry is added, the adapter runs mvn -pl . -am test and reports "Maven tested . successfully … this run covered the changed modules" — the changed project is never compiled. Removing the equality half flips to unsupported / "outside the root reactor: src" with zero commands; all 124 existing tests still pass without the clause, and real fixture shapes live strictly below src/.

Suggested change
const src = projectDir === '.' ? 'src' : `${projectDir}/src`;
return rel === src || rel.startsWith(`${src}/`);
const src = projectDir === '.' ? 'src' : `${projectDir}/src`;
return rel.startsWith(`${src}/`);
中文说明

isUnderTestSourceTreerel === src 相等分支会把恰好位于某项目 src 路径上的真实 Maven 项目判定为测试数据 fixture,从而使针对该形态的 reactor 外 fail-closed 规则(ownership 规则 5)失效。失败场景(已探针验证并翻转):一个独立、未被聚合的项目位于 <root>/src/pom.xml;PR 修改其中的 src/main/java/App.javanearestMavenProject 向上查找发现 src/pom.xml,但 isUnderTestSourceTree('src') 经相等分支返回 true,于是查找返回 .;不会添加任何 inactiveProjects 条目,adapter 运行 mvn -pl . -am test 并报告 "Maven tested . successfully … this run covered the changed modules" —— 被修改的项目从未被编译。去掉相等分支后翻转为 unsupported/"outside the root reactor: src" 且零命令;没有该分支时现有 124 个测试全部通过,且真实 fixture 形态都严格位于 src/ 之下。

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

Comment on lines +105 to +106
const callBudgetMs =
(args.budget ?? Math.max(args.timeout, args.timeout * 2 - 30)) * 1000;

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] Deadline values are never coerced to positive-integer milliseconds before spawnSync: fractional --timeout/--budget values throw ERR_OUT_OF_RANGE (the command dies with a raw message, no report, no --out file), and --timeout 0 silently arms no kill timer. The npm arm is verbatim-moved pre-existing code, but the Maven arm is new in this PR, and its new attemptFloorMs makes --timeout 0 spawnable (the old fixed 15 s floor blocked every attempt). — Failure scenario (measured on node 22): 32.2 * 1000 = 32200.000000000004; spawnSync with timeout: 15000.1 throws "The value of "timeout" is out of range. It must be an unsigned integer"; end-to-end through runBuildTest, both npm (budget: 32.2) and Maven fixtures crash, exit 1, no JSON report. --timeout 0: spawnSync {timeout: 0} arms no kill timer (a 2 s sleep ran to completion, measured) — the Maven lifecycle then runs deadline-less, and a cold-reactor warm-up can hang forever.

Suggested fix: sanitize once, e.g. const perCommandMs = Math.max(1, Math.round(args.timeout * 1000)); and round callBudgetMs the same way (and/or validate integer seconds in the yargs builders of build-test/base-tree/test-delta).

中文说明

截止期数值在传给 spawnSync 之前从未被强制转换为正整数毫秒:小数 --timeout/--budget 会抛出 ERR_OUT_OF_RANGE(命令以裸消息退出,没有报告、没有 --out 文件),而 --timeout 0 会静默地不设置任何终止定时器。npm 分支是逐字搬移的既有代码,但 Maven 分支是本 PR 新增的,且其新的 attemptFloorMs 使 --timeout 0 可以被实际执行(旧的固定 15 秒下限会挡住所有尝试)。失败场景(在 node 22 上实测):32.2 * 1000 = 32200.000000000004timeout: 15000.1spawnSync 抛出越界错误;端到端经过 runBuildTest,npm(budget: 32.2)与 Maven fixture 都会崩溃、exit 1、无 JSON 报告。--timeout 0spawnSync {timeout: 0} 不设置终止定时器(实测 2 秒 sleep 完整跑完)—— Maven 生命周期将在无截止期状态下运行,冷 reactor 的预热可能永久挂起。建议修复:统一取整 sanitise(例如 Math.max(1, Math.round(args.timeout * 1000))),并/或在 yargs builder 中校验整数秒。

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

Comment on lines +1842 to +1845
export const mavenToolchainAdapter: ReviewToolchainAdapter = {
applies: (root) => existsSync(join(root, 'pom.xml')),
run: runMavenToolchain,
};

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] Maven's applies() does not mirror its run-side gate, so at a mixed npm+Maven root a Maven run that would immediately concede unsupported still blocks the npm adapter — the exact defect class the npm adapter's applies() comment says this PR fixed. This is the round-6 finding R6-22, deferred to a follow-up round; this is that round. — Failure scenario (probe-verified at this commit): mixed root (modeled packages/* workspaces with a member package + root pom.xml containing <module>${x}</module>): runBuildTest returns the generic "Both npm and Maven apply … ran nothing" note with 0 exec calls — although mavenToolchainAdapter.run alone concedes unsupported before executing any command (!parsed.reactorunsupportedReport), and npmToolchainAdapter.run alone returns toolchain: 'npm' (fully applicable). The review loses its one deterministic build/test verdict, and the ambiguity note hides Maven's concession reason. Safe direction (no false verdict) — Suggestion.

Suggested fix: tighten the gate to mirror the run side, e.g. applies: (root) => readMavenReactor(root).reactor !== null (the npm gate already pays for a full tree walk in applies()); or in the ambiguity branch, discount an applicable adapter whose run concedes unsupported without executing.

中文说明

Maven 的 applies() 没有镜像其运行侧门控,因此在 npm+Maven 混合根目录下,一个本会立即让步为 unsupported 的 Maven 运行仍会挡住 npm adapter——这正是 npm adapter 的 applies() 注释声称本 PR 已修复的缺陷类别。这就是第 6 轮的发现 R6-22,当时被推迟到后续轮次处理;本轮即是该后续轮次。失败场景(已在当前提交探针验证):混合根(建模的 packages/* workspaces 且有成员包 + 含 <module>${x}</module> 的根 pom.xml):runBuildTest 返回通用的 "Both npm and Maven apply … ran nothing" 注记且 0 次 exec 调用——尽管 mavenToolchainAdapter.run 单独运行时会在执行任何命令前让步为 unsupported,而 npmToolchainAdapter.run 单独运行会返回 toolchain: 'npm'(完全适用)。评审失去唯一的确定性构建/测试裁决,且歧义注记隐藏了 Maven 的让步原因。方向安全(不会产生错误裁决)——Suggestion。建议修复:收紧门控以镜像运行侧(例如 applies: (root) => readMavenReactor(root).reactor !== null;npm 门控本就已在 applies() 中付出整树遍历的代价),或在歧义分支中对 "运行会不执行任何命令即让步为 unsupported" 的适用 adapter 打折。

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

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.

Deferred (round cap). Maven's applies() not mirroring its run-side gate (R6-22, probe-verified again at this commit) is acknowledged and still valid. This PR is in review round 9; per AGENTS.md, rounds past ~5 land only Critical fixes. Tightening the gate to readMavenReactor(root).reactor !== null (or discounting an unsupported-conceding adapter in the ambiguity branch) will land in a follow-up; the deferral is recorded here so nothing is silently dropped.

中文说明

推迟(轮次上限)。 Maven 的 applies() 未镜像其运行侧门控(R6-22,在本提交再次探针验证)被认可且仍然成立。本 PR 已进入第 9 轮评审;按 AGENTS.md,超过约 5 轮后只落地 Critical 修复。将门控收紧为 readMavenReactor(root).reactor !== null(或在歧义分支中对会让步为 unsupported 的 adapter 打折)将在后续落地;此回复记录推迟以免被静默丢弃。

Comment on lines +293 to +294
P1 intentionally does not implement Maven dependency resolution in TypeScript.
It reads only the aggregation structure needed to map paths to module directories:

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 "Reactor and module ownership" section describes an aggregation-only discovery algorithm ("reads only the aggregation structure", and below "Parent inheritance … remain Maven's job through -am"), but the shipped implementation reads and walks <parent> inheritance edges — contradicting both the code and this same doc's Testing section ("aggregation edges AND <parent> inheritance edges are read from checked-in POMs"). — Concrete cost: this doc is the committed design-to-implementation record per AGENTS.md, and prior rounds already fixed four instances of this same drift class. A maintainer or doc-driven cleanup reconciling code against this section sees the inheritance machinery (inheritors, addDescendantClosure walking children AND inheritors, ~115 lines) declared out-of-spec; removing it per the spec regresses a changed parent POM to -pl <aggregator> only, so inheriting modules are never compiled or tested and the run reports green — the exact false-green class the Criticals on maven-toolchain.ts hunt. The -am attribution is itself inaccurate (-am is upstream-only; the adapter does the inheritor fan-out itself).

Suggested fix: rewrite the section to match the implementation and the Testing section — reads aggregation structure AND <parent> inheritance edges; add a step resolving each project's <parent> <relativePath> (including named parent POM files) and closing over both edge classes; narrow the closing paragraph to effective-POM semantics (dependency resolution/management, reactor ordering) only.

中文说明

"Reactor and module ownership" 一节描述的是仅聚合的发现算法("只读取聚合结构",以及下文 "父继承……仍由 Maven 通过 -am 负责"),但实际实现读取并遍历 <parent> 继承边——与代码及同一文档 Testing 一节("聚合边与 <parent> 继承边都从提交的 POM 中读取")相矛盾。具体代价:按 AGENTS.md,本文档是提交在案的设计-实现记录,且此前各轮已修复过四个同类漂移实例。维护者或文档驱动的清理工作在按本节对齐代码时,会看到继承机制(inheritors、同时遍历 children 与 inheritors 的 addDescendantClosure,约 115 行)被声明为规范之外;按规范删除它会把被修改父 POM 的构建范围退化为仅 -pl <aggregator>,继承它的模块从不被编译或测试而运行仍报告绿色——正是 maven-toolchain.ts 上那些 Critical 所追猎的假绿类别。-am 的归因本身也不准确(-am 只拉上游;继承者扇出是 adapter 自己做的)。建议修复:改写该节以与实现和 Testing 节一致——读取聚合结构与 <parent> 继承边;增加一步解析每个项目的 <parent> <relativePath>(包括命名父 POM 文件)并对两类边做闭包;把结尾段落收窄到仅 effective-POM 语义(依赖解析/管理、reactor 排序)。

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

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.

Deferred (round cap). The design-doc drift in the "Reactor and module ownership" section (aggregation-only wording vs the shipped inheritance walk) is acknowledged and still valid. This PR is in review round 9; per AGENTS.md, rounds past ~5 land only Critical fixes. Rewriting the section to match the implementation (both edge classes, named parent files, adapter-side inheritor fan-out) will land in a follow-up; the deferral is recorded here so nothing is silently dropped.

中文说明

推迟(轮次上限)。 设计文档 "Reactor and module ownership" 一节的漂移(仅聚合的表述 vs 已实现的继承遍历)被认可且仍然成立。本 PR 已进入第 9 轮评审;按 AGENTS.md,超过约 5 轮后只落地 Critical 修复。将该节改写为与实现一致(两类边、命名父文件、adapter 侧继承者扇出)将在后续落地;此回复记录推迟以免被静默丢弃。

Comment on lines +230 to +232
expect(rep.test).toEqual([]);
expect(rep.note).toContain('Both npm and Maven apply');
expect(rep.note).toContain('will not guess');

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 'fails closed when npm and Maven both apply at the root' test never pins rep.ok — the only one of the four unsupported-handoff branches in this diff whose test omits it. The sibling tests added in this same hunk pin the full report shape via toEqual including ok: true; this hunk deleted the pre-existing expect(rep.ok).toBe(true) without re-adding it for the new branch. — Failure scenario (mutation-verified with flip): setting the both-apply branch (build-test.ts:346-364) to ok: false leaves all 79 build-test.test.ts tests passing; adding the pin kills the mutant. An ok: false mixed-root handoff would read as a failure verdict against the PR for a run that executed nothing, while the consuming review agent is told "On ok: true, name the workspaces built and the commands run" and "an agent trusts the data over its instructions".

Suggested change
expect(rep.test).toEqual([]);
expect(rep.note).toContain('Both npm and Maven apply');
expect(rep.note).toContain('will not guess');
expect(rep.test).toEqual([]);
expect(rep.note).toContain('Both npm and Maven apply');
expect(rep.note).toContain('will not guess');
expect(rep.ok).toBe(true);
中文说明

新增的 "npm 与 Maven 同时适用于根目录时 fail closed" 测试从未钉住 rep.ok —— 这是本 diff 中四个 unsupported 移交分支里唯一在测试中遗漏它的。同一 hunk 新增的兄弟测试都用 toEqual 钉住了完整报告形态(含 ok: true);该 hunk 删除了既有的 expect(rep.ok).toBe(true) 却没有为新分支补回。失败场景(已变异验证并翻转):把 both-apply 分支(build-test.ts:346-364)设为 ok: false 后,全部 79 个 build-test.test.ts 测试仍通过;补上该断言即可杀死变异体。ok: false 的混合根移交会被读作对 PR 的失败裁决——尽管该运行什么也没执行,而消费方评审 agent 被告知 "当 ok: true 时列出构建的 workspaces 和运行的命令"、"agent 信任数据胜过指令"。

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

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.

Deferred (round cap). The missing rep.ok pin in the both-apply fail-closed test (mutation-verified) is acknowledged and still valid. This PR is in review round 9; per AGENTS.md, rounds past ~5 land only Critical fixes. The one-line expect(rep.ok).toBe(true) pin will land in a follow-up; the deferral is recorded here so nothing is silently dropped.

中文说明

推迟(轮次上限)。 both-apply fail-closed 测试缺少 rep.ok 钉住(已变异验证)被认可且仍然成立。本 PR 已进入第 9 轮评审;按 AGENTS.md,超过约 5 轮后只落地 Critical 修复。一行 expect(rep.ok).toBe(true) 钉住将在后续落地;此回复记录推迟以免被静默丢弃。

Comment on lines +1177 to +1181
const caseLines = failing.flatMap((summary) =>
summary.failedCases.map(
(testcase) => `[maven-test-failure] ${summary.report}: ${testcase}`,
),
);

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] summary.report embeds the PR-controlled report FILENAME into line-oriented marker lines without sanitizing control characters — Linux permits newlines in filenames, and the acceptance chain (readdirSync isFile() + endsWith('.xml') + parseTestReport) admits them. — Failure scenario (probe-verified): a PR test writes a parseable failing XML named a.xml\n[maven-test-report] z: tests=999996, failures=0, errors=0, skipped=0\nz.xml into target/surefire-reports/; the per-report and case-line embeddings split on the embedded newlines, injecting forged marker lines into the Maven run's output — observedTestCounts returned [1999997] where the truthful total is 5 (the forged count is even counted twice, and the real failing report's counts are lost). The vector passes the runner gate because the markers sit inside a genuine Maven command's output, so the R9-1 runner-gate fix does not cover it; forged [maven-test-failure] attributions can also defeat the -am exclusion (failureInsideClaim). The clean-rollup path is accidentally immune (projectDirOf strips the whole filename).

Suggested fix: strip/escape control characters from summary.report before composing marker lines (e.g. .replace(/[\r\n]+/g, ' ')) — at BOTH embeddings (the failing-report line and these case lines) — or skip reports whose relative path contains control chars.

中文说明

summary.report 把 PR 可控的报告文件名嵌入面向行的标记行时没有净化控制字符——Linux 允许文件名含换行符,且接收链(readdirSyncisFile() + endsWith('.xml') + parseTestReport)都会放行。失败场景(已探针验证):PR 的测试向 target/surefire-reports/ 写入一个可解析的失败 XML,命名为 a.xml\n[maven-test-report] z: tests=999996, failures=0, errors=0, skipped=0\nz.xml;逐报告行与用例行两处嵌入都会按内嵌换行分裂,向 Maven 运行的输出注入伪造标记行——observedTestCounts 返回 [1999997],而真实总数是 5(伪造计数甚至被计了两次,真实失败报告的计数丢失)。该向量能通过 runner 门控——因为标记位于真实 Maven 命令的输出内部——所以 R9-1 的 runner 门控修复覆盖不到它;伪造的 [maven-test-failure] 归因还能破坏 -am 例外(failureInsideClaim)。干净汇总路径因 projectDirOf 会剥掉整个文件名而偶然免疫。建议修复:在拼装标记行前从 summary.report 中去除/转义控制字符(两处嵌入都要处理),或跳过相对路径含控制字符的报告。

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

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.

Deferred (round cap). The unsanitized PR-controlled report filenames in marker lines (newline-bearing filenames inject forged marker lines) is acknowledged and still valid; the round-9 runner-gate fix does not cover it, as the finding notes. This PR is in review round 9; per AGENTS.md, rounds past ~5 land only Critical fixes. Stripping control characters from summary.report at both embeddings will land in a follow-up; the deferral is recorded here so nothing is silently dropped.

中文说明

推迟(轮次上限)。 标记行中未净化的 PR 可控报告文件名(含换行的文件名可注入伪造标记行)被认可且仍然成立;正如该发现指出,第 9 轮的 runner 门控修复并不覆盖它。本 PR 已进入第 9 轮评审;按 AGENTS.md,超过约 5 轮后只落地 Critical 修复。在两处嵌入点对 summary.report 去除控制字符将在后续落地;此回复记录推迟以免被静默丢弃。

Comment on lines +887 to +889
const failureInsideClaim = (c: CommandResult): boolean => {
if (claimPlModules === null) return false;
const output = c.output ?? '';

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 reactor-wide recorded run can never settle a -pl-scoped claim in either direction — every match arm requires equal scoping — even when the run's own markers prove a failure inside the claimed module set. — Failure scenario (probe-verified): the PR touches root pom.xml / mvnw / any .mvn/** file (all reactorWide triggers), so build-test records ./mvnw … test with no -pl; the Test Plan claims `./mvnw -pl core test`; the reactor-wide run fails with [maven-test-failure] core/target/surefire-reports/TEST-A.xml: …. Match arms: exact ✗, prefix ✗, settledByLifecycle ✗ (claimScopesItself), settledBySameScope ✗ (mavenPlModules(recorded) is null) → verdict unchecked — a provably false claim is never contradicted at claim level (and symmetrically a green reactor-wide run — a strict superset of the claim's scope — never confirms a truthful claim). The diff already accepts this exact evidence standard elsewhere: the -am carve-out yields precisely when failureInsideClaim attributes a failure to the claimed set. The underlying failure still shows in the build-test report (ok: false + markers), so this is a claim-adjudication miss, not a hidden failure — hence Suggestion.

Suggested fix: add a superset arm mirroring the -am carve-out's evidence bar: when the recorded run is reactor-wide (mavenPlModules(command) === null, equal lifecycle, no non--pl scopes) and finished(c) && ranFailed(c) && failureInsideClaim(c), admit it as a contradiction of the -pl claim; green superset settling can stay conservative.

中文说明

reactor 级的记录运行永远无法在任一方向结算带 -pl 作用域的声明——每个匹配分支都要求作用域相等——即使该运行自己的标记已证明失败落在声明的模块集合内。失败场景(已探针验证):PR 改动根 pom.xml/mvnw/任意 .mvn/** 文件(均为 reactorWide 触发器),build-test 记录不带 -pl./mvnw … test;Test Plan 声明 `./mvnw -pl core test`;reactor 级运行失败且带 [maven-test-failure] core/target/surefire-reports/TEST-A.xml: …。匹配分支:exact ✗、prefix ✗、settledByLifecycle ✗(claimScopesItself)、settledBySameScope ✗(mavenPlModules(recorded) 为 null)→ 裁决 unchecked —— 一个明显为假的声明在声明层面从未被 contradicted(对称地,绿色的 reactor 级运行——声明作用域的严格超集——也从不确认真实声明)。本 diff 在别处已接受完全相同的证据标准:-am 例外正是在 failureInsideClaim 把失败归因到声明集合时让步。底层失败仍会出现在 build-test 报告中(ok: false + 标记),所以这是声明裁决的遗漏,不是被隐藏的失败——因此定为 Suggestion。建议修复:增加镜像 -am 例外证据标准的超集分支(reactor 级且生命周期相同且无非 -pl 作用域时,finished(c) && ranFailed(c) && failureInsideClaim(c) 即判 contradicted);绿色超集结算可保持保守。

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

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.

Deferred (round cap). The missing superset arm (a reactor-wide run can never settle a -pl-scoped claim, even with attributed failures) is acknowledged and still valid; the underlying failure still surfaces via ok: false, so this is a claim-adjudication gap. This PR is in review round 9; per AGENTS.md, rounds past ~5 land only Critical fixes. The superset arm mirroring the -am carve-out's evidence bar will land in a follow-up; the deferral is recorded here so nothing is silently dropped.

中文说明

推迟(轮次上限)。 缺少的超集分支(reactor 级运行永远无法结算带 -pl 作用域的声明,即使失败已归因于声明模块)被认可且仍然成立;底层失败仍会经由 ok: false 呈现,因此这是声明裁决层面的遗漏。本 PR 已进入第 9 轮评审;按 AGENTS.md,超过约 5 轮后只落地 Critical 修复。镜像 -am 例外证据标准的超集分支将在后续落地;此回复记录推迟以免被静默丢弃。

@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 轮)。改动内容与我反驳保留之处如下:

Round 9 review feedback — summary

Decision framework: this PR is in review round 9. Per the repository's review policy (AGENTS.md), once a PR has been through roughly 5 review rounds, only Critical fixes (correctness, security, data loss, regressions) are landed, and remaining Suggestions are deferred to a follow-up with each deferral recorded in the PR thread. All 10 Critical findings are fixed in this commit; all 18 Suggestions are acknowledged as valid and deferred with per-thread replies.

No base-conflict resolution was requested (--conflict false); no merge was performed.

Critical findings — fixed (commit db9ffc7)

Each fix is pinned by a new regression test that was verified to FAIL against the pre-fix source (and only these 10 fail) before the fixes were restored.

  1. [maven-test-failure] marker mining not runner-gated (test-plan.ts) — freshTestFailures is now gated on MAVEN_RUNNER_RE.test(c.command), mirroring the sibling [maven-test-report] count mining. A green npm test whose stdout merely prints the marker no longer flips the claim to contradicted. New test: "does not read a maven failure marker out of a non-Maven run".
  2. Inheritance chains through a non-aggregated pom.xml dropped (maven-toolchain.ts) — a pom.xml parent in a directory the reactor does not aggregate is now read and parsed like a named parent file, and its own <parent> continues the chain for the same heir; an absent pom.xml target falls back to Maven's repository resolution (no edge), as before. New test: "walks an inheritance chain through an out-of-reactor pom.xml".
  3. Other-platform-wrapper skip swallowing a recorded parent file (maven-toolchain.ts) — the skip now yields when the wrapper-spelled file is also a recorded named parent (!namedParentDirs.has(path)), so it routes through the parent-config closure instead of vanishing into a green no-op. New test: "routes a wrapper-spelled named parent file instead of skipping it".
  4. isUnderTestSourceTree equality clause (maven-toolchain.ts) — the rel === src half is removed; the guard now fires only strictly beneath src/. A real project located exactly at a project's src path reaches the out-of-reactor fail-closed abort instead of being tested as .. New test: "fails closed for a real project located exactly at a src path".
  5. Deadline values never coerced before spawnSync (build-test.ts, mirrored in test-delta.ts) — the deadline is coerced once at the spawn boundary (Math.max(1, Math.round(timeoutMs))): fractional --timeout/--budget values no longer throw ERR_OUT_OF_RANGE out of the whole call, and --timeout 0 arms a real (1 ms, fail-closed) kill timer instead of none. deadlineMs reports the coerced value. New test: "coerces fractional and zero deadlines at the spawn boundary".
  6. mavenPlModules duplicating the first selector word (test-plan.ts) — the flag value is now read after advancing i, like the sibling token walkers, so space-bearing quoted -pl selectors parse intact and attributed [maven-test-failure] evidence is no longer silently discarded. New test: "contradicts a space-bearing -pl claim when the same-scope run failed inside it".
  7. MAVEN_VALUE_FLAGS missing -b/-t/-gt (test-plan.ts) — --builder, --toolchains, and --global-toolchains (short and long forms) now consume their value token, so a toolchains/builder file named test can no longer certify a verify claim. New test: "does not read -b/-t/-gt flag values as lifecycle phases".
  8. Self-parent guard killing named parents inside the heir dir (maven-toolchain.ts) — the guard now skips only a true pom.xml self-reference; a named parent file in the heir's own directory keeps its namedParentPoms registration, its chain continuation, and its fan-out (without recording a self inheritor edge). New test: "keeps the chain through a named parent file inside the heir dir".
  9. stripOpaqueSections quadratic (maven-toolchain.ts) — rewritten as a single forward walk (the shape of the POM-side stripCdataAndComments twin): each position is examined once, closers are searched only from a found marker. Semantics are unchanged (earlier marker wins; unterminated sections stay verbatim). New test: "strips dense comment sections in linear time" (960 KiB of comments: pre-fix measured 14.3 s in this suite, post-fix milliseconds, bound 5 s).
  10. Deleted named parent POM laundered into infrastructure (maven-toolchain.ts) — readMavenReactor now records every declared named-parent path (declaredParentFiles) before the existence check, and dependencyInputsChanged consults it, so a diff-deleted parent's Non-resolvable parent POM death is attributed to the PR instead of the infrastructure carve-out. New test: "treats a DELETED named parent file as a dependency input".

Suggestions — deferred to a follow-up (round cap)

All 18 Suggestion findings are acknowledged as valid and left open with a per-thread reply recording the deferral: the base-tree symlink family (3 findings, to be fixed together), named-parent read memoization, CDATA/comment section separator, per-element quoted -pl parsing, --projects in the scoped disclosure, the exitCode === null fixture, the usable-name manifest pin, the inert test-delta probe, the clamped clean-rollup line, the per-report failing-case floor, .mvn/maven.config comment stripping, Maven applies() mirroring its run-side gate (R6-22), the design-doc reconciliation, the rep.ok pin, report-filename control-character sanitization, and the reactor-wide superset settlement arm.

Review-level notes (no code action available here)

  • The CHANGES_REQUESTED review records coverage gaps (macOS/Windows CI legs and the Integration Tests leg skipped at the prior commit; reverse audit stopped by the time budget). These are statements about what was not reviewed, not code findings; the CI run for this commit re-exercises the changed code.
  • "Test Plan (not a blocker)": the PR description's Test Plan wording from earlier rounds (./mvnw, "326 tests to pass") is stale relative to the current suite. It is not editable from this mode; recording it here for the maintainer.

Verification

Commands actually run, in order:

  • npx vitest run (packages/cli) — maven-toolchain.test.ts, test-plan.test.ts, build-test.test.ts, npm-toolchain.test.ts, test-delta.test.ts, base-tree.test.ts410 passed (6 files), including the 10 new regression tests
  • Mutation check: the four source fixes temporarily reverted, same suites re-run — exactly the 10 new tests failed (333 passed), confirming each test pins its fix; fixes restored and suites re-run green
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check on the 7 changed files — passed

No settings source changed, so npm run generate:settings-schema was not required. The changed behavior is fully exercised by the unit suites above (not only through the bundled CLI), so no integration run was needed.

中文说明

第 9 轮评审反馈——总结

决策框架: 本 PR 已进入第 9 轮评审。按仓库评审政策(AGENTS.md),PR 经过约 5 轮评审后,只落地 Critical 修复(正确性、安全、数据丢失、回归),其余 Suggestion 推迟到后续跟进,并把每条推迟记录在 PR 线程中。本次提交修复了全部 10 条 Critical 发现;全部 18 条 Suggestion 被认可为成立,并按线程逐条回复记录推迟。

未要求解决 base 冲突(--conflict false);未执行任何合并。

Critical 发现——已修复(提交 db9ffc7

每条修复都由一个新增回归测试钉住;这些测试在恢复修复之前已被验证会对修复前的源码失败(且只有这 10 个失败)。

  1. [maven-test-failure] 标记挖掘未按 runner 门控test-plan.ts)——freshTestFailures 现在以 MAVEN_RUNNER_RE.test(c.command) 门控,与同级的 [maven-test-report] 计数挖掘一致。stdout 恰好打印该标记的绿色 npm test 不再把声明翻转为 contradicted。新测试:"does not read a maven failure marker out of a non-Maven run"。
  2. 经过非聚合目录 pom.xml 的继承链被丢弃maven-toolchain.ts)——reactor 未聚合目录中的 pom.xml 父级现在会像命名父文件一样被读取解析,其自身的 <parent> 也会为同一继承者延续链条;不存在的 pom.xml 目标与之前一样回退到 Maven 的仓库解析(无边)。新测试:"walks an inheritance chain through an out-of-reactor pom.xml"。
  3. "另一平台 wrapper"跳过吞掉已记录父文件maven-toolchain.ts)——当 wrapper 拼写的文件同时是已记录的命名父文件时(!namedParentDirs.has(path)),跳过逻辑让位,使其走父配置闭包路由,而不是消失为绿色无操作。新测试:"routes a wrapper-spelled named parent file instead of skipping it"。
  4. isUnderTestSourceTree 的相等分支maven-toolchain.ts)——移除 rel === src 半边;守卫现在只在严格位于 src/ 之下时触发。恰好位于某项目 src 路径上的真实项目会进入 reactor 外 fail-closed 中止,而不是被当作 . 来测试。新测试:"fails closed for a real project located exactly at a src path"。
  5. 截止期数值在 spawnSync 前未取整build-test.ts,并在 test-delta.ts 镜像)——截止期在 spawn 边界统一强制转换(Math.max(1, Math.round(timeoutMs))):小数 --timeout/--budget 不再以 ERR_OUT_OF_RANGE 杀死整个调用,--timeout 0 会设置一个真实的(1 毫秒、fail-closed)终止定时器而不是不设。deadlineMs 报告转换后的值。新测试:"coerces fractional and zero deadlines at the spawn boundary"。
  6. mavenPlModules 重复选择器首词test-plan.ts)——现在与兄弟 token 遍历器一样先推进 i 再读取标志值,带空格的引号 -pl 选择器得以完整解析,已归因的 [maven-test-failure] 证据不再被静默丢弃。新测试:"contradicts a space-bearing -pl claim when the same-scope run failed inside it"。
  7. MAVEN_VALUE_FLAGS 缺少 -b/-t/-gttest-plan.ts)——--builder--toolchains--global-toolchains(短长格式)现在会消费其取值 token,名为 test 的 toolchains/builder 文件不再能为 verify 声明背书。新测试:"does not read -b/-t/-gt flag values as lifecycle phases"。
  8. 自父守卫杀死继承者目录内的命名父文件maven-toolchain.ts)——守卫现在只跳过真正的 pom.xml 自引用;继承者自身目录内的命名父文件保留 namedParentPoms 注册、链式延续与扇出(但不记录自继承边)。新测试:"keeps the chain through a named parent file inside the heir dir"。
  9. stripOpaqueSections 平方复杂度maven-toolchain.ts)——改写为单趟前向扫描(与 POM 侧 stripCdataAndComments 孪生同形):每个位置只检查一次,仅在发现标记处才搜索终止符。语义不变(较早标记优先;未终止区段原样保留)。新测试:"strips dense comment sections in linear time"(960 KiB 注释:修复前在本套件实测 14.3 秒,修复后为毫秒级,上限 5 秒)。
  10. 被删除的命名父 POM 被洗白为基础设施maven-toolchain.ts)——readMavenReactor 现在在存在性检查之前记录每个声明的命名父路径(declaredParentFiles),dependencyInputsChanged 会查询它,于是 diff 删除父文件导致的 Non-resolvable parent POM 死亡归因于 PR,而不是基础设施例外。新测试:"treats a DELETED named parent file as a dependency input"。

Suggestion——推迟到后续跟进(轮次上限)

全部 18 条 Suggestion 发现均被认可为成立,线程保持打开并逐条回复记录推迟:base-tree 符号链接家族(3 条,将一并修复)、命名父读取记忆化、CDATA/注释区段分隔符、逐元素带引号的 -pl 解析、作用域披露中的 --projectsexitCode === null fixture、可用 name 的 manifest 钉住、无效的 test-delta 探针、钳制后的干净汇总行、按报告保底的失败用例上限、.mvn/maven.config 注释剥离、Maven applies() 镜像运行侧门控(R6-22)、设计文档对齐、rep.ok 钉住、报告文件名控制字符净化、reactor 级超集结算分支。

评审级注记(此处无可用代码动作)

  • CHANGES_REQUESTED 评审记录的是覆盖缺口(上一提交处 macOS/Windows CI 腿与 Integration Tests 腿被跳过;反向审计因时间预算停止)。这些是关于"什么未被审查"的陈述,不是代码发现;本次提交的 CI 会重新执行被修改的代码。
  • "Test Plan(非阻断)":PR 描述中来自更早轮次的 Test Plan 表述(./mvnw、"326 tests to pass")相对当前套件已过时。本模式无法编辑 PR 描述;在此记录以供维护者查看。

验证

实际依次运行的命令:

  • npx vitest run(packages/cli)——maven-toolchain.test.tstest-plan.test.tsbuild-test.test.tsnpm-toolchain.test.tstest-delta.test.tsbase-tree.test.ts——410 通过(6 个文件),包含 10 个新回归测试
  • 变异检查:临时回退四处源码修复后重跑相同套件——恰好 10 个新测试失败(333 通过),确认每个测试都钉住了对应修复;恢复修复后重跑全绿
  • npm run build——通过
  • npm run typecheck——通过
  • npm run lint——通过
  • 对 7 个变更文件运行 npx prettier --check——通过

未改动任何 settings 源,因此无需 npm run generate:settings-schema。变更行为已由上述单元测试完全覆盖(并非只经由打包后的 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

@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 reviewed: build-and-test — Test (macos-latest, Node 22.x), Test (windows-latest, Node 22.x) and Integration Tests (CLI, No Sandbox) were skipped in CI; the platform-dependent branches (win32 wrapper arms in maven-toolchain.ts, platform guards in the new test files) were exercised only by platform-injectable unit tests on Linux.

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

Test Plan (not a blocker): ./mvnwno such file or directory.

中文说明

未审查:build-and-test — Test (macos-latest, Node 22.x), Test (windows-latest, Node 22.x) and Integration Tests (CLI, No Sandbox) were skipped in CI; the platform-dependent branches (win32 wrapper arms in maven-toolchain.ts, platform guards in the new test files) were exercised only by platform-injectable unit tests on Linux。

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

Test Plan(非阻断):./mvnwno such file or directory

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

Comment on lines +472 to +474
// Per-(heir, file) cycle guard: a hostile chain can name files in a ring,
// and each (heir, parent file) pair is resolved at most once.
const enqueued = new Set<string>();

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] R9-14: The inheritance worklist re-reads and re-parses every non-reactor named parent file once per heir — enqueued is keyed per (heir, file) pair, so it bounds rings but not volume, and only reactor projects' parsed structures are cached. Total work is O(#heirs × #parent files) on PR-controlled bytes, inside readMavenReactor before any budget accounting exists. Filed as a Suggestion in round 9; the DoS escalation is probe-verified this round. — Failure scenario: a PR commits N modules whose <parent> declarations chain through M named parent files the same PR adds (artifactIds match; a ring works). Measured through the real readMavenReactor: 20 heirs × 20 files = 1431 ms, 40 × 20 = 2831 ms (linear in heirs), 11–25 ms per 2 MiB parse; N=M=300 extrapolates to ~90,000 parses (16–37 minutes), past the outer ~600 s tool timeout — qwen review build-test dies with no report. Same PR-controlled-bytes DoS class this file elsewhere caps and regression-tests (MAX_POM_BYTES, MAX_REACTOR_DEPTH, the linear-parse tests). Suggested fix: memoize the parsed structure per absolute parent-file path for the duration of the call (probe-verified: 2831 ms → 81 ms, heir-independent), or cap total worklist pops and fail closed past the cap.

中文说明

继承 worklist 会为每个 heir 重新读取并重新解析每个非 reactor 的命名 parent 文件——enqueued 以 (heir, file) 对为键,只能限制环形引用,无法限制总量,且只有 reactor 项目的解析结构被缓存。总工作量为 O(#heirs × #parent files),作用于 PR 可控的字节,发生在 readMavenReactor 内部、任何预算计时之前。第 9 轮曾以 Suggestion 提交;本轮通过探针实测确认其 DoS 升级:实测真实 readMavenReactor,20 heirs × 20 文件 = 1431 ms,40 × 20 = 2831 ms(随 heir 数线性增长),每次解析 2 MiB 需 11–25 ms;N=M=300 外推约 90,000 次解析(16–37 分钟),超过外层约 600 秒的工具超时——qwen review build-test 会无报告而终。与本文件其他地方已有上限并有回归测试的 PR 可控字节 DoS 同类(MAX_POM_BYTESMAX_REACTOR_DEPTH、线性解析测试)。建议修复:在调用期间按 parent 文件绝对路径记忆化解析结果(实测 2831 ms → 81 ms,与 heir 数无关),或对 worklist 弹出总数加上限并在超限时 fail closed。

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

Comment on lines +1101 to +1103
): MavenTestSummary[] {
const summaries: MavenTestSummary[] = [];
for (const path of reportPaths(root, reactor)) {

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] R10-3: MAX_REPORT_BYTES caps per-file bytes, but nothing caps how many fresh .xml reports this loop reads and parses after exec returns, nor how many failedCases strings parseTestReport accumulates before the line caps in appendTestSummaries apply. This parsing runs synchronously, outside any deadline, on files the PR's own tests can write during the run (the mtime freshness filter accepts any writer — stated in this file's own MAX_REPORT_BYTES comment). — Failure scenario: probe at sub-scale: 250 fresh failing reports were all read and parsed (the omission marker's summed totals prove every file was parsed); 3 reports × 300 failing cases = 900 case strings materialized before caps. At the scale a hostile PR can write (~1,000 × 2 MiB reports, tens of thousands of <testcase><failure/></testcase> entries each): tens of millions of live strings across simultaneously-held summaries plus the flatMap doubling — multi-GB heap / OOM and minutes of CPU past the outer tool timeout, discarding the entire report. Suggested fix: cap the number of fresh reports parsed (disclose the omission in the evidence block like the existing line caps) and cap failedCases per report while building it in parseTestReport, not after the fact in appendTestSummaries.

中文说明

MAX_REPORT_BYTES 限制了单文件字节数,但这个循环在 exec 返回后读取和解析多少个新的 .xml 报告没有任何上限,parseTestReportappendTestSummaries 的行数上限生效之前累积多少 failedCases 字符串也没有上限。这段解析在任意截止时间之外同步执行,且作用于 PR 自己的测试在运行期间就能写入的文件(mtime 新鲜度过滤器接受任何写入者——本文件 MAX_REPORT_BYTES 的注释自己也这么说)。探针实测(次规模):250 个新的失败报告全部被读取并解析(省略标记的合计总数证明每个文件都被解析过);3 个报告 × 300 个失败用例 = 900 条用例字符串在上限生效前就已实体化。按恶意 PR 可写入的规模(约 1,000 个 × 2 MiB 报告、每个含数万条 <testcase><failure/></testcase>):同时持有的 summaries 上千万条活跃字符串,加上 flatMap 翻倍——数 GB 堆 / OOM,CPU 耗时超出外层工具超时,整个报告被丢弃。建议修复:对解析的新报告数量加上限(像现有行数上限一样在证据块中披露省略),并在 parseTestReport 构建期间就为每个报告的 failedCases 加上限,而不是事后在 appendTestSummaries 里截断。

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

Comment on lines +394 to +395
if (visited.has(pomPath)) return null;
visited.add(pomPath);

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] RA1-4: The module walk deduplicates by lexical path only — no realpathSync, no total-visit cap — so symlink-aliased directories produce exponentially many distinct lexical paths while depth stays small; MAX_REACTOR_DEPTH is a depth cap and never engages. — Failure scenario (probe-measured through the real readMavenReactor): dirs d0..dN where each d_k/pom.xml lists <module>s1</module><module>s2</module> and s1/s2 are symlinks to d_{k+1}: modules returned are exactly 2^(depth+1)−1 (depth 6→127, 10→2,047, 14→32,767, 16→131,071), wall time doubling per level. The d38 shape extrapolates to ~2^39 statSync+readFileSync+parsePomStructure visits plus map entries — hang/OOM, no report. Git materializes symlinks on Linux/macOS checkouts, and plain s1/s2 names pass the parsePomStructure gates. This sits inside the file's own DoS-hardening threat model (MAX_POM_BYTES, MAX_REACTOR_DEPTH, the linear-parse regression tests). Suggested fix: key visited on realpathSync(pomPath) (fail closed on resolution failure) and/or fail closed when visited.size exceeds a generous bound (e.g. 10,000 reactor projects).

中文说明

模块遍历只按词法路径去重——没有 realpathSync,也没有总访问次数上限——因此符号链接别名的目录会在深度不变的情况下产生指数级数量的不同词法路径;MAX_REACTOR_DEPTH 是深度上限,永远不会触发。探针实测真实 readMavenReactor:目录 d0..dN,每个 d_k/pom.xml 列出 <module>s1</module><module>s2</module>,且 s1/s2 是指向 d_{k+1} 的符号链接:返回的模块数恰好是 2^(depth+1)−1(深度 6→127,10→2,047,14→32,767,16→131,071),耗时每层翻倍。d38 形状外推约 2^39 次 statSync+readFileSync+parsePomStructure 访问加映射条目——挂起/OOM,无报告。Git 在 Linux/macOS 检出时会实体化符号链接,普通的 s1/s2 名称能通过 parsePomStructure 的门禁。这正在本文件自身的 DoS 加固威胁模型之内(MAX_POM_BYTESMAX_REACTOR_DEPTH、线性解析回归测试)。建议修复:以 realpathSync(pomPath) 作为 visited 的键(解析失败时 fail closed),和/或在 visited.size 超过宽松上限(如 10,000 个 reactor 项目)时 fail closed。

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

Comment on lines +287 to +289
} else if (capture.field === 'artifactId') {
artifactId = text;
} else if (capture.field === 'parentArtifactId') {

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] RA2-3: parsePomStructure captures raw, XML-undecoded text for artifactId/parentArtifactId/relativePath, and the inheritance-edge match (~line 553) compares those literals — but Maven matches parent resolution on decoded values, so two spellings that decode to the same id silently delete a real inheritance edge. — Failure scenario (probe-confirmed against the real readMavenReactor): parent declares <artifactId>my-app</artifactId>, heir declares <parent><artifactId>my&#45;app</artifactId><relativePath>../parent/pom.xml</relativePath></parent> — both decode to the valid artifactId my-app — yet observed inheritors: {}; detectMavenOwnership(root, ['parent/pom.xml']) scopes -pl parent -am test only, so the heir is never compiled or tested and the review can report green over the broken heir. Control arm with the decoded spelling: inheritors: {"parent":["heir"]}. The entity-spelled <relativePath> variant drops the named-parent-file edge the same way. - is a legal artifactId character and Maven's XML layer decodes entities before model validation, so this is legitimate Maven input. Suggested fix: decode captured text before storing it — note the existing decodeXml helper decodes only the five named entities, not numeric references (verified), so extend it or fail the POM closed on any &…; sequence in these captured fields, consistent with the fail-closed stance the <module> gate already takes.

中文说明

parsePomStructureartifactId/parentArtifactId/relativePath 捕获的是未经 XML 解码的原始文本,而约第 553 行的继承边匹配比较的是这些字面量——但 Maven 的 parent 解析按解码后的值匹配,因此解码后相同的两种写法会悄悄删除一条真实的继承边。探针对真实 readMavenReactor 确认:parent 声明 <artifactId>my-app</artifactId>,heir 声明 <parent><artifactId>my&#45;app</artifactId><relativePath>../parent/pom.xml</relativePath></parent>——两者都解码为合法的 artifactId my-app——却观测到 inheritors: {}detectMavenOwnership(root, ['parent/pom.xml']) 只圈定 -pl parent -am test,heir 从不被编译或测试,评审可能对损坏的 heir 报告绿色。解码写法的对照组:inheritors: {"parent":["heir"]}。实体写法的 <relativePath> 变体以同样方式丢失命名 parent 文件边。- 是合法 artifactId 字符,且 Maven 的 XML 层在模型校验前就解码实体,所以这是合法的 Maven 输入。建议修复:存储前先解码捕获的文本——注意现有 decodeXml 助手只解码五种命名实体、不解码数字引用(已验证),需扩展它,或在这些捕获字段出现任何 &…; 序列时对该 POM fail closed,与 <module> 门禁已有的 fail-closed 立场一致。

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

Comment on lines +495 to +498
if (basename(parentPom) !== 'pom.xml') {
if (isInside(reactorRoot, parentPom)) {
declaredParentFiles.add(toPosix(relative(reactorRoot, parentPom)));
}

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] RA3-1: A declared parent spelled pom.xml in a directory that is NOT a reactor member never becomes a dependency input — this branch skips it — so deleting it launders the diff-caused Non-resolvable parent POM death into the infrastructure carve-out. The directory spelling records the directory path, which git never reports as a changed file, while the real input is <dir>/pom.xml — same hole. — Failure scenario (probe-verified, both spellings): app/pom.xml declares <relativePath>../shared/parent/pom.xml</relativePath>; shared/parent is not a reactor member and the diff deletes the file. No declaredParentFiles entry is recorded; ownership routes reactor-wide; the run dies at model build with [FATAL] Non-resolvable parent POM (matches DEPENDENCY_FAILURE_LINE_RE) — observed infrastructure: true, "infrastructure evidence, not a source finding": the diff's own breakage laundered. Modify-contrast probed fail-closed (caught), so only the deletion shape escapes. This diff ships 'treats a DELETED named parent file as a dependency input' proving the intent covers deleted declared parents; only the pom.xml-spelled non-member parent was missed. Suggested fix: record the FINAL resolved parent path (after the directory→pom.xml append) in declaredParentFiles for any parent whose parentPath is absent from structures — probe-verified flip with all 137 tests passing (the toEqual(['parent.xml']) pin at maven-toolchain.test.ts:3068 moves with it).

中文说明

位于非 reactor 成员目录中、拼写为 pom.xml 的声明 parent 永远不会成为依赖输入——这个分支跳过了它——因此删除它会把 diff 自身导致的 Non-resolvable parent POM 死亡洗白成基础设施例外。目录拼写记录的是目录路径,而 git 从不把目录路径报告为变更文件,真正的输入是 <dir>/pom.xml——同一个漏洞。探针验证(两种拼写):app/pom.xml 声明 <relativePath>../shared/parent/pom.xml</relativePath>shared/parent 不是 reactor 成员且 diff 删除了该文件。没有记录任何 declaredParentFiles 条目;ownership 路由为全 reactor;运行在模型构建时以 [FATAL] Non-resolvable parent POM 死亡(匹配 DEPENDENCY_FAILURE_LINE_RE)——观测到 infrastructure: true,"infrastructure evidence, not a source finding":diff 自身的破坏被洗白。修改(非删除)的对照探针走了 fail-closed(被捕获),因此只有删除形状逃脱。本 diff 自带测试 'treats a DELETED named parent file as a dependency input',证明意图覆盖已删除的声明 parent;唯独漏掉了 pom.xml 拼写的非成员 parent。建议修复:对任何 parentPath 不在 structures 中的 parent,在目录→pom.xml 追加之后把最终解析出的 parent 路径记录进 declaredParentFiles——探针验证修复翻转成立且全部 137 个测试通过(maven-toolchain.test.ts:3068 处 toEqual(['parent.xml']) 的断言需随之调整)。

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

Comment on lines +189 to +190
- `packages/cli/src/commands/review/lib/agent-briefs.ts`
- Agent 7's Maven branch and the fail-closed fallback rules.

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] RA3-9: The Files section and the Verification-commands list both omit test-delta.ts / test-delta.test.ts even though this PR changes both — test-delta.ts is the ONLY changed production file absent from Files, and test-delta.test.ts the ONLY changed suite absent from the verification command. — Concrete cost: verified at the commit (git diff --name-only lists both); the doc demonstrably knows the file (names test-delta as a report consumer and discusses its Maven exclusion); AGENTS.md commits docs/design/ docs as the tracked scope record — anyone using it for backports/reverts/audits misses a changed production file, and anyone running the doc's own verification command never exercises the new Maven-rejection pin. Suggested fix: add test-delta.ts to the Files section and src/commands/review/test-delta.test.ts to the verification command.

中文说明

Files 一节与 Verification-commands 列表都遗漏了 test-delta.ts / test-delta.test.ts,尽管本 PR 同时修改了两者——test-delta.ts 是唯一未出现在 Files 中的被改生产文件,test-delta.test.ts 是唯一未出现在验证命令中的被改套件。具体代价:已在该提交核实(git diff --name-only 列出两者);文档显然知道该文件(把 test-delta 列为报告消费者并讨论其 Maven 豁免);AGENTS.md 把 docs/design/ 文档作为被追踪的范围记录——用它做 backport/回滚/审计的人会漏掉一个被改的生产文件,运行文档自己的验证命令的人也永远不会执行新的 Maven 拒绝钉桩。建议修复:在 Files 一节加入 test-delta.ts,并在验证命令中加入 src/commands/review/test-delta.test.ts

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

Comment on lines +225 to +226
5. Timeout, spawn death, and acquisition failures are infrastructure with the
diff-inputs exceptions, never a finding.

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] RA4-2: Testing-oracle item 5 attaches the diff-inputs exceptions to timeouts and spawn deaths ("Timeout, spawn death, and acquisition failures are infrastructure with the diff-inputs exceptions, never a finding"), directly contradicting the same doc's Report-semantics section ("Timeout and spawn death are always infrastructure — no input exception exists for them") and the shipped code. — Concrete cost: all three texts verified at the commit; acquisitionFailure requires exitCode !== null (spawn death and timeout structurally excluded) and the suppression flags gate only launch/dependency failures. An author writing a test from item 5 writes a failing test against correct behavior, and "fixing" the code to satisfy item 5 would re-introduce the laundering the exceptions exist to bound. Suggested fix: reword item 5 to scope the exception to acquisition failures only.

中文说明

测试 oracle 第 5 条把 diff 输入例外挂到了超时与 spawn 死亡上("Timeout, spawn death, and acquisition failures are infrastructure with the diff-inputs exceptions, never a finding"),与同一文档 Report-semantics 一节("Timeout and spawn death are always infrastructure — no input exception exists for them")以及实际代码直接矛盾。具体代价:三处文本均已在该提交核实;acquisitionFailure 要求 exitCode !== null(spawn 死亡与超时在结构上被排除),且抑制 flag 只门禁 launch/dependency 失败。按第 5 条写测试的作者会写出对正确行为失败的测试,而为满足第 5 条"修复"代码会重新引入这些例外本要限制的洗白。建议修复:改写第 5 条,把例外限定为仅针对获取失败。

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

Comment on lines +218 to +220
it.each([
['${module.name}', 'property expressions'],
['../outside', 'paths escaping the reactor'],

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] RA4-3: The fail-closed it.each cases for 'a%b', 'a,b', 'a:b' (shell-active characters) never create the declared module's child pom.xml, so their assertions pass via the missing-child-POM gate instead of the character gate they are named for. — Failure scenario (mutation verified): with the /[<$>{}&%,:]/ class removed from parsePomStructure, all 11 'fails closed' tests still pass — the named cases are masked by the existsSync gate ('Maven module … has no child pom.xml'). With real child poms present under the mutation, a%b/a,b/a:b/a%PATH%x pass every gate and reach the reactor model — a hostile a%PATH%x dir WITH a real child pom reaches the -pl selector the moment the gate drops (cmd.exe %VAR% expansion on Windows reviewers). This file's tokenizer has already been rewritten several times in this PR. Suggested fix: create the child project for every case except 'missing' (keep the '../outside' special case) so each guard is the sole error source.

中文说明

fail-closed 的 it.each 用例 'a%b''a,b''a:b'(shell 活性字符)从不创建所声明模块的子 pom.xml,因此断言实际经由"缺少子 POM"门禁通过,而不是它们名义上要测的字符门禁。失败场景(已做变异验证):从 parsePomStructure 中移除 /[<$>{}&%,:]/ 类后,全部 11 个 'fails closed' 测试仍然通过——这些用例被 existsSync 门禁掩盖('Maven module … has no child pom.xml')。在变异且存在真实子 pom 时,a%b/a,b/a:b/a%PATH%x 通过所有门禁进入 reactor 模型——一旦字符门禁被删,带真实子 pom 的恶意 a%PATH%x 目录就会到达 -pl 选择器(Windows 评审机上 cmd.exe 的 %VAR% 展开)。本文件的 tokenizer 在本 PR 中已被重写过数次。建议修复:除 'missing' 外为每个用例创建子项目(保留 '../outside' 特例),使每个守卫都是唯一的错误来源。

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

Comment on lines +572 to +574
if (parentPath !== item.heir) {
const inherited = inheritors.get(parentPath);
if (inherited) {

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] RA4-4: Named parent-file inheritance edges are registered under the parent file's DIRECTORY key in inheritors, conflating them with the inheritors of that directory's own pom.xml; addDescendantClosure then over-scopes in both directions when a named parent file lives inside an active module directory. — Failure scenario (probe through the real functions, both directions): named parent core/parent.xml backing app's <parent>, core aggregating parentless core/sub: change core/pom.xml → modules ['app','core','core/sub'] (app inherits core/parent.xml, not core/pom.xml — over-scoped); change core/parent.xml → same set (core/sub has no parent, pulled via children['core'] — over-scoped). Direction conservative (over-verification): selector inflation toward MAX_SELECTOR_CHARS (4096), past which the run widens to the full reactor. Suggested fix: record named-file parentage as file-keyed edges separate from directory-keyed inheritors.

中文说明

命名 parent 文件的继承边被登记在 inheritors 中该 parent 文件所在目录的键下,与该目录自身 pom.xml 的继承者混同;当命名 parent 文件位于活跃模块目录内时,addDescendantClosure 会在两个方向上过度圈定。失败场景(经真实函数探针,双向):命名 parent core/parent.xml 支撑 app<parent>core 聚合无 parent 的 core/sub:修改 core/pom.xml → modules ['app','core','core/sub']app 继承的是 core/parent.xml 而非 core/pom.xml——过度圈定);修改 core/parent.xml → 同样的集合(core/sub 根本没有 parent,却经 children['core'] 被拉入——过度圈定)。方向保守(过度验证):选择器向 MAX_SELECTOR_CHARS(4096)膨胀,超过后运行放宽为全 reactor。建议修复:把命名文件的 parent 关系记录为按文件为键的边,与按目录为键的 inheritors 分离。

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

Comment on lines +1648 to +1650
const free = freeDiskBytes(args.root);
if (free !== null && free < INSTALL_MIN_FREE_BYTES) {
return mavenReport({

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] RA4-7: The disk preflight runs once before the warm-up and is never re-checked before the lifecycle command, even though the warm-up (dependency:go-offline) is the phase that fills the disk; the "mirroring the npm adapter" comment mirrors only the first of npm's TWO preflights. — Failure scenario: verified at the commit — exactly one freeDiskBytes call site here vs the npm twin's second BUILD_MIN_FREE_BYTES check before its build phase. Chain: ~3.2 GiB free passes the 3 GiB floor; a cold large reactor's warm-up consumes the headroom (the incident disk.ts's rationale cites); the lifecycle dies ENOSPC mid-write leaving a full disk that fails every later agent; an unframed ENOSPC death is not classified infrastructure. Suggested fix: re-check freeDiskBytes(args.root) against BUILD_MIN_FREE_BYTES after the warm-up with the same skip-and-disclose report shape.

中文说明

磁盘预检只在 warm-up 之前运行一次,在 lifecycle 命令之前从不复查——而 warm-up(dependency:go-offline)恰恰是填满磁盘的阶段;"mirroring the npm adapter" 注释只镜像了 npm 两次预检中的第一次。失败场景:已在该提交核实——这里只有一处 freeDiskBytes 调用,而 npm 孪生在构建阶段前还有第二处 BUILD_MIN_FREE_BYTES 检查。链条:约 3.2 GiB 空闲通过 3 GiB 下限;冷的大 reactor 的 warm-up 吃光余量(disk.ts 理由引用的事故);lifecycle 在写入中途 ENOSPC 死亡,留下满盘使后续每个 agent 失败;无 [ERROR] 框架的 ENOSPC 死亡不会被分类为基础设施。建议修复:warm-up 之后按 BUILD_MIN_FREE_BYTES 复查 freeDiskBytes(args.root),采用同样的"跳过并披露"报告形状。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Round 10 Review Feedback — Address Summary

All 27 inline findings (5 Critical, 22 Suggestion) are resolved in the code in
commit 2be30c54a2. No finding was declined, deferred, or escalated.

Critical findings

ID Finding Resolution
R9-14 Inheritance worklist re-read and re-parsed every named parent file once per heir (O(#heirs × #parent files) before any budget exists) Non-reactor parent files are now memoized per absolute path in readMavenReactor, so each file is read and parsed once per reactor read. Pinned by a new 25-heir × 30-file padded timing test.
R10-3 No cap on how many fresh .xml reports are parsed, nor on failedCases accumulated per report MAX_FRESH_REPORTS = 1000 caps parsed fresh reports (sorted before the cap so the parsed subset stays deterministic), disclosed in the evidence block as N more fresh report(s) not parsed. MAX_FAILURE_CASES_PER_REPORT = 200 caps cases while building in parseTestReport; the dropped count joins the existing more failing case(s) omitted marker. Both shapes pinned by new tests.
R10-1 (RA1-4) Module-traversal dedup keyed on lexical paths only — symlink-aliased dirs blow up exponentially at constant depth, with no total-visit cap The walk now deduplicates on realpathSync (fail closed on resolution errors) and fails closed past MAX_REACTOR_PROJECTS = 10000 projects. Pinned by a new symlink-chain test (skipped on win32).
R10-2 (RA2-3) parsePomStructure captured raw XML-un-decoded text for artifactId/parent artifactId/relativePath; entity-spelled ids silently dropped inheritance edges Any & in those captured fields now fails the POM closed — consistent with the existing <module> character gate. Pinned by a new &#45; test.
R10-4 (RA3-1) Declared parents named pom.xml in a non-member directory were never recorded as dependency inputs, laundering their deletion's Non-resolvable parent POM death into the infrastructure carve-out For every parent whose directory is not a reactor member, the FINAL resolved path (after Maven's directory→pom.xml append) is recorded in declaredParentFiles. Named files keep recording their declared path itself (also when deleted). Pinned by a new deleted-parent suppression test.

Suggestions

ID Finding Resolution
R10-5 A Maven claim whose final positional token is outside the lifecycle vocabulary (mvn test deploy, mvn test site, plugin goals) settled on the recognized phase undisclosed settledByLifecycle/settledBySameScope now also require the claim's final non-flag positional token to equal the claimed lifecycle. Phase reduction (clean test) still settles and discloses. Pinned by new tests.
R10-6 Comment wrongly claimed -Dmaven.repo.local.tail= starts with -Dmaven.repo.local= Comment corrected: the two prefixes are disjoint (local.tail= vs local=), ordering does not matter, and both checks stay.
R10-7 failureInsideClaim's root-module branch untested Added the -pl . twin of the claimed-set contradiction test with a target/-rooted marker.
R10-8 --timeout/--budget coercion was a no-op for NaN/Infinity runBuildTest now rejects non-finite --timeout/--budget with a descriptive error at the boundary every call crosses (the yargs handlers already catch and surface it). Pinned by a new test.
R10-9 test-delta's spawn-boundary coercion was inert — every test injected the exec seam Added a test that drives the REAL spawn path with --timeout 60.123 (60122.99999999999 ms), which throws ERR_OUT_OF_RANGE without the coercion.
R10-10 (RA1-1) Fractional deadline test used 0.1, which rounds to an integer Test now uses timeout: 1.005 (IEEE 1004.9999999999999) with an explicit --budget 600 so the fractional deadline is the binding term, and asserts deadlineMs === 1005.
R10-11 (RA1-6) The ENOSPC shape isLaunchFailure needs could be lost to trimOutput's omitted middle The disk-full line shape is exported as isDiskFailureLine and joined the trim's rescue predicate; marker text updated. Pinned by a new trim test.
R10-12 (RA1-7) .mvn/maven.config was read with uncapped readFileSync The read now statSyncs first and fails closed past the MAX_POM_BYTES class cap (the .mvn/ prefix still marks the config itself as a dependency input). Pinned by an over/under-cap twin test.
R10-13 (RA1-10) Install-failure framing was prepended only in the final return; the build disk preflight and build-failure early returns skipped it Extracted a frameInstallFailure helper applied on every post-install return in the npm adapter.
R10-14 (RA1-11) Coordinate-form -pl entries (:core, groupId:core) were kept verbatim and could never settle or be contradicted mavenPlModules normalizes coordinate entries to their last : segment; recorded directory selectors can never carry : (the POM gate rejects it), so they are untouched. Pinned by a new settlement test.
R10-15 (RA1-12) The interrupted-run contradiction and the unchecked cascade notes hardcoded "this review ran it" / "this Maven command was run by this review", overstating module-scoped reduced forms Extracted the scoped/phaseReduced/howItRan computation into a shared runForm helper used by the finished-run ruling, the interrupted contradiction, and all three unchecked-cascade notes. Pinned by new interrupted/timeout/spawn-death disclosure tests.
R10-16 (RA2-9) Maven gate pom.xml probes used git cat-file -e, which exits 0 for a directory named pom.xml gitHasPath now uses cat-file -t and requires a blob, covering the root probe, the nested probe, and the package.json probe. Pinned by root and nested directory-named-pom.xml regression tests.
R10-17 (RA2-11) An unbalanced quote inside a tag made tokenizePom swallow the rest of the document into a phantom token, returning a successful-but-shrunken structure EOF with an OPEN QUOTE now fails the POM closed; the quote-less unterminated tag keeps its historical treatment (its control shape already failed closed downstream). Pinned by a new test.
R10-18 (RA2-12) Maven's whole-call budget charged only time inside args.exec; pre-command parsing/scoping ran uncounted The budget is now wall clock from the top of the call, like the npm adapter and the toolchain.ts contract. To keep the documented "a run whose whole budget is one short deadline still gets that attempt" guarantee, the FIRST attempt keys on the granted budget (later attempts key on the remainder), and deadlines clamp at zero. The two deadline-asserting tests freeze Date.now.
R10-19 (RA2-13) scopesNonPl omitted -s/--settings, -gs/--global-settings, -t/--toolchains, -gt/--global-toolchains, -b/--builder All five flags added with their = forms. Pinned in the existing non--pl scope loop.
R10-20 (RA3-4) -amd=/--also-make-dependents= attached forms bypassed the exact-token match Both = forms added to scopesNonPl. Pinned in the same loop.
R10-21 (RA3-5) A changed named parent file whose directory hosts no project escalated to a reactor-wide run via the unowned catch-all The catch-all now skips files routed through a named parent — their scope is exactly the inheritor closure computed above. The owning-module, out-of-reactor-abort, and root-level branches are unchanged. Pinned by a new probe-shaped test.
R10-22 (RA3-9) Design doc Files section omitted test-delta.ts Added, with its role, and test-delta.test.ts joined the verification command.
R10-23 (RA4-2) Test oracle item 5 wrongly tied the diff-inputs exceptions to timeouts and spawn deaths Reworded: timeout and spawn death are always infrastructure (no input exception); acquisition failures carry the diff-inputs exceptions.
R10-24 (RA4-3) The fail-closed it.each cases never created child POMs, so they passed via the missing-child gate even with their character gate removed Every case except missing now creates a real child project (the : case keeps the fallback on win32, where : is not a legal directory character), so each named gate is the sole error source.
R10-25 (RA4-4) Named-parent-file inheritance edges registered under the directory key over-scoped closures in both directions Heirs of a named parent file are now keyed on the file path itself; the ownership closure for a changed named parent walks from the file key. Existing pins updated; a new test proves both over-scope directions (a core/pom.xml change no longer pulls app, and a core/parent.xml change no longer pulls core/sub/ext).
R10-26 (RA4-7) The Maven disk preflight ran once before the warm-up and was never re-checked The floor is re-checked after the warm-up and before the lifecycle command (mirroring the npm adapter's second preflight), with the same skip-and-disclose report shape. Pinned by a new mocked-statfsSync test.

Review-level comment (not an inline finding)

The review summary's CHANGES_REQUESTED records that the macos-latest,
windows-latest, and no-sandbox integration shards were skipped in CI, so the
platform-dependent branches were exercised only by platform-injectable unit
tests on Linux, plus that the reverse audit stopped early on its own time
budget. There is no code change a commit can make for skipped CI shards — the
platform branches (win32 wrapper arms, platform guards) remain covered by the
platform-injectable unit tests (platform: 'win32' arguments to
detectMavenOwnership, it.skipIf(process.platform === 'win32') twins), and
the "Test Plan: ./mvnw — no such file or directory" note was explicitly
marked not a blocker.

Verification

Commands actually run (all after the final code state):

  • npm run build — passed (exit 0, no errors)
  • npm run typecheck — passed (0 TypeScript errors; an initial webui failure was missing build artifacts, resolved by npm run build)
  • npm run lint — passed (0 errors, 0 warnings)
  • npx prettier --check on the 11 changed files — all use Prettier code style
  • npx vitest run on the review suite (build-test, npm-toolchain, maven-toolchain, test-plan, base-tree, agent-prompt, test-delta — the design doc's verification set) — 7 files, 609 passed
  • npx vitest run src/commands/review/ — 63 files, 2399 passed, 4 skipped
  • npx vitest run (entire packages/cli) — 18317 passed, 7 skipped; the 28 failures (config/settings/UI suites) reproduce identically with this round's changes stashed, i.e. pre-existing on the branch and unrelated to review code
  • No settings source changed, so npm run generate:settings-schema was not required; no bundled-CLI behavior changed, so no integration run was required
中文说明

第 10 轮评审反馈 — 处理总结

全部 27 条行内发现(5 条 Critical、22 条 Suggestion)均已在提交
2be30c54a2 的代码中解决。没有拒绝、推迟或升级任何发现。

Critical 发现

编号 发现 处理
R9-14 继承工作队列对每个继承者重复读取并重新解析每个具名 parent 文件(预算机制生效前即 O(继承者数 × parent 文件数) readMavenReactor 现在按绝对路径对非 reactor parent 文件做记忆化,每次 reactor 读取中每个文件只读取和解析一次。由新增的 25 继承者 × 30 文件带填充的计时测试固化。
R10-3 对解析多少份新 .xml 报告没有上限,每份报告累积的 failedCases 也没有上限 MAX_FRESH_REPORTS = 1000 限制解析的新报告数量(先排序再截断,保证被解析子集确定性),并在证据块中披露 N more fresh report(s) not parsedMAX_FAILURE_CASES_PER_REPORT = 200parseTestReport 构建期间限制用例数;被丢弃的数量并入现有 more failing case(s) omitted 标记。两种形态均由新测试固化。
R10-1 (RA1-4) 模块遍历仅按词法路径去重——符号链接别名目录在恒定深度下指数级膨胀,且没有总访问上限 遍历现在按 realpathSync 去重(解析失败即失败关闭),并在超过 MAX_REACTOR_PROJECTS = 10000 个项目时失败关闭。由新增的符号链接链测试固化(win32 上跳过)。
R10-2 (RA2-3) parsePomStructureartifactId/parent artifactId/relativePath 捕获的是未经 XML 解码的原始文本;实体拼写的 id 会静默丢失继承边 这些捕获字段中出现任何 & 都会使该 POM 失败关闭——与现有 <module> 字符门禁一致。由新增的 &#45; 测试固化。
R10-4 (RA3-1) 位于非成员目录中、名为 pom.xml 的已声明 parent 从未被记录为依赖输入,导致删除它引发的 Non-resolvable parent POM 死亡被洗白进基础设施豁免 对每个其所在目录不是 reactor 成员的 parent,现在把最终解析路径(Maven 目录→pom.xml 追加之后)记录到 declaredParentFiles。具名文件继续记录其声明路径本身(被删除时同样记录)。由新增的已删除 parent 抑制测试固化。

Suggestion 发现

编号 发现 处理
R10-5 末位位置词不在生命周期词表内的 Maven 声明(mvn test deploymvn test site、插件目标)会按已识别阶段静默定论 settledByLifecycle/settledBySameScope 现在额外要求声明的末位非旗标位置词等于所声明的生命周期。阶段裁剪(clean test)仍可定论并保持披露。由新测试固化。
R10-6 注释错误地声称 -Dmaven.repo.local.tail=-Dmaven.repo.local= 开头 注释已更正:两个前缀互不相交(local.tail=local=),检查顺序无关紧要,两个检查都保留。
R10-7 failureInsideClaim 的根模块分支没有测试 新增 -pl . 孪生测试,使用 target/ 根部的标记来验证声明集矛盾分支。
R10-8 --timeout/--budget 的类型强转对 NaN/Infinity 是空操作 runBuildTest 现在在所有调用必经的边界处用描述性错误拒绝非有限的 --timeout/--budget(yargs 处理器已经会捕获并输出)。由新测试固化。
R10-9 test-delta 的 spawn 边界强转是惰性的——所有测试都注入了 exec 缝隙 新增测试走真实 spawn 路径并使用 --timeout 60.12360122.99999999999 毫秒),没有强转时会抛 ERR_OUT_OF_RANGE
R10-10 (RA1-1) 小数截止测试用 0.1,舍入后是整数 测试改用 timeout: 1.005(IEEE 1004.9999999999999)并显式传 --budget 600,使小数截止成为约束项,并断言 deadlineMs === 1005
R10-11 (RA1-6) isLaunchFailure 需要的 ENOSPC 形态可能被 trimOutput 的省略中段吞掉 磁盘满行形态导出为 isDiskFailureLine 并加入 trim 的抢救谓词;标记文本同步更新。由新 trim 测试固化。
R10-12 (RA1-7) .mvn/maven.config 用无上限的 readFileSync 读取 读取前先 statSync,超过 MAX_POM_BYTES 级上限即失败关闭(.mvn/ 前缀仍把配置文件本身标记为依赖输入)。由超限/未超限孪生测试固化。
R10-13 (RA1-10) install 失败的前置说明只在最终 return 处拼接;构建磁盘预检和构建失败的提前 return 跳过了它 提取 frameInstallFailure 辅助函数,在 npm 适配器所有 install 之后的 return 上应用。
R10-14 (RA1-11) 坐标形式 -pl 条目(:coregroupId:core)原样保留,永远无法定论或被证伪 mavenPlModules 把坐标条目规范化为最后一个 : 段;记录的目录选择器不可能含 :(POM 门禁拒绝),因此不受影响。由新定论测试固化。
R10-15 (RA1-12) 中断运行矛盾分支与未检查级联注释硬编码 "this review ran it" / "this Maven command was run by this review",对模块范围裁剪形式过度断言 scoped/phaseReduced/howItRan 计算提取为共享的 runForm 辅助函数,供已完成运行裁决、中断矛盾分支和全部三条未检查级联注释使用。由新的中断/超时/spawn 死亡披露测试固化。
R10-16 (RA2-9) Maven 门禁的 pom.xml 探测用 git cat-file -e,对名为 pom.xml 的目录也返回 0 gitHasPath 改用 cat-file -t 并要求 blob,覆盖根探测、嵌套探测和 package.json 探测。由根级与嵌套的目录名 pom.xml 回归测试固化。
R10-17 (RA2-11) 标签内不平衡的引号使 tokenizePom 把文档剩余部分吞进幻影 token,返回"成功但缩水"的结构 EOF 时引号仍打开现在使 POM 失败关闭;无引号的不闭合标签保持历史处理(其对照形态本就在下游失败关闭)。由新测试固化。
R10-18 (RA2-12) Maven 的整体调用预算只计 args.exec 内部耗时;命令前的解析/定界不计费 预算现在从调用顶部按墙钟计,与 npm 适配器和 toolchain.ts 契约一致。为保持"整体预算只有一个短截止的运行仍获得该次尝试"的既有保证,首次尝试以授予的预算为准(后续尝试以余额为准),截止时间以零为下限。两个断言截止时间的测试冻结 Date.now
R10-19 (RA2-13) scopesNonPl 遗漏 -s/--settings-gs/--global-settings-t/--toolchains-gt/--global-toolchains-b/--builder 五个旗标连同 = 形式一并加入。在现有非 -pl 作用域循环中固化。
R10-20 (RA3-4) -amd=/--also-make-dependents= 连接形式绕过了精确 token 匹配 两个 = 形式加入 scopesNonPl。在同一循环中固化。
R10-21 (RA3-5) 所在目录不承载任何项目的具名 parent 文件被变更时,经无主兜底升级为全 reactor 运行 兜底现在跳过经由具名 parent 路由的文件——其作用域正是上面已计算的继承者闭包。所属模块分支、reactor 外项目中止分支和根级分支保持不变。由新的探针形态测试固化。
R10-22 (RA3-9) 设计文档 Files 一节遗漏 test-delta.ts 已补充并说明其职责,test-delta.test.ts 加入验证命令。
R10-23 (RA4-2) 测试预言第 5 条错误地把 diff 输入例外套用到超时和 spawn 死亡 已改写:超时和 spawn 死亡始终是基础设施(无输入例外);获取失败才带 diff 输入例外。
R10-24 (RA4-3) fail-closed 的 it.each 用例从未创建子 POM,即使移除其字符门禁也会经缺失子 POM 门禁通过 missing 外每个用例现在都创建真实子项目(: 用例在 win32 上保留回退,因为 : 不是合法目录字符),使每个被点名的门禁成为唯一错误来源。
R10-25 (RA4-4) 具名 parent 文件的继承边注册在目录键下,两个方向都过度扩大闭包 具名 parent 文件的继承者现在按文件路径本身作键;具名 parent 变更的所有权闭包从文件键出发遍历。既有断言同步更新;新测试证明两个过度扩大方向(变更 core/pom.xml 不再拉入 app,变更 core/parent.xml 不再拉入 core/sub/ext)。
R10-26 (RA4-7) Maven 磁盘预检只在预热前运行一次,之后不再检查 预热后、生命周期命令前重新检查下限(与 npm 适配器的第二次预检一致),保持同样的"跳过并披露"报告形态。由新的 statfsSync 打桩测试固化。

评审级评论(非行内发现)

评审摘要的 CHANGES_REQUESTED 记录了 macos-latest、windows-latest 和无沙箱
集成分片在 CI 中被跳过,因此平台相关分支只由 Linux 上可注入平台的单元测试
覆盖;另外逆向审计因自身时间预算提前停止。提交无法改变被跳过的 CI 分片——
平台分支(win32 wrapper 分支、平台守卫)继续由可注入平台的单元测试覆盖
detectMavenOwnershipplatform: 'win32' 参数、
it.skipIf(process.platform === 'win32') 孪生测试),且 "Test Plan:
./mvnw — no such file or directory" 备注已被明确标记为非阻塞。

验证

实际执行的命令(均针对最终代码状态):

  • npm run build — 通过(退出码 0,无错误)
  • npm run typecheck — 通过(0 个 TypeScript 错误;最初的 webui 失败是缺少构建产物,npm run build 后解决)
  • npm run lint — 通过(0 错误、0 警告)
  • 对 11 个变更文件执行 npx prettier --check — 全部符合 Prettier 风格
  • 对评审套件执行 npx vitest runbuild-testnpm-toolchainmaven-toolchaintest-planbase-treeagent-prompttest-delta——即设计文档的验证命令集)— 7 个文件,609 通过
  • npx vitest run src/commands/review/ — 63 个文件,2399 通过,4 跳过
  • npx vitest run(整个 packages/cli)— 18317 通过,7 跳过;28 个失败(config/settings/UI 套件)在本轮改动被 stash 后同样复现,属于分支上的既有失败,与评审代码无关
  • 未改动 settings 源,因此无需 npm run generate:settings-schema;未改动仅经打包 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

@wenshao

wenshao commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor
_Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/31243168233)._

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Autofix round summary — no code changes this round

Feedback triaged

The only feedback newer than the last evaluation is one issue-level comment from
@qwen-code-ci-bot: "Qwen Code review timed out." — the review run hit the
360-minute budget after 21,600 seconds (workflow
run
). There are no
new inline comments, no new reviews, no failed checks, and no still-red checks;
all CI checks on the current head completed green.

Classification: operational notification, not actionable in code

This comment is the review workflow's own failure fallback ("Post fallback comment
on failure"), not a finding about the diff. Evidence:

  1. The run already had the maximum budget. The review workflow auto-scales the
    timeout by PR size: any PR changing more than 300 lines gets
    QWEN_REVIEW_MAX_TIMEOUT_MINUTES instead of the 180-minute default. This PR
    changes 11,273 lines across 17 files, so it received the 360-minute cap and
    consumed all of it. The fallback text itself confirms: "This run already used
    the maximum 360 minute timeout," so a @qwen-code /review --timeout=N retry
    cannot go higher.
  2. The timed-out review did not execute this PR's code. The review workflow
    checks out the trusted base branch and drives the review with the runner's
    installed/released qwen CLI ("SECURITY: checkout trusted base code; /review
    fetches PR diff context"). The review-toolchain changes in this diff were not
    what ran during the timed-out review, so the timeout is not attributable to a
    defect introduced here.
  3. The cost is workload, not a hang. A deep review of an ~11k-line PR —
    chunked line-by-line passes, removed-behavior and cross-file audits, test
    matrix, build/test probes, reverse-audit rounds, verification and composition —
    legitimately exceeds the cap on a loaded runner; the workflow's own comments
    record the same budget variance on earlier large PRs (e.g. fix(qqbot): restore per-group session isolation under thread scope #8241).

Disposition

  • Declined (not a code defect): the timeout names no defect, no failing check,
    and no finding; there is nothing in the diff to fix. No files were changed, no
    commit was made, and the branch remains at its previous head.
  • Left for a maintainer, with options and a recommendation: the review simply
    needs another attempt or a bigger envelope. Options, in increasing cost:
    1. Re-run the review with @qwen-code /review — run-to-run variance (runner
      load, model latency/quota) may let it finish inside the same budget.
    2. Raise the QWEN_REVIEW_MAX_TIMEOUT_MINUTES and
      QWEN_REVIEW_JOB_TIMEOUT_MINUTES repository variables (both are repo
      settings, not files in this PR, so this round cannot change them).
    3. Split this PR. That is an author/maintainer scope decision after ten review
      rounds, not one an autofix round should make unilaterally — not recommended
      at this stage.

State

  • Conflict flag: false — no base merge performed.
  • Working tree clean; no code edits; nothing committed.
  • All CI checks on the current head green (Test ubuntu Node 22.x, Desktop Shell,
    web-shell E2E Smoke, etc.; platform variants skipped by configuration).
中文说明

Autofix 本轮总结 — 本轮不做任何代码改动

本轮分诊的反馈

距上次评估之后新增的反馈只有一条来自 @qwen-code-ci-bot 的 issue 级评论:"Qwen Code review timed out." — 评审运行在 21,600 秒(360 分钟预算)后被终止(工作流运行记录)。没有新的行内评论、没有新的 review、没有失败的检查,也没有持续红色的检查;当前 head 上的所有 CI 检查均已通过。

分类:运维性通知,代码层面无需处理

这条评论是评审工作流自身的失败兜底步骤("Post fallback comment on failure")发出的,并不是针对本 diff 的缺陷发现。证据如下:

  1. 本次运行已经使用了最大预算。 评审工作流会按 PR 规模自动调整超时:任何改动超过 300 行的 PR 都会获得 QWEN_REVIEW_MAX_TIMEOUT_MINUTES,而不是默认的 180 分钟。本 PR 在 17 个文件中改动了 11,273 行,因此获得了 360 分钟的上限并全部用完。兜底评论文本本身也确认:"This run already used the maximum 360 minute timeout",所以用 @qwen-code /review --timeout=N 重试也无法设置更高的超时。
  2. 超时的评审并没有执行本 PR 的代码。 评审工作流检出受信任的基础分支,并用运行器上已安装/已发布的 qwen CLI 驱动评审("SECURITY: checkout trusted base code; /review fetches PR diff context")。本 diff 中对评审工具链的改动并不是那次超时评审中实际运行的代码,因此这次超时不能归因于本 PR 引入的缺陷。
  3. 耗时来自工作量本身,而非卡死。 对一个约 1.1 万行的 PR 做深度评审——分块逐行扫描、删除行为审计、跨文件追踪、测试矩阵、构建/测试探测、反向审计轮次、验证与组装——在负载较高的运行器上合理地超出上限;工作流自身的注释也记录了早期大型 PR(如 fix(qqbot): restore per-group session isolation under thread scope #8241)出现过同样的预算波动。

处置

  • 拒绝(非代码缺陷): 该超时没有指出任何缺陷、失败检查或发现;diff 中没有可修复的内容。本轮未改动任何文件、未创建提交,分支仍停留在原 head。
  • 留给维护者决定,附选项与建议:评审只是需要再试一次或更大的时间额度。选项按成本递增排列:
    1. @qwen-code /review 重新运行评审——运行之间的波动(运行器负载、模型延迟/配额)可能让它在相同预算内完成。
    2. 提高仓库变量 QWEN_REVIEW_MAX_TIMEOUT_MINUTESQWEN_REVIEW_JOB_TIMEOUT_MINUTES(两者都是仓库设置,不是本 PR 中的文件,本轮无法修改)。
    3. 拆分本 PR。这是在十轮评审之后由作者/维护者做出的范围决策,不应由 autofix 轮次单方面决定——现阶段不建议这样做。

状态

  • 冲突标志:false — 未执行 base 合并。
  • 工作区干净;无代码改动;未提交任何内容。
  • 当前 head 上的所有 CI 检查均为绿色(Test ubuntu Node 22.x、Desktop Shell、web-shell E2E Smoke 等;平台变体按配置跳过)。

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


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

@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. 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. Test Plan (not a blocker): ./mvnwno such file or directory.

中文说明

⚠️ 已从请求修改降级为评论:self-PR。 已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 Test Plan(非阻断):./mvnwno such file or directory

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

Comment on lines +366 to +369
const { adapter, applicable } = selectToolchainAdapter(
root,
toolchainAdapters,
);

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.

[Critical] Mixed root with npm-refused (unmodeled glob) + root pom.xml: the Maven adapter is selected alone and certifies the whole diff green — the unscopable npm half (frontend files) is disclosed nowhere; a PR that breaks the frontend ships green.

中文说明

npm 被拒绝(未建模 glob)+ 根 pom.xml 的混合根目录:Maven adapter 被单独选中并为整个 diff 出具绿色证明——无法定界的 npm 一侧(前端文件)完全未被披露;破坏前端的 PR 会带绿色报告合入。

Suggested change
const { adapter, applicable } = selectToolchainAdapter(
root,
toolchainAdapters,
);
When the selected adapter is Maven and a root package.json exists, append a disclosure to the report note ('files outside the Maven reactor were not verified'); for diffs touching only files no adapter scopes, route back to the unsupported handoff.

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

Comment on lines +254 to +255
it.skipIf(process.platform === 'win32')(
'deduplicates symlink-aliased reactor dirs by real path',

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.

[Critical] Two new Maven suite tests fail deterministically on macOS (30-level symlink fixture exceeds APFS symlink limit ~16 hops; 600-deep nesting fixture exceeds PATH_MAX 1024 with ENAMETOOLONG); only win32 is skipIf-guarded, so the required macOS merge-queue job goes red.

中文说明

新增的两个 Maven 测试在 macOS 上必然失败:30 层符号链接 fixture 超过 APFS 符号链接解析上限(约 16 跳),600 层嵌套 fixture 超过 PATH_MAX(1024) 在 setup 阶段抛 ENAMETOOLONG。只对 win32 做了 skipIf 守卫,必需的 macOS merge-queue 任务会变红。

Suggested change
it.skipIf(process.platform === 'win32')(
'deduplicates symlink-aliased reactor dirs by real path',
Guard both with `it.skipIf(process.platform === 'win32' || process.platform === 'darwin')`, or shrink the symlink chain to <= 15 levels and build the deep chain with short per-level chdir paths.

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

Comment on lines +463 to +466
if (statSync(pomPath).size > MAX_POM_BYTES) {
return `Maven POM ${toPosix(relative(reactorRoot, pomPath))} is larger than the ${MAX_POM_BYTES}-byte read cap.`;
}
pom = readFileSync(pomPath, 'utf8');

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.

[Critical] MAX_POM_BYTES cap checks size but not isFile(); a symlink-to-/dev/zero POM hangs the review with an infinite read loop (PR-controlled DoS).

中文说明

MAX_POM_BYTES 上限只检查大小不检查 isFile():指向 /dev/zero 的符号链接 POM 通过上限检查后,readFileSync 永不返回,评审进程被无限读循环挂死(PR 可控 DoS)。

Suggested change
if (statSync(pomPath).size > MAX_POM_BYTES) {
return `Maven POM ${toPosix(relative(reactorRoot, pomPath))} is larger than the ${MAX_POM_BYTES}-byte read cap.`;
}
pom = readFileSync(pomPath, 'utf8');
Require a regular file: `if (!st.isFile() || st.size > MAX_POM_BYTES) return ...;` at both read sites (visit and parent-file).

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

Comment on lines +622 to +626
if (basename(parentPom) === 'pom.xml') {
// An absent `pom.xml` target: Maven falls back to repository
// resolution, so there is no local edge to model.
continue;
}

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.

[Critical] A parent POM the diff DELETED inside a reactor module's tree never records an inheritance edge or parentPomFiles entry, so the heirs that now fail to resolve are never built — a false-green for a PR-caused Non-resolvable parent POM.

中文说明

被 diff 删除的、位于 reactor 模块目录内的父 POM 从不记录继承边或 parentPomFiles 条目,现在无法解析的继承者永远不会被构建——PR 引起的 Non-resolvable parent POM 得到假绿色。

Suggested change
if (basename(parentPom) === 'pom.xml') {
// An absent `pom.xml` target: Maven falls back to repository
// resolution, so there is no local edge to model.
continue;
}
When the read of a declared parent fails inside the named/pom.xml-spelled branch, still record the inheritance edge keyed by the declared path so the consumer's named-parent closure adds the heirs.

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

Comment on lines +661 to +663
const inheritorKey = namedParentFile
? toPosix(relative(reactorRoot, parentPom))
: parentPath;

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.

[Critical] Stale namedParentFile flag mis-keys inheritor edges for directory-spelled (../parent → keyed on parent/pom.xml), so the heir is silently left out of -pl scope; also launders deleted named-parent POM deaths into infrastructure.

中文说明

过期的 namedParentFile 标志导致目录拼写形式的 (如 ../parent)把继承边错误地挂在 parent/pom.xml 上,继承模块被静默排除在 -pl 作用域之外;同时删除的命名父 POM 死亡会被洗白成基础设施问题。

Suggested change
const inheritorKey = namedParentFile
? toPosix(relative(reactorRoot, parentPom))
: parentPath;
Recompute the flag after the append `const resolvedIsNamedFile = basename(parentPom) !== 'pom.xml';` and use it for the declaredParentFiles fall-through, namedParentPoms check, and inheritorKey ternary.

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

}
});

it('does not settle a claim whose final positional token is not the claimed lifecycle', () => {

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.

[Suggestion] The 'final positional token names the last work' guard is defeated by out-of-order phase spellings whose late phase is not in MAVEN_PHASE_RE: 'mvn deploy test' settles reproduces on a test-only run with no disclosure (probe: deploy test → reproduces; test deploy control → unchecked).

中文说明

'最终位置 token 命名最后工作' 守卫被乱序阶段拼写击败,其晚阶段不在 MAVEN_PHASE_RE 中:'mvn deploy test' 在仅 test 的运行上无披露地结算为 reproduces。

Suggested change
it('does not settle a claim whose final positional token is not the claimed lifecycle', () => {
When the claim's positional tokens contain a later default-lifecycle phase not in MAVEN_PHASE_RE (deploy, integration-test, site), treat the settlement like test deploy (unchecked), or extend MAVEN_PHASE_RE's late phases.

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

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.

Deferred to a later round / human follow-up. Critical-only mode is active on this PR (after 5 change-producing rounds), and the previous round ran out of time, so this round fixed only the 11 Critical findings (committed this round) and deferred every Suggestion-level item. This suggestion stays open and is recorded in the deferral audit — nothing was dropped.

中文说明

暂缓至后续轮次 / 人工跟进。 本 PR 已进入 Critical-only 模式(5 个产生改动的轮次之后),且上一轮耗尽了时间预算,因此本轮仅修复了 11 条 Critical 发现(已随本轮提交),所有 Suggestion 级别的条目均暂缓处理。该建议保持开放,并已记录在延后审计中——不会被遗漏。

Comment on lines +873 to +876
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');

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.

[Suggestion] scopesNonPl omits -T/--threads → a parallelism-scoped claim settles reproduces on a serial run (probe: claim -T 4 test vs serial run → reproduces).

中文说明

scopesNonPl 漏掉 -T/--threads → 并行度限定的声明在串行运行上结算为 reproduces。

Suggested change
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
Add -T/--threads (and attached forms) to scopesNonPl.

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

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.

Deferred to a later round / human follow-up. Critical-only mode is active on this PR (after 5 change-producing rounds), and the previous round ran out of time, so this round fixed only the 11 Critical findings (committed this round) and deferred every Suggestion-level item. This suggestion stays open and is recorded in the deferral audit — nothing was dropped.

中文说明

暂缓至后续轮次 / 人工跟进。 本 PR 已进入 Critical-only 模式(5 个产生改动的轮次之后),且上一轮耗尽了时间预算,因此本轮仅修复了 11 条 Critical 发现(已随本轮提交),所有 Suggestion 级别的条目均暂缓处理。该建议保持开放,并已记录在延后审计中——不会被遗漏。

Comment on lines +873 to +876
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');

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.

[Suggestion] scopesNonPl omits -fn/--fail-never → a fail-never claim is settled contradicted on a run whose failure its own semantics would exit 0 (probe with flip).

中文说明

scopesNonPl 漏掉 -fn/--fail-never → fail-never 声明在按其自身语义本应 exit 0 的运行失败上被结算为 contradicted。

Suggested change
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
Add -fn/--fail-never (and consider -o/--offline, -U) to scopesNonPl.

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

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.

Deferred to a later round / human follow-up. Critical-only mode is active on this PR (after 5 change-producing rounds), and the previous round ran out of time, so this round fixed only the 11 Critical findings (committed this round) and deferred every Suggestion-level item. This suggestion stays open and is recorded in the deferral audit — nothing was dropped.

中文说明

暂缓至后续轮次 / 人工跟进。 本 PR 已进入 Critical-only 模式(5 个产生改动的轮次之后),且上一轮耗尽了时间预算,因此本轮仅修复了 11 条 Critical 发现(已随本轮提交),所有 Suggestion 级别的条目均暂缓处理。该建议保持开放,并已记录在延后审计中——不会被遗漏。

token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
const claimTokens = claimed.split(/\s+/);

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.

[Suggestion] claimScopesItself uses naive whitespace split → scope-flag words inside a quoted -pl selector misread, false unchecked (probe with flip).

中文说明

claimScopesItself 使用朴素空白切分 → 引号内 -pl 选择器中的作用域标志词被误读,产生虚假的 unchecked。

Suggested change
const claimTokens = claimed.split(/\s+/);
Derive claimScopesItself/claimOnlyPlScoped from a quote-aware token walk.

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

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.

Deferred to a later round / human follow-up. Critical-only mode is active on this PR (after 5 change-producing rounds), and the previous round ran out of time, so this round fixed only the 11 Critical findings (committed this round) and deferred every Suggestion-level item. This suggestion stays open and is recorded in the deferral audit — nothing was dropped.

中文说明

暂缓至后续轮次 / 人工跟进。 本 PR 已进入 Critical-only 模式(5 个产生改动的轮次之后),且上一轮耗尽了时间预算,因此本轮仅修复了 11 条 Critical 发现(已随本轮提交),所有 Suggestion 级别的条目均暂缓处理。该建议保持开放,并已记录在延后审计中——不会被遗漏。

Comment on lines +1141 to +1144
const anyMavenRun = [
...(buildTest?.build ?? []),
...(buildTest?.test ?? []),
].some((c) => mavenLifecycle(c.command.trim()) !== null);

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.

[Suggestion] Goal-style Maven claims can never settle, and the 'was not run' note is factually false for the one goal the review itself does run: './mvnw dependency:go-offline' falls through to 'unchecked — this Maven command was not run by this review' even when the warm-up ran exactly that goal (probe-confirmed).

中文说明

目标风格 Maven 声明永远无法结算,且对评审自身确实运行的唯一目标来说"was not run"说明在事实上为假:./mvnw dependency:go-offline 在 warm-up 恰好运行了该目标的同一 reactor 上仍落入 "unchecked — this Maven command was not run by this review"。

Suggested change
const anyMavenRun = [
...(buildTest?.build ?? []),
...(buildTest?.test ?? []),
].some((c) => mavenLifecycle(c.command.trim()) !== null);
Test the recorded install command's goal tokens for the claim, or at minimum count the install run in anyMavenRun so a goal claim the warm-up executed reads reproduces or a note naming the warm-up.

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

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.

Deferred to a later round / human follow-up. Critical-only mode is active on this PR (after 5 change-producing rounds), and the previous round ran out of time, so this round fixed only the 11 Critical findings (committed this round) and deferred every Suggestion-level item. This suggestion stays open and is recorded in the deferral audit — nothing was dropped.

中文说明

暂缓至后续轮次 / 人工跟进。 本 PR 已进入 Critical-only 模式(5 个产生改动的轮次之后),且上一轮耗尽了时间预算,因此本轮仅修复了 11 条 Critical 发现(已随本轮提交),所有 Suggestion 级别的条目均暂缓处理。该建议保持开放,并已记录在延后审计中——不会被遗漏。

@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. 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. Test Plan (not a blocker): ./mvnwno such file or directory.

中文说明

⚠️ 已从请求修改降级为评论:self-PR。 已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 Test Plan(非阻断):./mvnwno such file or directory

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

Comment on lines +366 to +369
const { adapter, applicable } = selectToolchainAdapter(
root,
toolchainAdapters,
);

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.

[Critical] Mixed root with npm-refused (unmodeled glob) + root pom.xml: the Maven adapter is selected alone and certifies the whole diff green — the unscopable npm half (frontend files) is disclosed nowhere; a PR that breaks the frontend ships green.

中文说明

npm 被拒绝(未建模 glob)+ 根 pom.xml 的混合根目录:Maven adapter 被单独选中并为整个 diff 出具绿色证明——无法定界的 npm 一侧(前端文件)完全未被披露;破坏前端的 PR 会带绿色报告合入。

Suggested change
const { adapter, applicable } = selectToolchainAdapter(
root,
toolchainAdapters,
);
When the selected adapter is Maven and a root package.json exists, append a disclosure to the report note ('files outside the Maven reactor were not verified'); for diffs touching only files no adapter scopes, route back to the unsupported handoff.

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

Comment on lines +254 to +255
it.skipIf(process.platform === 'win32')(
'deduplicates symlink-aliased reactor dirs by real path',

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.

[Critical] Two new Maven suite tests fail deterministically on macOS (30-level symlink fixture exceeds APFS symlink limit ~16 hops; 600-deep nesting fixture exceeds PATH_MAX 1024 with ENAMETOOLONG); only win32 is skipIf-guarded, so the required macOS merge-queue job goes red.

中文说明

新增的两个 Maven 测试在 macOS 上必然失败:30 层符号链接 fixture 超过 APFS 符号链接解析上限(约 16 跳),600 层嵌套 fixture 超过 PATH_MAX(1024) 在 setup 阶段抛 ENAMETOOLONG。只对 win32 做了 skipIf 守卫,必需的 macOS merge-queue 任务会变红。

Suggested change
it.skipIf(process.platform === 'win32')(
'deduplicates symlink-aliased reactor dirs by real path',
Guard both with `it.skipIf(process.platform === 'win32' || process.platform === 'darwin')`, or shrink the symlink chain to <= 15 levels and build the deep chain with short per-level chdir paths.

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

Comment on lines +463 to +466
if (statSync(pomPath).size > MAX_POM_BYTES) {
return `Maven POM ${toPosix(relative(reactorRoot, pomPath))} is larger than the ${MAX_POM_BYTES}-byte read cap.`;
}
pom = readFileSync(pomPath, 'utf8');

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.

[Critical] MAX_POM_BYTES cap checks size but not isFile(); a symlink-to-/dev/zero POM hangs the review with an infinite read loop (PR-controlled DoS).

中文说明

MAX_POM_BYTES 上限只检查大小不检查 isFile():指向 /dev/zero 的符号链接 POM 通过上限检查后,readFileSync 永不返回,评审进程被无限读循环挂死(PR 可控 DoS)。

Suggested change
if (statSync(pomPath).size > MAX_POM_BYTES) {
return `Maven POM ${toPosix(relative(reactorRoot, pomPath))} is larger than the ${MAX_POM_BYTES}-byte read cap.`;
}
pom = readFileSync(pomPath, 'utf8');
Require a regular file: `if (!st.isFile() || st.size > MAX_POM_BYTES) return ...;` at both read sites (visit and parent-file).

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

Comment on lines +622 to +626
if (basename(parentPom) === 'pom.xml') {
// An absent `pom.xml` target: Maven falls back to repository
// resolution, so there is no local edge to model.
continue;
}

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.

[Critical] A parent POM the diff DELETED inside a reactor module's tree never records an inheritance edge or parentPomFiles entry, so the heirs that now fail to resolve are never built — a false-green for a PR-caused Non-resolvable parent POM.

中文说明

被 diff 删除的、位于 reactor 模块目录内的父 POM 从不记录继承边或 parentPomFiles 条目,现在无法解析的继承者永远不会被构建——PR 引起的 Non-resolvable parent POM 得到假绿色。

Suggested change
if (basename(parentPom) === 'pom.xml') {
// An absent `pom.xml` target: Maven falls back to repository
// resolution, so there is no local edge to model.
continue;
}
When the read of a declared parent fails inside the named/pom.xml-spelled branch, still record the inheritance edge keyed by the declared path so the consumer's named-parent closure adds the heirs.

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

Comment on lines +661 to +663
const inheritorKey = namedParentFile
? toPosix(relative(reactorRoot, parentPom))
: parentPath;

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.

[Critical] Stale namedParentFile flag mis-keys inheritor edges for directory-spelled (../parent → keyed on parent/pom.xml), so the heir is silently left out of -pl scope; also launders deleted named-parent POM deaths into infrastructure.

中文说明

过期的 namedParentFile 标志导致目录拼写形式的 (如 ../parent)把继承边错误地挂在 parent/pom.xml 上,继承模块被静默排除在 -pl 作用域之外;同时删除的命名父 POM 死亡会被洗白成基础设施问题。

Suggested change
const inheritorKey = namedParentFile
? toPosix(relative(reactorRoot, parentPom))
: parentPath;
Recompute the flag after the append `const resolvedIsNamedFile = basename(parentPom) !== 'pom.xml';` and use it for the declaredParentFiles fall-through, namedParentPoms check, and inheritorKey ternary.

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

}
});

it('does not settle a claim whose final positional token is not the claimed lifecycle', () => {

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.

[Suggestion] The 'final positional token names the last work' guard is defeated by out-of-order phase spellings whose late phase is not in MAVEN_PHASE_RE: 'mvn deploy test' settles reproduces on a test-only run with no disclosure (probe: deploy test → reproduces; test deploy control → unchecked).

中文说明

'最终位置 token 命名最后工作' 守卫被乱序阶段拼写击败,其晚阶段不在 MAVEN_PHASE_RE 中:'mvn deploy test' 在仅 test 的运行上无披露地结算为 reproduces。

Suggested change
it('does not settle a claim whose final positional token is not the claimed lifecycle', () => {
When the claim's positional tokens contain a later default-lifecycle phase not in MAVEN_PHASE_RE (deploy, integration-test, site), treat the settlement like test deploy (unchecked), or extend MAVEN_PHASE_RE's late phases.

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

Comment on lines +873 to +876
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');

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.

[Suggestion] scopesNonPl omits -T/--threads → a parallelism-scoped claim settles reproduces on a serial run (probe: claim -T 4 test vs serial run → reproduces).

中文说明

scopesNonPl 漏掉 -T/--threads → 并行度限定的声明在串行运行上结算为 reproduces。

Suggested change
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
Add -T/--threads (and attached forms) to scopesNonPl.

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

Comment on lines +873 to +876
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');

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.

[Suggestion] scopesNonPl omits -fn/--fail-never → a fail-never claim is settled contradicted on a run whose failure its own semantics would exit 0 (probe with flip).

中文说明

scopesNonPl 漏掉 -fn/--fail-never → fail-never 声明在按其自身语义本应 exit 0 的运行失败上被结算为 contradicted。

Suggested change
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
Add -fn/--fail-never (and consider -o/--offline, -U) to scopesNonPl.

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

token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
const claimTokens = claimed.split(/\s+/);

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.

[Suggestion] claimScopesItself uses naive whitespace split → scope-flag words inside a quoted -pl selector misread, false unchecked (probe with flip).

中文说明

claimScopesItself 使用朴素空白切分 → 引号内 -pl 选择器中的作用域标志词被误读,产生虚假的 unchecked。

Suggested change
const claimTokens = claimed.split(/\s+/);
Derive claimScopesItself/claimOnlyPlScoped from a quote-aware token walk.

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

Comment on lines +1141 to +1144
const anyMavenRun = [
...(buildTest?.build ?? []),
...(buildTest?.test ?? []),
].some((c) => mavenLifecycle(c.command.trim()) !== null);

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.

[Suggestion] Goal-style Maven claims can never settle, and the 'was not run' note is factually false for the one goal the review itself does run: './mvnw dependency:go-offline' falls through to 'unchecked — this Maven command was not run by this review' even when the warm-up ran exactly that goal (probe-confirmed).

中文说明

目标风格 Maven 声明永远无法结算,且对评审自身确实运行的唯一目标来说"was not run"说明在事实上为假:./mvnw dependency:go-offline 在 warm-up 恰好运行了该目标的同一 reactor 上仍落入 "unchecked — this Maven command was not run by this review"。

Suggested change
const anyMavenRun = [
...(buildTest?.build ?? []),
...(buildTest?.test ?? []),
].some((c) => mavenLifecycle(c.command.trim()) !== null);
Test the recorded install command's goal tokens for the claim, or at minimum count the install run in anyMavenRun so a goal claim the warm-up executed reads reproduces or a note naming the warm-up.

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

@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. 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. Test Plan (not a blocker): ./mvnwno such file or directory.

中文说明

⚠️ 已从请求修改降级为评论:self-PR。 已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 Test Plan(非阻断):./mvnwno such file or directory

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

Comment on lines +366 to +369
const { adapter, applicable } = selectToolchainAdapter(
root,
toolchainAdapters,
);

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.

[Critical] Mixed root with npm-refused (unmodeled glob) + root pom.xml: the Maven adapter is selected alone and certifies the whole diff green — the unscopable npm half (frontend files) is disclosed nowhere; a PR that breaks the frontend ships green.

中文说明

npm 被拒绝(未建模 glob)+ 根 pom.xml 的混合根目录:Maven adapter 被单独选中并为整个 diff 出具绿色证明——无法定界的 npm 一侧(前端文件)完全未被披露;破坏前端的 PR 会带绿色报告合入。

Suggested change
const { adapter, applicable } = selectToolchainAdapter(
root,
toolchainAdapters,
);
When the selected adapter is Maven and a root package.json exists, append a disclosure to the report note ('files outside the Maven reactor were not verified'); for diffs touching only files no adapter scopes, route back to the unsupported handoff.

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

Comment on lines +254 to +255
it.skipIf(process.platform === 'win32')(
'deduplicates symlink-aliased reactor dirs by real path',

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.

[Critical] Two new Maven suite tests fail deterministically on macOS (30-level symlink fixture exceeds APFS symlink limit ~16 hops; 600-deep nesting fixture exceeds PATH_MAX 1024 with ENAMETOOLONG); only win32 is skipIf-guarded, so the required macOS merge-queue job goes red.

中文说明

新增的两个 Maven 测试在 macOS 上必然失败:30 层符号链接 fixture 超过 APFS 符号链接解析上限(约 16 跳),600 层嵌套 fixture 超过 PATH_MAX(1024) 在 setup 阶段抛 ENAMETOOLONG。只对 win32 做了 skipIf 守卫,必需的 macOS merge-queue 任务会变红。

Suggested change
it.skipIf(process.platform === 'win32')(
'deduplicates symlink-aliased reactor dirs by real path',
Guard both with `it.skipIf(process.platform === 'win32' || process.platform === 'darwin')`, or shrink the symlink chain to <= 15 levels and build the deep chain with short per-level chdir paths.

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

Comment on lines +463 to +466
if (statSync(pomPath).size > MAX_POM_BYTES) {
return `Maven POM ${toPosix(relative(reactorRoot, pomPath))} is larger than the ${MAX_POM_BYTES}-byte read cap.`;
}
pom = readFileSync(pomPath, 'utf8');

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.

[Critical] MAX_POM_BYTES cap checks size but not isFile(); a symlink-to-/dev/zero POM hangs the review with an infinite read loop (PR-controlled DoS).

中文说明

MAX_POM_BYTES 上限只检查大小不检查 isFile():指向 /dev/zero 的符号链接 POM 通过上限检查后,readFileSync 永不返回,评审进程被无限读循环挂死(PR 可控 DoS)。

Suggested change
if (statSync(pomPath).size > MAX_POM_BYTES) {
return `Maven POM ${toPosix(relative(reactorRoot, pomPath))} is larger than the ${MAX_POM_BYTES}-byte read cap.`;
}
pom = readFileSync(pomPath, 'utf8');
Require a regular file: `if (!st.isFile() || st.size > MAX_POM_BYTES) return ...;` at both read sites (visit and parent-file).

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

Comment on lines +622 to +626
if (basename(parentPom) === 'pom.xml') {
// An absent `pom.xml` target: Maven falls back to repository
// resolution, so there is no local edge to model.
continue;
}

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.

[Critical] A parent POM the diff DELETED inside a reactor module's tree never records an inheritance edge or parentPomFiles entry, so the heirs that now fail to resolve are never built — a false-green for a PR-caused Non-resolvable parent POM.

中文说明

被 diff 删除的、位于 reactor 模块目录内的父 POM 从不记录继承边或 parentPomFiles 条目,现在无法解析的继承者永远不会被构建——PR 引起的 Non-resolvable parent POM 得到假绿色。

Suggested change
if (basename(parentPom) === 'pom.xml') {
// An absent `pom.xml` target: Maven falls back to repository
// resolution, so there is no local edge to model.
continue;
}
When the read of a declared parent fails inside the named/pom.xml-spelled branch, still record the inheritance edge keyed by the declared path so the consumer's named-parent closure adds the heirs.

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

Comment on lines +661 to +663
const inheritorKey = namedParentFile
? toPosix(relative(reactorRoot, parentPom))
: parentPath;

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.

[Critical] Stale namedParentFile flag mis-keys inheritor edges for directory-spelled (../parent → keyed on parent/pom.xml), so the heir is silently left out of -pl scope; also launders deleted named-parent POM deaths into infrastructure.

中文说明

过期的 namedParentFile 标志导致目录拼写形式的 (如 ../parent)把继承边错误地挂在 parent/pom.xml 上,继承模块被静默排除在 -pl 作用域之外;同时删除的命名父 POM 死亡会被洗白成基础设施问题。

Suggested change
const inheritorKey = namedParentFile
? toPosix(relative(reactorRoot, parentPom))
: parentPath;
Recompute the flag after the append `const resolvedIsNamedFile = basename(parentPom) !== 'pom.xml';` and use it for the declaredParentFiles fall-through, namedParentPoms check, and inheritorKey ternary.

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

}
});

it('does not settle a claim whose final positional token is not the claimed lifecycle', () => {

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.

[Suggestion] The 'final positional token names the last work' guard is defeated by out-of-order phase spellings whose late phase is not in MAVEN_PHASE_RE: 'mvn deploy test' settles reproduces on a test-only run with no disclosure (probe: deploy test → reproduces; test deploy control → unchecked).

中文说明

'最终位置 token 命名最后工作' 守卫被乱序阶段拼写击败,其晚阶段不在 MAVEN_PHASE_RE 中:'mvn deploy test' 在仅 test 的运行上无披露地结算为 reproduces。

Suggested change
it('does not settle a claim whose final positional token is not the claimed lifecycle', () => {
When the claim's positional tokens contain a later default-lifecycle phase not in MAVEN_PHASE_RE (deploy, integration-test, site), treat the settlement like test deploy (unchecked), or extend MAVEN_PHASE_RE's late phases.

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

Comment on lines +873 to +876
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');

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.

[Suggestion] scopesNonPl omits -T/--threads → a parallelism-scoped claim settles reproduces on a serial run (probe: claim -T 4 test vs serial run → reproduces).

中文说明

scopesNonPl 漏掉 -T/--threads → 并行度限定的声明在串行运行上结算为 reproduces。

Suggested change
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
Add -T/--threads (and attached forms) to scopesNonPl.

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

Comment on lines +873 to +876
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');

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.

[Suggestion] scopesNonPl omits -fn/--fail-never → a fail-never claim is settled contradicted on a run whose failure its own semantics would exit 0 (probe with flip).

中文说明

scopesNonPl 漏掉 -fn/--fail-never → fail-never 声明在按其自身语义本应 exit 0 的运行失败上被结算为 contradicted。

Suggested change
token === '-b' ||
token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
Add -fn/--fail-never (and consider -o/--offline, -U) to scopesNonPl.

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

token.startsWith('-b=') ||
token === '--builder' ||
token.startsWith('--builder=');
const claimTokens = claimed.split(/\s+/);

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.

[Suggestion] claimScopesItself uses naive whitespace split → scope-flag words inside a quoted -pl selector misread, false unchecked (probe with flip).

中文说明

claimScopesItself 使用朴素空白切分 → 引号内 -pl 选择器中的作用域标志词被误读,产生虚假的 unchecked。

Suggested change
const claimTokens = claimed.split(/\s+/);
Derive claimScopesItself/claimOnlyPlScoped from a quote-aware token walk.

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

Comment on lines +1141 to +1144
const anyMavenRun = [
...(buildTest?.build ?? []),
...(buildTest?.test ?? []),
].some((c) => mavenLifecycle(c.command.trim()) !== null);

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.

[Suggestion] Goal-style Maven claims can never settle, and the 'was not run' note is factually false for the one goal the review itself does run: './mvnw dependency:go-offline' falls through to 'unchecked — this Maven command was not run by this review' even when the warm-up ran exactly that goal (probe-confirmed).

中文说明

目标风格 Maven 声明永远无法结算,且对评审自身确实运行的唯一目标来说"was not run"说明在事实上为假:./mvnw dependency:go-offline 在 warm-up 恰好运行了该目标的同一 reactor 上仍落入 "unchecked — this Maven command was not run by this review"。

Suggested change
const anyMavenRun = [
...(buildTest?.build ?? []),
...(buildTest?.test ?? []),
].some((c) => mavenLifecycle(c.command.trim()) !== null);
Test the recorded install command's goal tokens for the claim, or at minimum count the install run in anyMavenRun so a goal claim the warm-up executed reads reproduces or a note naming the warm-up.

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (timeout (7200000ms)) (attempt 10/100) — it will retry on the next scan.

What I found before stopping:
Qwen failed during address-review: timeout (7200000ms).

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31267968791


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

…nt defects (QwenLM#8394)

Eleven Critical findings from the latest review round:

Evidence integrity (false-green shapes):
- Fail closed when fresh Surefire/Failsafe reports exceed the parse cap:
  unparsed reports carry unknown failure status, so ok can no longer be
  certified (was: disclose-only, a fail-never exit-0 green over failed runs).
- Treat Maven's `[ERROR] Failed to execute goal` framing as a swallowed
  failure under fail-never — checkstyle/enforcer/spotless goal deaths
  matched none of the compile/dependency/launch classes and read green.
  Kept out of SOURCE_FAILURE_LINE_RE so dependency-framed goal failures
  keep their acquisition carve-out; also rescued from the output trim.
- DEPENDENCY_FAILURE_LINE_RE now covers `Non-resolvable import POM`,
  `Failure to find`, and `Could not find artifact`.

POM reads (DoS + deleted-parent scoping):
- Require isFile() — not just the size cap — at every MAX_POM_BYTES read
  site (reactor walk, parent-file read, .mvn/maven.config): a symlink to
  /dev/zero or a FIFO passed the size check and hung readFileSync.
- Record inheritance edges for ABSENT declared parents (the shape a
  deleting diff leaves), keyed on the paths git reports, so the heirs of
  a Non-resolvable parent POM join the changed-parent closure instead of
  shipping the death green.
- Recompute the named-parent flag after the directory->pom.xml append: a
  directory-spelled <relativePath> used to key its heir edge on
  `dir/pom.xml` (where the closure never looks) and skipped the
  declared-file recording.

Runner and selection honesty:
- mavenExecutable falls back to system mvn for an EMPTY wrapper (it
  passed the existence/exec-bit gates and exited 0 over a build that
  never started).
- Mixed roots (Maven selected while a root package.json exists) disclose
  on every report that files outside the Maven reactor were NOT verified.

Claim settlement:
- A Maven run interrupted with fresh recorded failures now out-ranks a
  green finished sibling; the finished-run ranking used to read
  `reproduces` while the build-test report said ok:false.

Tests: skip the 30-hop symlink and 600-deep nesting fixtures on darwin
(APFS symlink limit, PATH_MAX 1024) — both failed deterministically on
the macOS merge-queue job — and add focused gates for every fix above.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this round itself may have failed differently). That is 3 full agent runs that pushed nothing. A human should split or reduce the PR (or raise the agent time budget AND its step backstop together), then comment @qwen-code /retry to re-arm. Until then future scans will skip this PR.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen failed during address-review: timeout (1080000ms).

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/31275438474


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

⏸️ Takeover paused: this PR reached its round cap (100/100). Comment @qwen-code /takeover to re-arm a fresh window and continue management, or @qwen-code /takeover stop to release.

中文说明

⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 @qwen-code /takeover 可重新武装、开启新窗口继续托管;或评论 @qwen-code /takeover stop 释放。

@wenshao

wenshao commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover stop

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

Copy link
Copy Markdown
Collaborator

👋 Takeover released: the autofix loop will no longer engage this PR (an in-flight round, if any, completes its bounded work). Re-apply autofix/takeover (or comment @qwen-code /takeover) to re-engage.

中文说明

👋 已释放:autofix 循环不再介入此 PR(在飞的一轮如有,将完成其有界工作)。重新打上 autofix/takeover 标签(或评论 @qwen-code /takeover)即可再次接管。

@wenshao

wenshao commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favour of a split, rebased pair. This PR accumulated 55 issue comments, 132 reviews, and 514 inline comments across 11 review rounds, which made the thread impractical to work from.

Replaced by:

What changed beyond the split. The static POM parser is gone. #8394 read the reactor out of the POM text — literal <modules> recursion, CDATA and comment handling, <parent> relativePath resolution with the artifactId match Maven itself applies, named and deleted parent files, and an aggregation-plus-inheritance closure over all of it. That was a second, weaker model of exactly what the next command evaluates for real, and it was weakest on the shapes that motivated the work: profile-activated modules in Druid and Flink, where a text-level parse cannot evaluate activation and had to fail closed.

Maven now answers those questions itself. Ownership is a nearest-ancestor pom.xml walk; whether a project is in the active reactor comes from Maven rejecting the -pl selector, which it does before compiling anything; a changed POM runs the reactor instead of a guessed closure. That removed ~670 lines of adapter source and ~1360 lines of its tests, with no extra Maven invocation.

Review feedback already addressed here is carried into the new PRs. Apologies for the churn on the thread.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants