Skip to content

chore(pnpm): gate pnpm-lock on package-lock and declare hoisted imports - #11625

Merged
yiliang114 merged 9 commits into
mainfrom
chore/pnpm-lock-gates
Sep 12, 2026
Merged

yiliang114 merged 9 commits into
mainfrom
chore/pnpm-lock-gates

Conversation

@yiliang114

Copy link
Copy Markdown
Collaborator

What this PR does

Follow-up to the post–Stage 1 review on #10444. It adds the consistency gates the dual-lockfile setup has been missing, and it fixes everything those gates flag today.

  • pnpm lockfile vs npm lockfile. The lockfile check now requires every package version in the pnpm lockfile to also be locked in the npm lockfile. A pnpm worktree therefore never runs a dependency version that CI has not installed.
    • The reverse direction is deliberately not enforced. pnpm dedupes where npm keeps nested copies, so the two graphs are never identical. Example: npm locks esbuild 0.25.6 at the root and 0.25.12 nested; pnpm uses 0.25.12 for both.
    • One documented exception covers a gap in the npm lockfile itself: form-data's mime-types@2.1.35 pins mime-db 1.52.0 exactly, but npm locks no nested copy. The check also fails if that exception goes stale.
  • Install scripts need an explicit decision. Every dependency that runs an install script under npm must now have an explicit pnpm build decision: allow or deny. fsevents gets a deny entry, which records what pnpm already does on the macOS smoke job.
  • pnpm lockfile regenerated with pnpm import. The pnpm lockfile is now generated from the npm lockfile with pnpm import, which is also the documented regeneration step from now on.
    • Two consecutive imports produced byte-identical output, and a frozen install accepts the result.
    • This drops 18 versions that pnpm had resolved on its own and npm never locked, including @typescript-eslint/*@8.53.1, @vitest/browser@3.2.7, minimatch@10.2.6 and diff@8.0.4.
  • Undeclared imports. ESLint now enforces import/no-extraneous-dependencies on shipped sources; tests are excluded and type-only imports are exempt. This is the check an isolated node_modules layout would add, without changing the layout.
    • It found 15 imports that only resolved through hoisting. This PR declares them with caret ranges on the versions already locked, so no new package enters either tree:
      • CLI: ansi-escapes, chalk, open, semver
      • core: zod and seven @opentelemetry/* packages
      • VS Code companion: lucide-react
    • web-templates' two build sub-projects are pointed at web-templates' own manifest.
  • Smoke trigger. The pnpm smoke workflow now also runs when only the npm lockfile changes.
  • Contributor guide. It now describes the new regeneration step and adds two clarifications: on ext4 the pnpm bootstrap saves little disk, and it skips the prepare build, so run npm run build before package tests.
  • Verification brief. A brief under docs/verification/ describes the two measurements that still decide Stage 2: per-worktree disk on the hosts that run agent worktrees, and a run of the CI matrix on a pnpm-installed hoisted tree.

Why it's needed

The #10444 review found that the two lockfiles had already drifted three days after Stage 1 merged, and no gate noticed. Two separate symptoms:

  • 14 direct dependencies of workspace packages resolved to different versions under the two lockfiles. The review comment undercounted this as two.
  • 19 versions existed only in the pnpm lockfile.

The cause is systemic. pnpm install --lockfile-only resolves every range on its own to the newest match, so hand-kept overrides cannot keep up. Deriving the pnpm lockfile from the npm one, and gating the direction that matters, makes the dual-lock setup safe to keep for as long as the pnpm bootstrap stays opt-in.

After this PR, 13 direct dependencies still resolve to different versions under the two lockfiles, and each of them resolves under pnpm to a version npm also locks elsewhere in its tree.

The undeclared imports matter for the same reason. Under hoisting they are invisible until a package is installed on its own, and with the lint rule in place, isolated linking is no longer needed to find them.

Reviewer Test Plan

How to verify

  • The lockfile check passes on this branch. It prints four passing lines, ending with "pnpm lockfile matches package-lock.json." and "pnpm build approvals cover every install script."
  • The version gate fails on the old lock. Put main's pnpm lockfile back into this branch and run the check again. It fails and lists the same 18 versions quoted above.
  • The build-decision gate fails without fsevents. Remove the fsevents entry from the pnpm workspace config and run the check. It fails and names fsevents.
  • The regenerated lockfile installs everywhere. The pnpm Worktree Smoke workflow passes on Linux, macOS and Windows.
  • The new lint rule holds. Lint & Static passes. Deleting one of the new declarations, for example core's zod, makes lint fail.

Evidence (Before & After)

N/A. This PR changes tooling and dependency manifests only.

Tested on

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

These checks ran on Linux with Node 22.23.2:

  • the lockfile check, including both failure cases above;
  • pnpm import twice, with identical output, followed by a frozen lockfile-only install;
  • ESLint with the repository config over all packages and integrations: 0 violations of the new rule. That run used a local node_modules older than main, so CI is the authority.
  • Prettier's experimental CLI on every changed file.

Not run locally: build, typecheck, and unit tests, including the updated package-scripts test. CI covers those. macOS and Windows are covered by the smoke workflow.

Environment (optional)

N/A

Risk & Scope

  • Main risk or tradeoff: the regenerated pnpm lockfile changes which versions a pnpm worktree installs, always to versions the npm lockfile already locks. The smoke workflow's frozen installs on three OSes check that. The new manifest declarations change no installed version, and they do not reach the published CLI, whose manifest is generated separately.
  • Not validated / out of scope:
    • The disk and parity measurements themselves; the brief hands them off.
    • The build-artifact probe suggested in the review. Every install script npm runs now needs an explicit pnpm decision, and the remaining silent cases, such as non-fatal native builds, behave the same under npm.
  • Breaking changes / migration notes: anyone changing dependencies now updates the npm lockfile first, then regenerates the pnpm lockfile with corepack pnpm import. The old --lockfile-only command will usually make the lockfile check fail.

Linked Issues

Part of #10444. The issue stays open for the Stage 2 go/no-go decision.

中文说明

这个 PR 做了什么

这是 #10444 上 Stage 1 之后那次复盘 的后续。它补上双 lockfile 一直缺少的一致性门,并修掉这些门在今天会报出的所有问题。

  • pnpm lockfile 对照 npm lockfile。 lockfile 检查现在要求 pnpm lockfile 里的每个包版本都已在 npm lockfile 中锁定。这样 pnpm worktree 就不会运行 CI 没有安装过的依赖版本。
    • 反方向刻意不做要求。npm 会保留嵌套副本的地方,pnpm 会去重,所以两张依赖图永远不会完全相同。例如 npm 在根目录锁定 esbuild 0.25.6、嵌套位置锁定 0.25.12,pnpm 两处都用 0.25.12。
    • 有一条带说明的例外,对应 npm lockfile 自身的缺口:form-data 下的 mime-types@2.1.35 精确要求 mime-db 1.52.0,但 npm 没有锁定嵌套副本。这条例外一旦过期,检查也会失败。
  • 构建脚本需要显式决定。 在 npm 下会运行 install 脚本的每个依赖,现在都必须有一条显式的 pnpm 构建决定:允许或拒绝。fsevents 加了一条拒绝,记录的是 pnpm 在 macOS smoke 任务上本来就有的行为。
  • pnpm import 重新生成 pnpm lockfile。 pnpm lockfile 现在用 pnpm import 从 npm lockfile 生成,这也是今后文档里写的再生成步骤。
    • 连续两次 import 的输出逐字节相同,frozen install 也接受这个结果。
    • 这去掉了 18 个由 pnpm 自行解析、npm 从未锁定的版本,包括 @typescript-eslint/*@8.53.1@vitest/browser@3.2.7minimatch@10.2.6diff@8.0.4
  • 未声明的 import。 ESLint 现在对会发出去的源码启用 import/no-extraneous-dependencies;测试文件不在范围内,纯类型 import 豁免。这正是 isolated node_modules 布局才会带来的检查,但不需要改动布局。
    • 规则找出 15 处只靠 hoisting 才能解析的 import。本 PR 用已锁定版本上的 caret 范围把它们声明出来,两个依赖树都不会引入新包:
      • CLI:ansi-escapeschalkopensemver
      • core:zod 和 7 个 @opentelemetry/*
      • VS Code companion:lucide-react
    • web-templates 的两个构建子项目改为指向 web-templates 自己的 manifest。
  • smoke 触发条件。 只改 npm lockfile 时,pnpm smoke workflow 现在也会运行。
  • 贡献者指南。 写入新的再生成步骤,并补两句说明:ext4 上 pnpm bootstrap 基本不省磁盘;它跳过了 prepare 构建,所以跑包内测试前要先执行 npm run build
  • 验证说明。 docs/verification/ 下新增一份说明,描述仍然决定 Stage 2 的两项测量:跑 agent worktree 的主机上每个 worktree 的磁盘占用,以及在 pnpm 安装的 hoisted 树上跑一遍 CI 矩阵。

为什么需要

#10444 的复盘发现,Stage 1 合入三天后两个 lockfile 就已经漂移,而且没有任何门察觉。两种症状:

  • 在两个 lockfile 下,有 14 个 workspace 包的直接依赖解析到不同版本。复盘评论里低估成了两处。
  • 有 19 个版本只存在于 pnpm lockfile。

原因是系统性的。pnpm install --lockfile-only 会把每个范围独立地解析到最新的匹配版本,手工维护的 overrides 跟不上。让 pnpm lockfile 从 npm lockfile 派生,并对要紧的那个方向设门,就能让双 lock 在 pnpm bootstrap 仍是 opt-in 期间安全地保留下去。

合入本 PR 后,仍有 13 个直接依赖在两个 lockfile 下解析到不同版本,而且每一个在 pnpm 下解析到的版本,npm 在自己的依赖树里也锁定了。

未声明 import 的问题出于同样的原因。在 hoisting 下,它们要等某个包被单独安装时才会暴露;有了这条 lint 规则,就不再需要靠 isolated 链接去发现它们。

审阅者测试计划

如何验证

  • 本分支上 lockfile 检查通过。 它打印四行通过信息,最后两行是 "pnpm lockfile matches package-lock.json." 和 "pnpm build approvals cover every install script."。
  • 版本门在旧 lock 上会失败。 把 main 的 pnpm lockfile 放回本分支再跑检查。它会失败,并列出上面提到的同样 18 个版本。
  • 构建决定门在缺少 fsevents 时会失败。 从 pnpm workspace 配置里删掉 fsevents 那一条再跑检查。它会失败,并点名 fsevents
  • 重新生成的 lockfile 在各平台都能安装。 pnpm Worktree Smoke workflow 在 Linux、macOS、Windows 上都通过。
  • 新的 lint 规则生效。 Lint & Static 通过。删掉任意一条新增声明(例如 core 的 zod),lint 就会失败。

证据(前后对比)

N/A。本 PR 只改工具链和依赖 manifest。

测试平台

macOS ⚠️、Windows ⚠️、Linux ✅。以下检查在 Linux、Node 22.23.2 上运行过:

  • lockfile 检查,包括上面两种失败情形;
  • 两次 pnpm import,输出相同,之后跑一次 frozen lockfile-only install;
  • 用仓库的 ESLint 配置检查全部 packages 和 integrations:新规则 0 处违规。本地 node_modulesmain 旧,所以以 CI 结果为准。
  • 对所有改动文件跑 Prettier 的 experimental CLI。

本地没有运行:build、typecheck 和单元测试(包括改过的 package-scripts 测试)。这些由 CI 负责,macOS 和 Windows 由 smoke workflow 覆盖。

环境(可选)

N/A

风险与范围

  • 主要风险或取舍: 重新生成的 pnpm lockfile 会改变 pnpm worktree 安装的版本,但改到的一定是 npm lockfile 已经锁定的版本。smoke workflow 在三个操作系统上的 frozen install 负责检查这一点。新增的 manifest 声明不改变任何已安装版本,也不会进入已发布的 CLI,因为它的 manifest 是单独生成的。
  • 未验证 / 不在范围内:
    • 磁盘测量和 parity 测量本身;验证说明把它们交接出去。
    • 复盘里建议的构建产物探针。现在 npm 会运行的每个 install 脚本都需要显式的 pnpm 决定,剩下的静默情形(例如不致命的原生构建)在 npm 下表现相同。
  • 破坏性变更 / 迁移说明: 修改依赖时,先更新 npm lockfile,再用 corepack pnpm import 重新生成 pnpm lockfile。旧的 --lockfile-only 命令通常会让 lockfile 检查失败。

关联 Issue

属于 #10444 的一部分。该 issue 继续保持打开,用于 Stage 2 的 go/no-go 决定。

- Lockfile check: every pnpm-lock version must be locked in package-lock
  (one documented npm-lock gap, failing if it goes stale), and every
  dependency npm runs an install script for needs an explicit allowBuilds
  decision (fsevents: false records current behaviour).
- Regenerate pnpm-lock with `pnpm import`, which is deterministic and drops
  18 versions npm never locked; document it as the regeneration step.
- Enforce import/no-extraneous-dependencies on shipped sources and declare
  the 15 imports that only resolved through hoisting, at already-locked
  versions.
- Run the pnpm smoke workflow when package-lock.json changes.
- Add the Stage 2 verification brief.

Refs #10444
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

⚠️ Deferred approval withheld — 1 PR CI workflow run(s) on efdd802 did not finish green; see the updated table in the Stage 2 comment. Re-run @qwen-code /triage after fixes. finalize run

⚠️ 延迟审批已搁置 —— efdd802 有 1 个 PR CI workflow 未以绿色完成,详见 Stage 2 评论中已更新的表格。修复后可重新运行 @qwen-code /triage查看 finalize 运行

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — every heading filled in, a real Tested-on table rather than a blank one, and a complete paragraph-by-paragraph Chinese translation.

Problem: observed, not theoretical. The #10444 post-Stage-1 review measured the two lockfiles drifting three days after the bootstrap merged. Rather than take the numbers on faith I reproduced the drift class myself: comparing main's pnpm-lock.yaml against this branch's package-lock.json yields exactly 19 pnpm-resolved versions that npm never locks, 18 once the documented mime-db@1.52.0 exception is set aside. That matches the description, including the four it names specifically — @typescript-eslint/*@8.53.1, @vitest/browser@3.2.7, minimatch@10.2.6, diff@8.0.4.

Direction: aligned. This hardens a Stage 1 that already landed (perf(dev): add pnpm worktree bootstrap foundation, #10449) rather than extending it, and it keeps npm authoritative — the gate only asserts that a pnpm worktree cannot run a version CI has not installed. It reaches no auth, sandbox, model-selection, telemetry, release, or public-contract surface: I checked all 15 new manifest declarations against the locked tree and not one changes a resolved version, so nothing here can reach the published CLI. No external CHANGELOG analogue, which is what you'd expect for internal build tooling; the repo's own history has precedent for lockfile-consistency work (#8858, #1659).

Size: no core source paths — packages/core/package.json is a manifest, not packages/core/src/**. Production/tooling logic is ~174 lines (scripts/check-lockfile.js 105, eslint.config.js 48, three package manifests 15, the pnpm workspace config 4, the smoke trigger 2). The rest is 1497 lines of regenerated lockfile, 111 lines of docs, 6 lines of test. Far under the 500-line threshold, so no large-PR advisory either. You also have admin on this repo, so the two-tier core gate does not apply regardless — I'm recording the breakdown so it's clear the size was measured, not skipped.

Approach: deriving the pnpm lockfile from the npm one is the right shape. It removes the hand-kept overrides that were the actual failure mode, and it makes the second gate compose correctly — because both trees now come from one source, "every npm install script has an explicit allowBuilds decision" is a check that can actually stay true. The packageDir override for web-templates is necessary rather than defensive: src/export-html/package.json and src/insight/package.json genuinely carry only type and a build script, so the default upward walk would flag every import under them.

One honest question, not a blocker: this is three changes — the lockfile gates, the undeclared-import rule plus its 15 declarations, and the Stage 2 verification brief. Each is defensible and none is risky, but the lint rule in particular is an independent win that would have been easy to land and review on its own. Worth keeping in mind for next time rather than reworking now.

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

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ —— 每个标题都填了,Tested-on 表格是真实填写的而不是空表,中文翻译也是逐段完整的。

问题: 是已观测到的,不是理论性的。#10444 上 Stage 1 之后的复盘测量到,bootstrap 合入三天后两个 lockfile 就已经漂移。我没有直接采信这些数字,而是自己复现了这类漂移:把 main 的 pnpm-lock.yaml 与本分支的 package-lock.json 对照,得到 19 个 npm 从未锁定的 pnpm 解析版本;去掉有说明的 mime-db@1.52.0 例外后正好是 18 个。这与 PR 描述一致,包括它点名的四个——@typescript-eslint/*@8.53.1@vitest/browser@3.2.7minimatch@10.2.6diff@8.0.4

方向: 对齐。这是在加固已经合入的 Stage 1(perf(dev): add pnpm worktree bootstrap foundation#10449),而不是继续扩张它,并且保持了 npm 的权威地位——这个门只断言 pnpm worktree 不会运行 CI 没有安装过的版本。它不涉及 auth、sandbox、模型选择、telemetry、发布或公开契约:我把 15 处新增 manifest 声明逐一对照了锁定树,没有任何一处改变已解析的版本,因此不可能影响到发布出去的 CLI。外部 CHANGELOG 没有对应项,这对内部构建工具是正常的;本仓库自己的历史里有 lockfile 一致性工作的先例(#8858#1659)。

规模: 没有触及核心源码路径——packages/core/package.json 是 manifest,不是 packages/core/src/**。生产/工具逻辑约 174 行(scripts/check-lockfile.js 105、eslint.config.js 48、三个 package manifest 15、pnpm workspace 配置 4、smoke 触发条件 2)。其余是 1497 行重新生成的 lockfile、111 行文档、6 行测试。远低于 500 行阈值,因此也不触发大 PR 提示。另外你在本仓库有 admin 权限,所以两层核心门本来就不适用——我把明细写出来是为了说明规模是被测量过的,而不是被跳过的。

方案: 让 pnpm lockfile 从 npm lockfile 派生,这个思路是对的。它去掉了真正的失效来源(手工维护的 overrides),也让第二个门能够正确组合——因为两棵树现在同源,"npm 的每个 install 脚本都有一条显式 allowBuilds 决定" 才是一个能持续成立的检查。web-templates 的 packageDir 覆盖是必需的,不是防御性写法:src/export-html/package.jsonsrc/insight/package.json 确实只带 type 和一个 build 脚本,所以默认的向上查找会把它们下面的每个 import 都判成未声明。

一个诚恳的疑问,不是阻塞项:这里其实是三件事——lockfile 门、未声明 import 规则及其 15 处声明、Stage 2 验证说明。每一件都站得住,也都没有风险,但特别是那条 lint 规则,它是一个独立的收益,本来可以单独提交、单独 review。这点值得下次留意,现在不必返工。

风险: 无升级风险信号——改动的文件都不匹配与 revert 相关的路径集合。

进入代码审查 🔍

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

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

I read the two lockfiles at this head directly and recomputed both new gates myself rather than reasoning about the script — so the pass/fail claims below are measurements, not a reading of the author's intent.

Both gates do what they say. The version gate compares 1966 pnpm package keys against the 2012 name@version pairs npm locks; exactly one is unmatched, mime-db@1.52.0, which is the declared exception. The staleness half holds too — the gap is still present in the pnpm lockfile and still absent from npm's — so it will fail loudly when it stops describing reality instead of rotting silently. The allowBuilds gate also checks out: npm flags exactly seven packages with an install script (@google/genai, @vscode/vsce-sign, esbuild, fsevents, keytar, msw, protobufjs) and all seven have a decision. Both failure cases in the test plan reproduce statically — main's pnpm lockfile against this branch's npm lockfile gives 19 unmatched versions (18 real), and dropping the fsevents entry gives exactly one undecided name.

The 15 new declarations change no resolved version. This was the thing most likely to be subtly wrong, so I checked all of them against the locked tree: semver ^7.7.2 → root 7.8.5, chalk ^4.1.2 → 4.1.2, open ^10.2.0 → 10.2.0, ansi-escapes ^7.3.0 → 7.3.0, lucide-react ^1.24.0 → 1.24.0, zod ^3.25.76 → root 3.25.76 (the 4.4.3 copies are nested under @modelcontextprotocol/* and packages/mobile-mcp and are untouched). The seven @opentelemetry/* additions are the interesting case — six of them are not at the root, they already sit at packages/core/node_modules/@opentelemetry/* at exactly 0.221.0 / 2.10.0, with semantic-conventions hoisted at root 1.36.0. So npm was already resolving these for core; the declarations make an existing resolution explicit. That's why package-lock.json gains only manifest-mirror lines and no tree entries, and it's the strongest evidence that the published CLI is unaffected.

The ESLint wiring is correct. Both new blocks reuse the exact files globs of the existing general block that registers the import plugin and its resolver setting, and they're inserted after it — so the plugin resolves, and the web-templates packageDir override wins by flat-config ordering. no-extraneous-dependencies is not part of the plugin's recommended set, so this is a genuinely new check rather than a re-enable of something already on.

Three things I'd note, none of them blocking:

  • The gate is set membership, not resolution identity. It guarantees a pnpm worktree never runs a version npm hasn't locked; it does not guarantee it runs the same version. The description discloses 13 remaining direct-dep differences, and I confirmed the shape of that with esbuild: npm's root is 0.25.6, pnpm's root importer resolves ^0.25.0 to 0.25.12, and 0.25.6 does not appear in the pnpm lockfile at all. Both versions are npm-locked, so the gate passes while a pnpm worktree bundles with a different esbuild than CI does. It's bounded by npm staying authoritative for build, packaging and release, and it's precisely what the brief's hoisted-parity measurement exists to settle — but the code comment frames 0.25.12 as benign deduplication, where the more useful framing is that it's a real, accepted parity gap.
  • fsevents: false is the one place this PR decides something rather than records it. The claim that pnpm already skipped it holds: the macOS smoke log from main before this change (run 34557626433) shows no fsevents build and no ignored-build warning, consistent with pnpm's default-deny for unapproved scripts, and this PR's own macOS log lists the scripts pnpm did run — protobufjs, three copies of esbuild, msw, @vscode/vsce-sign and keytar — with fsevents absent, which is positive confirmation rather than just a missing warning. What isn't spelled out is the consequence — fsevents is in the pnpm tree (2.3.2 and 2.3.3), pulled in as an optional dependency by vite, tsx, rollup, playwright and a chokidar copy nested under tailwindcss, so on a macOS pnpm worktree those lose the native FSEvents binding and fall back to fs.watch. All of them are build and test tooling rather than shipped runtime, which keeps the impact small for an opt-in, install-only layout; one clause in that comment would save the next reader the derivation.
  • devDependencies: true on shipped sources means a src/** file may import a root devDependency and pass. That's the right trade for the stated goal — catching imports that resolve through hoisting alone, i.e. packages in no applicable manifest — just noting the rule is narrower than "shipped code depends only on dependencies".

No critical blockers, and no AGENTS.md violations: the script extends an existing file instead of adding a parallel one, the verification brief goes into docs/verification/<topic>/README.md alongside four existing briefs of the same shape, and the AGENTS.md edit is required rather than drive-by, since it documented the regeneration command this PR makes wrong.

Files changed (12)
File What changed
scripts/check-lockfile.js The two new gates: every pnpm version must be npm-locked (one documented exception, which also fails when it goes stale), and every npm install script must have an allowBuilds decision.
pnpm-lock.yaml Regenerated from the npm lockfile; drops 18 versions pnpm had resolved on its own. Bulk of the diff.
package-lock.json Manifest-mirror lines only, no tree entries — the evidence the declarations change no resolution.
eslint.config.js Turns on the undeclared-import rule for shipped sources, with a second block pointing web-templates at its own manifest.
packages/core/package.json Declares zod and seven OpenTelemetry packages its sources already imported.
packages/cli/package.json Declares ansi-escapes, chalk, open and semver.
packages/vscode-ide-companion/package.json Declares lucide-react.
pnpm-workspace.yaml Adds the fsevents deny entry that makes the build-decision gate pass.
scripts/tests/package-scripts.test.js Asserts the two new success lines, so a silently-skipped gate fails the suite.
.github/workflows/pnpm-worktree-smoke.yml Also triggers on npm lockfile changes, now that the pnpm lockfile derives from it.
AGENTS.md Replaces the now-wrong regeneration command and adds the ext4 disk and prepare-build caveats.
docs/verification/pnpm-stage2-evidence/README.md New brief handing off the two Stage 2 measurements; docs only.

Testing

This is an unattended CI run, so nothing was built or executed here — and per the review rules I did not run the PR's own script either. The gate results above come from recomputing both checks against the lockfiles fetched at this commit. Real evidence is the smoke workflow, which is the one thing that actually exercises the regenerated lockfile:

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

Check Conclusion
Lint & Static (ubuntu-latest, Node 22.x) ❌ failure
Test (ubuntu-latest, Node 22.x) ❌ failure
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Install (macos-latest) ✅ success
Install (ubuntu-latest) ✅ success
Install (windows-latest) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
macos-latest / Java 21 ✅ success
OpenTUI no-flicker gate ✅ success
Real daemon E2E / Java 11 ✅ success
TUI parity snapshots (ink vs opentui) ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
windows-latest / Java 21 ✅ success

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

Read that with one caveat in mind: the three green Install jobs are the meaningful signal here, because they run node scripts/setup-worktree.js, and the macOS log shows pnpm printing "Lockfile is up to date, resolution step is skipped" and then "Lockfile passes supply-chain policies (1966 entries in 6.1s)" — it accepted the regenerated lockfile as-is instead of re-resolving it, which is precisely what would fail if that lockfile were inconsistent with the manifests. The 1966 also matches the package-key count I counted independently, a useful cross-check. One thing in those logs that is not this PR: the first, offline attempt fails with ERR_PNPM_NO_OFFLINE_META on @agentclientprotocol/sdk and the script retries with registry access. The identical failure appears in main's pre-change smoke run (34557626433), so it is a pre-existing cold-cache path rather than a regression from the regenerated lockfile — naming it so nobody mistakes it for one. But the job that runs npm run check:lockfileLint & Static (ubuntu-latest, Node 22.x), step "Check lockfile" — is still in progress, as is the unit job carrying the updated package-scripts test. So CI has not yet confirmed the new gates; my recomputation is the only evidence for them so far, and it says they pass. No check is red. Nothing was polled or waited on.

Not verified, and why:

  • That corepack pnpm import reproduces the committed lockfile byte-identically. Author's claim (two consecutive runs, ~51 s each, Linux / Node 22.23.2), not independently re-run — it cannot be from a static review, since it needs pnpm against the registry.
  • The type-only-import exemption asserted in the ESLint comment. node_modules is not installed in this checkout so I could not read the rule's implementation. Not load-bearing for lint passing, since @types/* packages are devDependencies and those are allowed regardless — but the comment states it as fact.
  • Lint and unit test results. Both jobs are still running; the author also notes their local ESLint run used a node_modules older than main and defers to CI, which is the right call.

Sandboxed verification would settle the one claim nothing currently covers: @qwen-code /verify — that corepack pnpm import regenerates the committed pnpm-lock.yaml byte-identically is now the documented contributor workflow in AGENTS.md, and neither the diff nor the smoke workflow exercises it, because smoke installs the committed lockfile and never regenerates it. You have write access, so this would be a direct run rather than a sponsored one.

中文说明

代码审查

我直接读取了这个 head 上的两个 lockfile,自己重新算了两道新门,而不是去推断脚本的意图——所以下面的通过/失败结论是测量结果,不是对作者意图的解读。

两道门都名副其实。 版本门把 1966 个 pnpm 包 key 与 npm 锁定的 2012 个 name@version 对照,只有一个不匹配,即 mime-db@1.52.0,正是声明的例外。失效检测那一半也成立——这个缺口在 pnpm lockfile 里仍然存在、在 npm 里仍然缺失——所以当它不再描述真实缺口时会大声失败,而不是悄悄腐烂。allowBuilds 门同样核对通过:npm 标记出恰好七个带 install 脚本的包(@google/genai@vscode/vsce-signesbuildfseventskeytarmswprotobufjs),七个都有决定。测试计划里的两种失败情形也能静态复现——把 main 的 pnpm lockfile 对照本分支的 npm lockfile,得到 19 个未匹配版本(实际 18 个);删掉 fsevents 那一条,得到恰好一个未决定的名字。

15 处新增声明没有改变任何已解析的版本。 这是最容易出现微妙错误的地方,所以我逐一对照了锁定树:semver ^7.7.2 → 根目录 7.8.5,chalk ^4.1.2 → 4.1.2,open ^10.2.0 → 10.2.0,ansi-escapes ^7.3.0 → 7.3.0,lucide-react ^1.24.0 → 1.24.0,zod ^3.25.76 → 根目录 3.25.76(4.4.3 的副本嵌套在 @modelcontextprotocol/*packages/mobile-mcp 下,未被触及)。七个 @opentelemetry/* 是有意思的情形——其中六个不在根目录,它们本来就在 packages/core/node_modules/@opentelemetry/*,版本恰好是 0.221.0 / 2.10.0,而 semantic-conventions 提升到根目录 1.36.0。也就是说 npm 早就在为 core 解析这些包,声明只是把已有的解析显式化。这也解释了为什么 package-lock.json 只增加了 manifest 镜像行、没有任何树条目,并且是"发布出去的 CLI 不受影响"最有力的证据。

ESLint 接线是正确的。 两个新 block 复用了已有通用 block 完全相同的 files glob——正是那个 block 注册了 import 插件及其 resolver 设置——并且插在它后面,所以插件能解析到,web-templates 的 packageDir 覆盖也按 flat config 的顺序生效。no-extraneous-dependencies 不在该插件的 recommended 集合里,所以这是一条真正新增的检查,而不是重新打开已有规则。

有三点想指出,都不是阻塞项:

  • 这道门是集合成员判断,不是解析一致性判断。 它保证 pnpm worktree 不会运行 npm 没锁定的版本;但不保证运行的是同一个版本。描述里披露还剩 13 处直接依赖版本不同,我用 esbuild 确认了这个形态:npm 根目录是 0.25.6,pnpm 的根 importer 把 ^0.25.0 解析到 0.25.12,而 0.25.6 在 pnpm lockfile 里根本不出现。两个版本 npm 都锁定了,所以门通过,但 pnpm worktree 打包用的 esbuild 与 CI 不同。它的影响范围受限于 npm 仍然是构建、打包、发布的权威路径,而这正是验证说明里 hoisted parity 测量要去解决的问题——但代码注释把 0.25.12 说成是无害的去重,更有用的说法是:这是一个真实存在、被接受的一致性缺口。
  • fsevents: false 是本 PR 唯一一处做决定而非记录事实的地方。 "pnpm 本来就跳过它"这个说法成立:改动前 main 上的 macOS smoke 日志(run 34557626433)里没有 fsevents 构建、也没有 ignored-build 警告,与 pnpm 对未批准脚本默认拒绝的行为一致;而且本 PR 自己的 macOS 日志列出了 pnpm 实际运行的脚本——protobufjs、三份 esbuild、msw、@vscode/vsce-sign 和 keytar——其中没有 fsevents,这是正面确认,而不只是缺少一条警告。没有写出来的是后果——fsevents 确实在 pnpm 树里(2.3.2 和 2.3.3),由 vitetsxrollupplaywright 以及嵌套在 tailwindcss 下的一份 chokidar 作为可选依赖引入,所以在 macOS 的 pnpm worktree 上,它们会失去原生的 FSEvents 绑定、退回 fs.watch。这些都是构建与测试工具,不是发布出去的运行时代码,所以对一个 opt-in、仅安装的布局来说影响很小;在那条注释里加一句话,可以省掉下一个读者的推导。
  • 对会发布的源码开启 devDependencies: true,意味着 src/** 里的文件 import 根目录的 devDependency 也能通过。对于既定目标这是正确的取舍——目标是抓出仅靠 hoisting 才能解析的 import,也就是任何相关 manifest 里都没有的包——只是说明这条规则比"发布代码只依赖 dependencies"要窄。

没有严重阻塞项,也没有违反 AGENTS.md 的地方:脚本是扩展已有文件而不是新加一个平行的;验证说明放进 docs/verification/<topic>/README.md,与已有的四份同形态说明并列;AGENTS.md 的修改是必需的而不是顺手改的,因为它写的正是本 PR 使之失效的再生成命令。

测试

这是一次无人值守的 CI 运行,所以这里没有构建或执行任何东西——按照 review 规则,我也没有运行 PR 自己的脚本。上面的门结果来自用该 commit 上取到的 lockfile 重新计算。真实证据是 smoke workflow,它是唯一真正验证了重新生成的 lockfile 的东西。

看这张表时请注意一点:三个绿色的 Install 任务才是这里的关键信号,因为它们运行的是 node scripts/setup-worktree.js,而 macOS 日志里 pnpm 打印了 "Lockfile is up to date, resolution step is skipped",随后是 "Lockfile passes supply-chain policies (1966 entries in 6.1s)"——它直接接受了重新生成的 lockfile,没有重新解析,而这正是"lockfile 与 manifest 不一致"时会失败的地方。那个 1966 也与我独立数出的包 key 数量一致,是一个有用的交叉验证。日志里有一件事不是本 PR 造成的:第一次离线尝试以 ERR_PNPM_NO_OFFLINE_META@agentclientprotocol/sdk)失败,脚本随后带 registry 访问重试。改动前 main 的 smoke 运行(34557626433)里有一模一样的失败,所以那是既有的冷缓存路径,不是重新生成 lockfile 带来的回归——把它点出来,免得有人误判。但运行 npm run check:lockfile 的那个任务——Lint & Static (ubuntu-latest, Node 22.x),步骤名 "Check lockfile"——仍在进行中,带着更新后 package-scripts 测试的单测任务也是。所以 CI 还没有确认这两道新门;目前唯一的证据是我的重算,结论是通过。没有红色检查。没有轮询或等待。

未验证的部分及原因:

  • corepack pnpm import 是否逐字节复现已提交的 lockfile。 这是作者的说法(连续两次运行,各约 51 秒,Linux / Node 22.23.2),没有独立重跑——静态 review 也做不到,因为它需要 pnpm 访问 registry。
  • ESLint 注释里断言的纯类型 import 豁免。 这个 checkout 里没有安装 node_modules,我无法读到规则实现。它对 lint 通过不是关键,因为 @types/* 是 devDependencies,本来就被允许——但注释是把它当事实陈述的。
  • lint 与单测结果。 两个任务都还在跑;作者也说明本地 ESLint 用的 node_modules 比 main 旧、以 CI 为准,这个判断是对的。

沙箱验证可以解决目前唯一没有任何覆盖的说法:@qwen-code /verify —— "用 corepack pnpm import 能逐字节重新生成已提交的 pnpm-lock.yaml" 现在已经写进 AGENTS.md 成为贡献者流程,但 diff 和 smoke workflow 都没有验证它,因为 smoke 是安装已提交的 lockfile,从不重新生成。你有写权限,所以这会是一次直接运行,而不是需要维护者背书的运行。

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

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — the central claims held up when I tried to break them; the nits are real but none of them is a reason to hold this back.

Going in, my own answer to "the two lockfiles drifted and hand-kept overrides can't keep up" was the same shape as this: stop resolving the pnpm lockfile independently, derive it from the npm one, and assert in CI that the pnpm set is a subset of the npm set. So the approach isn't just acceptable, it's the one I'd have proposed. It beats my version in two places I wouldn't have thought of. The allowBuilds gate closes a different silent-divergence channel — install scripts npm runs and pnpm doesn't — and it only stays tractable because the derivation makes both trees come from one source. And the exception list is self-policing: mime-db@1.52.0 fails the check the moment it stops describing a real gap, which is more discipline than I'd have written into a first pass.

The simpler alternative I considered and rejected was deleting the pnpm bootstrap outright. It's genuinely less code, but #10444 is still open specifically for a Stage 2 go/no-go, and ripping out a merged Stage 1 would preempt a decision the maintainer reserved. This PR keeps that decision open while removing the reason the interim state was unsafe.

What I'd want a reader to take away is that I did not take the description's word for anything. I recomputed both gates from the lockfiles at this commit: the version gate leaves exactly one unmatched entry out of 1966, and it's the declared exception; the build gate covers all seven packages npm flags. Both documented failure cases reproduce. All 15 new manifest declarations match versions already locked, including the six OpenTelemetry packages that turn out to have been nested under packages/core/node_modules all along — which is why the npm lockfile gains no tree entries and why the published CLI is untouched.

Three reservations, in the order I'd weight them:

  1. CI has not confirmed the new gates yet. The job that runs npm run check:lockfile is still in progress, as is the unit job carrying the updated test. My recomputation is static, so it says the gates should print their two success lines — it doesn't say the script exits 0 in CI. The green Install jobs on all three OSes are the real evidence so far, and they cover the part most likely to break.
  2. The reproducibility of corepack pnpm import is the load-bearing unverified claim. It's now the documented contributor workflow, and if it isn't actually reproducible, every future dependency PR produces a lockfile this gate rejects and contributors get stuck between two commands. It fails safe — the error message tells you to re-run the import — so it isn't a blocker, but it's the one thing I'd settle before this becomes process. /verify would close it.
  3. Three concerns in one PR, as raised in Stage 1. All low-risk, and reworking it now would cost more than it saves.

The set-membership limitation is worth restating once here because it's the thing most likely to be over-trusted later: this gate proves a pnpm worktree never runs a version CI hasn't locked, not that it runs the same one. esbuild is the concrete case — 0.25.6 under npm at the root, 0.25.12 under pnpm, and 0.25.6 absent from the pnpm lockfile entirely. The author discloses this, it's bounded by npm staying authoritative for build and release, and the brief already names the measurement that would retire the question. Disclosed-and-bounded is the right disposition, not a defect.

Six months from now this reads as a thank-you, not a curse: 105 lines of set arithmetic appended to an existing script, no new abstraction, no config surface, and it converts "nobody noticed the drift for three days" into a red check. One note for the record rather than as a concern — a maintainer's follow-up to their own Stage 1 and their own issue is exactly the shape where a gate should not relax, so I verified the claims instead of accepting them; they happened to hold.

Approving, with the approval deferred until CI lands green on efdd8027ec5f1148a7cca32b884c14591bca7ce2 — the deferred-approval path exists precisely so I don't attest to a suite that hasn't finished. Nothing is red right now; if anything lands red or the head moves, the approval is withheld and the status comment says so.

中文说明

Confidence: 4/5 —— 我试着推翻它的核心论断,结果都站得住;那些小问题是真实存在的,但没有一个构成拦下它的理由。

一开始,我对"两个 lockfile 漂移了、手工维护的 overrides 跟不上"这个问题给出的答案,与这个 PR 是同一个形态:不要再独立解析 pnpm lockfile,而是从 npm 的那份派生,并在 CI 里断言 pnpm 集合是 npm 集合的子集。所以这个方案不只是可接受,它就是我本来会提的方案。它在两个我不会想到的地方比我的版本更好:allowBuilds 门关闭了另一条静默分叉的通道——npm 会运行、pnpm 不会运行的 install 脚本——而它之所以可维护,正是因为派生让两棵树同源。另外那份例外清单是自我监管的:mime-db@1.52.0 一旦不再描述真实缺口就会让检查失败,这比我第一轮会写出的东西更有纪律。

我考虑过但否掉的更简方案是直接删掉 pnpm bootstrap。它确实代码更少,但 #10444 仍然开着,就是为了 Stage 2 的 go/no-go 决定;把已经合入的 Stage 1 拆掉,等于抢在维护者保留的那个决定之前行动。这个 PR 让那个决定继续悬着,同时消除了这段中间状态之所以不安全的原因。

我希望读者带走的一点是:描述里的任何说法我都没有直接采信。我用这个 commit 上的 lockfile 重算了两道门:版本门在 1966 项里只留下一项未匹配,而它正是声明的例外;构建门覆盖了 npm 标记出的全部七个包。两种文档化的失败情形都能复现。15 处新增 manifest 声明全部对应已锁定的版本,包括那六个 OpenTelemetry 包——它们其实一直嵌套在 packages/core/node_modules 下面——这也解释了为什么 npm lockfile 没有增加任何树条目,以及为什么发布出去的 CLI 不受影响。

三点保留意见,按我的权重排序:

  1. CI 还没有确认这两道新门。 运行 npm run check:lockfile 的任务仍在进行,带着更新后测试的单测任务也是。我的重算是静态的,所以它说明这两道门应该打印出那两行成功信息——但没有说明脚本在 CI 里退出码是 0。目前真正的证据是三个操作系统上都绿色的 Install 任务,它们覆盖了最可能崩掉的那部分。
  2. corepack pnpm import 的可复现性是最关键的未验证论断。 它现在是文档化的贡献者流程;如果它实际上不可复现,那么今后每个改依赖的 PR 都会产出一个被这道门拒绝的 lockfile,贡献者会卡在两条命令之间。它是安全失败的——报错信息会告诉你要重跑 import——所以不是阻塞项,但这是我会想在它变成流程之前先解决的一件事。/verify 可以关掉它。
  3. 一个 PR 里三件事,如 Stage 1 所提。都没有风险,而且现在返工的代价大于收益。

集合成员判断这个局限值得在这里再说一次,因为它最可能在以后被过度信任:这道门证明的是 pnpm worktree 不会运行 CI 没锁定的版本,而不是运行的是同一个版本。esbuild 是具体例子——npm 根目录 0.25.6,pnpm 0.25.12,而 0.25.6 在 pnpm lockfile 里完全不出现。作者披露了这一点,它的影响范围受限于 npm 仍是构建与发布的权威路径,而且验证说明里已经点名了能了结这个问题的测量。"已披露且有边界"是正确的处置方式,不是缺陷。

六个月后再看,这是一份让人道谢而不是骂人的改动:105 行集合运算追加到已有脚本里,没有新抽象,没有配置面,并且把"漂移了三天没人发现"变成一条红色检查。有一点写进记录、但不是顾虑——维护者跟进自己的 Stage 1、自己的 issue,正是门不应该放松的形态,所以我去验证了这些论断而不是接受它们;结果它们成立。

同意合入,但批准推迟到 CI 在 efdd8027ec5f1148a7cca32b884c14591bca7ce2 上全绿之后——推迟批准这条路径的存在,正是为了不让我为一个还没跑完的测试套件背书。目前没有任何红色检查;如果有检查变红或 head 移动,批准会被撤回,状态评论里会说明。

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

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

- NOTICES.txt: core's new OpenTelemetry declarations make the generator
  visit six Apache-2.0 entries in a different order. Same 655 entries,
  same text; only their order changes (6 header lines).
- package-scripts test: expect package-lock.json in the pnpm smoke paths.

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Agent-assisted review at 0c67d4872a0c60b8f6d29918f28170a6a72ad0a5 — no confirmed Critical; full current tooling/manifest/lockfile scope reviewed.

Independent data validation against this head (Python 3 + preinstalled PyYAML 6.0.1, not execution of PR code) found 2,012 unique npm name/version pairs and 1,966 pnpm package entries. The only pnpm version absent from npm is the documented mime-db@1.52.0 exception. All seven npm install-script package names have an allowBuilds decision. All 13 newly explicit dependency declarations match their npm workspace lock entries; their current npm resolution and pnpm importer version agree, including core's nested OpenTelemetry resolutions. I traced the corresponding runtime imports and reviewed the ESLint block ordering/web-templates package boundary, smoke workflow path additions, notices diff, bootstrap guidance and verification brief.

The gate's limit matters: scripts/check-lockfile.js:149-176 checks set membership, not per-importer resolution or whole dependency-graph equivalence. The allowBuilds check at :193-207 checks package names, not matching version-qualified approvals. Likewise devDependencies: true does not certify that a separately published package has every runtime dependency. These are scope limits, not proof of a current broken install; Stage 2 build/test parity remains unverified as the brief says.

Suggestion: scripts/tests/package-scripts.test.js:156-169 only checks today's successful lockfiles and log lines. Add small negative cases for an npm-unlocked pnpm version, a stale exception and an undecided install script. Removing the corresponding failure checks should fail those tests; successful current-data validation alone cannot protect those branches.

Prior history contains no standing Critical to close. This is tooling/manifests rather than a large core refactor; broad package impact warrants maintainer awareness, not a size-based rejection. Maintainer authorship was not independently verified.

Testing: the independent read-only lock/manifest validation above passed. No PR script, ESLint, package tests, install, build, lock regeneration, or workflow was executed. In particular I did not follow the verification brief's workflow-dispatch instructions. Exact remote head/base and paginated current diff/history used. Comment only; no approval implied.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": pnpm install --frozen-lockfile was never executed (no pnpm binary and no registry access in this review worktree, and a real install would dirty the shared tr…; "agent reverse-audit (round 1)": I did not execute a real pnpm install --frozen-lockfile , so registry reachability of the 1966 recorded integrity values is inferred from their byte-equality w…; "agent reverse-audit (round 2)": verifying whether packages/sdk-typescript 's build bundles or externalizes @qwen-code/acp-bridge / @qwen-code/qwen-code-core (no esbuild config at the pack….

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

Not linted (tool limitation, not a blocker): .github/workflows/pnpm-worktree-smoke.yml — actionlint embedded-shell source mapping is not yet supported.

中文说明

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

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)"pnpm install --frozen-lockfile was never executed (no pnpm binary and no registry access in this review worktree, and a real install would dirty the shared tr…"agent reverse-audit (round 1)"I did not execute a real pnpm install --frozen-lockfile , so registry reachability of the 1966 recorded integrity values is inferred from their byte-equality w…"agent reverse-audit (round 2)"verifying whether packages/sdk-typescript 's build bundles or externalizes @qwen-code/acp-bridge / @qwen-code/qwen-code-core (no esbuild config at the pack…

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

未检查(工具限制,非阻断):.github/workflows/pnpm-worktree-smoke.yml——actionlint 对 workflow 内嵌 shell 的源映射尚未支持。

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

Comment thread pnpm-lock.yaml
Comment thread .github/workflows/pnpm-worktree-smoke.yml Outdated
Comment thread AGENTS.md
Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread scripts/check-lockfile.js
Comment thread scripts/check-lockfile.js
Comment thread scripts/check-lockfile.js
Comment thread scripts/tests/package-scripts.test.js
@wenshao

wenshao commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

@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 Sep 11, 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. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

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

中文说明

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

- fail closed when pnpm-lock.yaml has no packages section instead of
  printing the strongest success line over an unread graph
- match git/file: dependency keys against npm's resolved field, whose
  pnpm key is the source rather than a version
- count an allowBuilds entry only when its value is boolean, so pnpm's
  own 'set this to true or false' placeholder no longer passes the gate
- drop the inert package-lock.json trigger from the pnpm smoke workflow;
  no step in that job reads it, and npm/pnpm agreement is already gated
  by ci.yml's Check lockfile step on the full profile
- stage-2 evidence recipe: cover nested workspace roots in the reflink
  arm, sample peak worktree count at most 5 minutes apart, run the
  parity matrix in a throwaway workflow, and give the npm baseline the
  same skip guards the pnpm arm has
- drive every gate branch with fixture-based negative tests
@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 round — PR #11625

Commit 1e43a5b229 (fix(pnpm): close lockfile-gate gaps found in review) on chore/pnpm-lock-gates.

Dispositions

Implemented (8 inline findings + the maintainer review-body suggestion):

  • R1-2 (pnpm-worktree-smoke.yml:13): dropped package-lock.json from both smoke-workflow path filters and from expectedPaths. Verified the finding first: no step in the job reads it (setup-worktree.js contains zero package-lock occurrences; the install validates pnpm-lock.yaml against the workspace manifests), and a lockfile-only PR classifies as full, so ci.yml's Check lockfile step already gates npm/pnpm agreement there. A why-comment now sits beside expectedPaths so the entry is not re-added casually.
  • R1-4 (evidence README :50): the reflink arm now copies every workspace root's node_modules (packages/*, packages/channels/*, integrations/*), and step 4 gained a check that actually fails on an incomplete copy (require.resolve('@larksuiteoapi/node-sdk/package.json', { paths: ['packages/channels/feishu'] })), since the two pre-existing checks resolve through the root symlink regardless.
  • R1-5 (:59): peak worktree sampling is now at most 5 minutes apart with the maximum kept over the week, with a note that a daily sample reports the floor because review worktrees under .qwen/tmp live for under an hour.
  • R1-6 (:75): section 2 now creates a throwaway workflow_dispatch workflow (copy of the smoke workflow, single ubuntu-latest job, no matrix, timeout-minutes: 150) instead of editing the smoke workflow — which has no per-OS job, has a 20-minute ceiling the chain exceeds, and is pinned by not.toContain('npm run build').
  • R1-7 (:81): the npm baseline is now QWEN_SKIP_PREPARE=1 QWEN_SKIP_NOTICE_GENERATION=1 npm ci --prefer-offline — the same guard pair scripts/setup-worktree.js sets — in section 2 and, for consistency, in section 1's table (the vscode-ide-companion workspace prepare runs generate:notices and reads only the second guard).
  • R1-8 (check-lockfile.js:138): npm entries whose resolved starts with git or file: (the file's own pre-existing definition of a source dependency) now also contribute name@<resolved> to a separate npmLockedSources set, so a git/file dependency pnpm keys by source no longer lands in unlockedPnpmVersions with an unactionable remediation.
  • R1-9 (check-lockfile.js:154): the version-agreement gate now fails closed — an empty/missing packages section exits 1 with pnpm-lock.yaml has no packages section instead of printing pnpm lockfile matches package-lock.json. over an unread graph.
  • R1-11 (package-scripts.test.js:166) and the maintainer's review-body suggestion: the script's root is overridable via CHECK_LOCKFILE_ROOT, and seven fixture-driven tests now pin every detector branch — bogus pnpm version, stale knownNpmLockGaps exception, undecided install script, empty packages section, git-source acceptance, spec-scoped allowBuilds key (pins pnpmPackageName slicing, the reviewer's mutation (c)), and the placeholder case below.

Partially implemented (thread left open):

  • R1-10 (check-lockfile.js:193): the value half is implemented — allowBuilds entries count as decisions only when the value is boolean, so pnpm's own set this to true or false placeholder now fails the gate naming the package. The depPath-existence half is not: it rejects the committed '@qwen-code/qwen-code-core@file:packages/core': true entry (file:packages/core appears 0 times in pnpm-lock.yaml; workspace packages are link:), so it reds the healthy tree unless that entry is reshaped, which changes what pnpm approves on real installs. The suggested allowBuilds staleness detector was also not added for the same reason — the core entry's stripped name is a workspace package the npm scan skips, so the detector would flag it immediately. Details in the thread reply.

Escalated (maintainer decision needed, thread left open):

  • R1-1 (pnpm-lock.yaml:249): the @agentclientprotocol/sdk zod peer re-binding (3.25.76 → 4.4.3) changes ACP schema-validation error classification (-32603 → -32602) in pnpm worktrees vs the npm tree CI gates on. Two defensible directions: pin '@agentclientprotocol/sdk>zod': '3.25.76' tree-wide (restores npm parity, deliberately keeps the vendor's misclassification, needs an upstream report) or accept the spec-right divergence and document it. Recommendation posted on the thread: pin for parity, since install-time behavioral parity is this PR's purpose.

Deferred to follow-up:

  • R1-3 (AGENTS.md:99): the freshness half of the gate (corepack pnpm import && git diff --exit-code) needs registry metadata (~51 s cold), so it cannot join the smoke job's offline attempt; its home is a ci.yml lane this PR never touched, and a network-dependent step in the full-profile lane running on every non-docs PR is a CI reliability tradeoff for a maintainer. Recorded in deferred-findings.json. The trigger half of the finding is addressed by R1-2.

Mutation probes (all restored after measurement)

  • Empty-packages guard removed → fails closed when pnpm-lock.yaml has no packages section failed as required.
  • npmLockedSources acceptance removed → accepts a git dependency pnpm keys by source instead of version failed as required.
  • Boolean allowBuilds filter removed → ignores an allowBuilds placeholder that decides nothing failed as required.
  • package-lock.json re-added to the smoke trigger → runs the pnpm smoke workflow when a dependency input changes failed as required.

Verification

  • node scripts/check-lockfile.js — passed, exit 0, all four success lines on the committed tree.
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/package-scripts.test.js — passed: 47 passed, 1 skipped (pre-existing platform skip), including the 7 new fixture tests; re-run green on the committed state.
  • npm run build — passed.
  • npm run typecheck — passed.
  • npm run lint — passed.
  • npx prettier --check on all four touched files, and node scripts/lint.js --prettier — passed.
  • npm run test:scripts (full suite) — 2436 passed, 17 skipped, 1 failed: verify-capture.test.js > preserves colour and bold independently ("bold was dropped"). Environment failure, not from this diff: this sandbox has no fonts at all (fc-list returns 0 entries), so bold text renders pixel-identical to plain while the colour assertions in the same test pass; the file imports only the terminal-capture script and shares no code with this round's changes. The failure reproduces identically in isolation.
  • Not run: node scripts/lint.js --actionlint / --yamllint — the sandbox blocks their one-time install (mkdir ~/.cache permission denied, xz missing). The workflow change is a two-line list deletion and the file is parsed by the passing package-scripts tests.
  • Integration tests not run: the round touches no bundled-CLI behavior.
中文说明

Autofix 本轮处理 — PR #11625

chore/pnpm-lock-gates 上提交 1e43a5b229fix(pnpm): close lockfile-gate gaps found in review)。

处理结论

已实现(8 条行内发现 + 维护者 review 正文建议):

  • R1-2pnpm-worktree-smoke.yml:13):从 smoke workflow 的两个路径过滤器和 expectedPaths 中删除 package-lock.json。已先验证发现:该 job 没有任何步骤读取它(setup-worktree.jspackage-lock 出现 0 次;安装只用 pnpm-lock.yaml 与 workspace manifests 校验),且仅改 lockfile 的 PR 分类为 full,因此 ci.yml 的 Check lockfile 步骤在那里已经门禁了 npm/pnpm 一致性。expectedPaths 旁新增了一条 why 注释,防止该条目被随意加回。
  • R1-4(证据 README :50):reflink 档现在复制每个 workspace 根的 node_modulespackages/*packages/channels/*integrations/*),第 4 步新增了一个在拷贝不完整时真的会失败的检查(require.resolve('@larksuiteoapi/node-sdk/package.json', { paths: ['packages/channels/feishu'] })),因为原有检查经根符号链接解析,无论如何都会通过。
  • R1-5:59):峰值 worktree 采样改为至多 5 分钟一次并记录一周内最大值,并注明每天采一次报的是下限,因为 .qwen/tmp 下的评审 worktree 存活不足一小时。
  • R1-6:75):第 2 节现在改为创建一次性 workflow_dispatch workflow(复制 smoke workflow、单个 ubuntu-latest job、去掉矩阵、timeout-minutes: 150),而不是编辑 smoke workflow——后者没有按操作系统的 job、20 分钟超时放不下这条命令链、且被 not.toContain('npm run build') 钉住。
  • R1-7:81):npm 基线改为 QWEN_SKIP_PREPARE=1 QWEN_SKIP_NOTICE_GENERATION=1 npm ci --prefer-offline——与 scripts/setup-worktree.js 设置的同一对保护变量——在第 2 节修改,并为一致性同步修改了第 1 节表格(vscode-ide-companion workspace 的 prepare 会运行 generate:notices,而它只读第二个保护变量)。
  • R1-8check-lockfile.js:138):resolvedgitfile: 开头的 npm 条目(即本文件已有的源依赖定义)现在额外向独立的 npmLockedSources 集合贡献 name@<resolved>,因此 pnpm 按源建键的 git/file 依赖不会再落入 unlockedPnpmVersions 并配上无法执行的补救提示。
  • R1-9check-lockfile.js:154):版本一致性门禁现在失败即拦截——packages 段落为空/缺失时退出 1 并报 pnpm-lock.yaml has no packages section,而不是在未读到依赖图的情况下打印 pnpm lockfile matches package-lock.json.
  • R1-11package-scripts.test.js:166)及维护者 review 正文建议:脚本根目录可用 CHECK_LOCKFILE_ROOT 覆盖,并新增七个 fixture 驱动的测试钉住每个检测器分支——伪造 pnpm 版本、过期的 knownNpmLockGaps 豁免、未决安装脚本、空 packages 段落、git 源依赖接受、带 spec 的 allowBuilds 键(钉住 pnpmPackageName 的切分,即评审者的变异 (c)),以及下面的占位值用例。

部分实现(讨论串保持打开):

  • R1-10check-lockfile.js:193):值那一半已实现——allowBuilds 条目只有在值为布尔时才计为决定,因此 pnpm 自己写入的 set this to true or false 占位值现在会让门禁失败并点名对应包。depPath 存在性那一半未实现:它会拒绝已提交的 '@qwen-code/qwen-code-core@file:packages/core': true 条目(file:packages/corepnpm-lock.yaml 中出现 0 次;workspace 包记录为 link:),所以该检查会让健康树变红,除非改写该条目形状,而这会改变真实安装中 pnpm 批准的内容。建议的 allowBuilds 过期检测器也未添加,原因相同——core 条目削出的名字是 npm 扫描跳过的 workspace 包,检测器会立即标记它。详见讨论串回复。

已升级(需维护者决策,讨论串保持打开):

  • R1-1pnpm-lock.yaml:249):@agentclientprotocol/sdk 的 zod peer 重新绑定(3.25.76 → 4.4.3)使 pnpm worktree 的 ACP schema 校验错误分类(-32603 → -32602)与 CI 所依据的 npm 树不同。两个站得住的方向:整树 pin '@agentclientprotocol/sdk>zod': '3.25.76'(恢复 npm 一致性,但刻意保留上游的误分类,需要同时提上游 issue),或接受这个符合规范的差异并写入文档。已在讨论串给出建议:为一致性做 pin,因为安装时行为一致正是本 PR 的目的。

已推迟到后续跟进:

  • R1-3AGENTS.md:99):门禁的新鲜度那一半(corepack pnpm import && git diff --exit-code)需要 registry 元数据(冷缓存约 51 秒),无法放进 smoke job 的离线首次尝试;它的归属是本 PR 从未触及的 ci.yml lane,而在每个非文档 PR 都运行的 full-profile lane 里加网络依赖步骤属于 CI 可靠性权衡,应交维护者决定。已记录在 deferred-findings.json。该发现的触发器那一半已由 R1-2 处理。

变异探针(测量后均已还原)

  • 删除空 packages 守卫 → fails closed when pnpm-lock.yaml has no packages section 按预期变红
  • 删除 npmLockedSources 接受逻辑 → accepts a git dependency pnpm keys by source instead of version 按预期变红
  • 删除 allowBuilds 布尔过滤 → ignores an allowBuilds placeholder that decides nothing 按预期变红
  • package-lock.json 加回 smoke 触发器 → runs the pnpm smoke workflow when a dependency input changes 按预期变红

验证

  • node scripts/check-lockfile.js——通过,退出 0,已提交树上四行成功信息齐全。
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/package-scripts.test.js——通过:47 通过、1 跳过(既有平台跳过),含 7 个新 fixture 测试;在已提交状态上重跑仍为绿。
  • npm run build——通过。
  • npm run typecheck——通过。
  • npm run lint——通过。
  • 对四个改动文件执行 npx prettier --check,以及 node scripts/lint.js --prettier——通过。
  • npm run test:scripts(完整套件)——2436 通过、17 跳过、1 失败verify-capture.test.js > preserves colour and bold independently("bold was dropped")。属环境失败而非本次改动:本沙箱完全没有任何字体(fc-list 返回 0 条),粗体与常规渲染逐像素相同,而同测试中的颜色断言均通过;该文件只引用终端截屏脚本,与本轮改动无任何共享代码。单独重跑时失败完全一致。
  • 未运行:node scripts/lint.js --actionlint / --yamllint——沙箱阻止其一次性安装(mkdir ~/.cache 权限拒绝、缺少 xz)。workflow 改动仅删除两行列表项,且该文件已被通过的 package-scripts 测试解析。
  • 集成测试未运行:本轮不触及任何打包后 CLI 行为。

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

🧵 Resolved all 8 selected review thread(s). · 已关闭全部选中的 8 条评审线程。

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/模型 kimi-k3 · CLI 0.23.3

@wenshao

wenshao commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

PR #11625 — local verification on macOS

Two heads are covered, because AutoFix pushed 1e43a5b229 while this was running:

  • 1e43a5b229 (current head) — the gate matrix, the package-scripts suite, and a live
    test of the git-dependency branch that commit adds.
  • 0c67d4872a — the lint work, the hoisted-parity matrix, and the disk measurement.

The delta between the two touches only scripts/check-lockfile.js, its tests, the smoke
workflow's trigger list and the Stage 2 brief — no manifest, no lockfile, no shipped source —
so the second set carries over unchanged.

Everything ran on a real macOS/APFS host in isolated worktrees of this branch: one installed
with npm ci (1,979 packages, 4m00s), one with node scripts/setup-worktree.js (pnpm
11.24.0, the repo pin, 1m12s). macOS is the row the PR marks ⚠️, and the two measurements
docs/verification/pnpm-stage2-evidence/README.md hands off are the ones this host could
actually run, so both are reported below.

Environment: macOS 26.6.2 arm64 · Node v24.18.1 · npm 11.16.0 · corepack pnpm 11.24.0.
(CI's smoke job uses Node 22.x; nothing below depends on the runtime version.)

Verdict

Recommend merge, with one thing worth fixing first (F1) and a description line that is now
out of date (F2). Every claim in the test plan reproduces, including both documented failure
cases; the canaries I added confirm the gates fail for the right reason rather than passing by
construction; and the three gaps 1e43a5b229 closes are real closures, not paper ones — except
the git-dependency one, which does not fire against a real git dependency.

CI right now

Lint & Static is red on 1e43a5b229, and the reason is outside this diff: it fails at
Check lint gate freshness, before install. main moved eslint.legacy-filenames.mjs at
ae78d5b800 (#11621) and this branch has not incorporated it yet, which is exactly what that
check exists to catch. Merging origin/main clears it. The branch's own lint is green locally on
both trees (§3), and the gate passes against today's main package-lock.json (M6), so nothing
here suggests a second problem hiding behind it.

1. The gates — 9 cases, run live at 1e43a5b229

gate matrix

case mutation expected actual
M0 none: head as committed pass pass — the four promised lines
M1 main's pnpm-lock.yaml put back fail fail — 18 unlocked versions, the exact list in the PR body
M2 fsevents deleted from allowBuilds fail fail — names fsevents
M3 canary: one fabricated version in pnpm-lock.yaml fail fail — names fake-canary-package@9.9.9
M4 canary: npm starts locking mime-db@1.52.0 fail fail — "remove these entries from knownNpmLockGaps"
M5 canary: a new install-script dependency lands fail fail — names canary-native-thing
M6 today's origin/main package-lock.json (post-merge) pass pass
M7 canary: pnpm-lock.yaml with no packages: section fail fail — "has no packages section" (new in 1e43a5b2)
M8 canary: allowBuilds placeholder string instead of a boolean fail fail — names esbuild (new in 1e43a5b2)

M3–M5 matter because "M0 passes, M1 fails" is also what a gate that only consults one hard-coded
list would produce. M4 exercises the staleness half of knownNpmLockGaps, which nothing in the
original test plan touched. M7 and M8 are the two fail-open holes 1e43a5b229 closed; both now
fail closed outside the test harness too.

scripts/tests/package-scripts.test.js at 1e43a5b229: 47 passed, 1 skipped (48), including
all seven new fixture-driven failure-branch tests.

M6 answers the question a maintainer actually has at merge time: main has moved 31 commits past
the merge-base and one of them (4ec9f63d0a) touched package-lock.json. The gate still passes
against today's main, and running corepack pnpm import against today's main
package-lock.json produces a pnpm-lock.yaml byte-identical to the one committed here — so
this lockfile is not already stale.

Recomputed independently. Rather than trust the script, I reimplemented both gates separately
and compared: 1,966 package keys in pnpm-lock.yaml, exactly one not locked by npm —
mime-db@1.52.0, the documented gap. Against the base pnpm-lock.yaml: 18, matching the PR's
list item for item. The residual drift the PR discloses also checks out — of 422 direct workspace
dependencies, 409 resolve identically and 13 differ, exactly the number claimed, and every
pnpm-side version is locked somewhere in npm's tree.

the 13, and the two of them that are runtime dependencies
.                                      esbuild          npm 0.25.6     pnpm 0.25.12
integrations/external-context-mem0     esbuild          npm 0.25.6     pnpm 0.25.12
packages/chrome-extension              esbuild          npm 0.25.6     pnpm 0.25.12
packages/vscode-ide-companion          esbuild          npm 0.25.6     pnpm 0.25.12
packages/cli                           @types/node      npm 22.19.19   pnpm 22.20.1
packages/mobile-mcp                    @types/node      npm 22.20.0    pnpm 22.20.1
packages/qwen-live                     @types/node      npm 22.19.19   pnpm 22.20.1
packages/sdk-typescript                @types/node      npm 22.19.19   pnpm 22.20.1
packages/vscode-ide-companion          @types/node      npm 22.19.19   pnpm 22.20.1
packages/web-shell                     @types/node      npm 22.19.20   pnpm 22.20.1
packages/cli                           @types/semver    npm 7.7.0      pnpm 7.7.1
packages/core                          fdir             npm 6.4.6      pnpm 6.5.0
packages/core                          picomatch        npm 4.0.4      pnpm 4.0.5

Eleven of the thirteen are build or type tooling. The two that are not are fdir and
picomatch, both in packages/core's dependencies: a pnpm worktree runs core against
6.5.0 / 4.0.5 where CI runs 6.4.6 / 4.0.4. npm locks those versions too (under vite and
tinyglobby), so the gate is right to pass — but that is the concrete shape of the accepted
parity gap, worth having on record next to the esbuild example the review already gave.

2. macOS — the ⚠️ row

macOS leg

  • corepack pnpm import reproduces the committed lockfile byte-for-byte. Deleting
    pnpm-lock.yaml and regenerating it from package-lock.json produced a file identical to the
    committed one, in 9s. The PR claimed only that two consecutive imports match each other, on
    Linux; the stronger property — that the committed file is exactly what the documented command
    produces, on a second OS — holds. The regenerated file then passes --frozen-lockfile.
  • The three assertions of the smoke job reproduce: the frozen install exits 0 — 1m12s from an
    empty tree, 4s when re-verifying an installed one, which is what the figure shows — the
    workspace link resolves inside the worktree, and the bootstrap leaves git status clean. The
    first --offline attempt on the cold tree falls back to --prefer-offline on one missing
    metadata entry (@node-rs/jieba-android-arm-eabi), the same cold-cache path the review already
    identified on main.
  • fsevents: false is safe, for a sharper reason than either the code comment or the review
    gives — see F5.

3. The lint rule

npm run lint:ci passes on both trees (npm 129s, pnpm 119s) — 0 violations of the new rule, which
is the run the author deferred to CI.

Two mutations show both new config blocks are load-bearing:

  • Reverting the three manifests to their base versions surfaces 30 import sites across 13
    (package, dependency) pairs
    — exactly the set the PR declares, and no other rule fires. (The
    body says "15 imports"; sites are 30 and declarations are 13. Cosmetic.)
  • Deleting the web-templates packageDir block surfaces 13 sites in the two build sub-projects,
    so that block is doing real work rather than documenting an intention.

All 13 new declarations resolve to a version already locked — checked against package-lock.json
with semver.satisfies, including the one that is not an exact match (semver ^7.7.2 → locked
7.8.5). The published CLI manifest is untouched by construction: scripts/prepare-package.js
writes dependencies: {} into dist/package.json.

4. Stage 2 brief, measurement 2 — hoisted parity

stage 2

The brief asks for the CI matrix on a pnpm-installed hoisted tree. I ran the list it names on both
trees on this host, substituting test:scripts for the full test:ci:

step pnpm tree npm tree
npm run build PASS 193s PASS 198s
npm run typecheck FAIL 59s PASS 79s
npm run lint:ci PASS 119s PASS 129s
npm run test:scripts FAIL 763s — 2 failed / 2406 passed FAIL 721s — 3 failed / 2405 passed
npm run bundle PASS 4s PASS 3s
npm run check:serve-fast-path-bundle PASS 193s PASS 181s

The two test:scripts failures are the same pair on both installers
(qwen-autofix-workflow.test.js, one of them a 50s timeout); the npm tree adds a third
(install-script.test.js, a network-shaped assertion). None of them touches this diff, and pnpm
is not the worse of the two — so test:scripts is a parity pass with a host-environment asterisk.

So the brief's premise — "npm scripts run unchanged on a hoisted pnpm tree" — holds for build,
lint, bundle and the fast-path check, and not for typecheck. See F4.

5. Stage 2 brief, measurement 1 — per-worktree disk on APFS

Filesystem APFS, warm pnpm store, three fresh worktrees of the same commit, df deltas (the
brief is right that du is useless here — it reports 1.2 GiB for the tree that actually cost
40 MiB). A 20-second sleep control measured −1 MiB of background noise.

method command in the new worktree df delta wall du -sh
npm QWEN_SKIP_PREPARE=1 QWEN_SKIP_NOTICE_GENERATION=1 npm ci --prefer-offline 1,398 MiB 28s 982 MiB
pnpm node scripts/setup-worktree.js 40 MiB 23s 1.2 GiB
reflink copy cp -c -R of the primary's node_modules + every workspace root's own 64 MiB 24s 1.0 GiB

All three of the brief's link checks pass on the reflink arm, including the one
1e43a5b229 added:

readlink node_modules/@qwen-code/qwen-code-core  -> ../../packages/core
require.resolve core                             -> ./packages/core/package.json
require.resolve @larksuiteoapi/node-sdk (feishu) -> ./packages/channels/feishu/node_modules/…

Two things this settles for the APFS/developer-Mac row:

  1. The 93% saving the brief quotes is, if anything, understated here — 1,398 → 40 MiB is 97%.
  2. The reflink copy gets 95% of that saving without pnpm (64 MiB vs 40 MiB, a 24 MiB gap on a
    1.4 GiB baseline). The brief names that as the comparison that decides whether disk alone
    justifies Stage 2; on this filesystem, it does not.

Findings

F1 — the new git-dependency exemption does not fire for a real git dependency

1e43a5b229 adds a branch that exempts a pnpm key matching npm's resolved for a git/file:
dependency, and a test for it. The test writes both sides itself, so it only proves the exemption
works when the two strings are equal. They are not equal in practice:

git dependency

root devDependency:  "is-plain-obj": "github:sindresorhus/is-plain-obj#v4.1.0"

npm  resolved     :  git+ssh://git@github.com/sindresorhus/is-plain-obj.git#68e8cc77…
pnpm packages key :  is-plain-obj@https://codeload.github.com/sindresorhus/is-plain-obj/tar.gz/68e8cc77…

$ node scripts/check-lockfile.js   → exit 1
Error: pnpm-lock.yaml resolves versions that package-lock.json does not lock.
Regenerate it from package-lock.json with `corepack pnpm import`:
- is-plain-obj@https://codeload.github.com/sindresorhus/is-plain-obj/tar.gz/68e8cc77…

npm normalises the spec to git+ssh://…#<sha>; pnpm keys the codeload tarball. The set membership
test can never match, and the remedy the error prints is the command that produced the state —
whoever hits this has no way out of the loop from the message alone.

I ran the git half; the file: half has the same shape (npm anchors file: at the referring
package, pnpm keys name@file:<workspace-relative path>) but I did not test it. Latent either way:
there are zero git and zero file: dependencies in either lockfile right now, so this
blocks nothing. But the branch exists precisely for the day one lands, and as written it will not
help then. Matching on the 40-hex commit SHA that appears in both strings (or on
resolution.tarball for a gitHosted: true entry) would. A fixture test that builds each side
with the tool that writes it — rather than by hand — would have caught this.

F2 — the PR description's "Smoke trigger" bullet is now out of date

1e43a5b229 reverted package-lock.json out of pnpm-worktree-smoke.yml's path filters and
added a comment explaining why, and the scripts test now asserts its absence. The description
still says "The pnpm smoke workflow now also runs when only the npm lockfile changes." Worth
editing before merge so the description matches the branch.

F3 — the type-only exemption leaves the rule short of what an isolated layout would catch

The rule config does not set includeTypes, so type-only imports are exempt; the comment
justifies that with "they disappear at build time". True for the bundle, not for tsc: an
isolated node_modules still has to resolve those specifiers to typecheck. Turning
includeTypes: true on surfaces 3 more (package, dependency) pairs across 5 sites today:

packages/core :: @opentelemetry/sdk-trace-base   declared nowhere in the repo
packages/core :: @agentclientprotocol/sdk        declared by 5 sibling packages, not by core
packages/cli  :: cli-spinners                    declared nowhere in the repo

@opentelemetry/sdk-trace-base is the sharpest: it exists only at
packages/core/node_modules/@opentelemetry/sdk-trace-base, pulled in transitively by
@opentelemetry/sdk-node, and packages/core/src/telemetry/{file-exporters,log-to-span-processor}.ts
type-import it. Same class of fragility the PR is closing, one lane over. Either flip the flag and
declare the three, or say in the comment that the exemption is deliberate and why.

F4 — npm run typecheck fails on a pnpm-installed tree; not this PR's doing

One error:

packages/acp-bridge/src/process-registry.ts(744,3): error TS2322:
  Type 'string | NonSharedBuffer' is not assignable to type 'string'.

Cause: the tree root's @types/node. npm puts 20.19.1 there, pnpm's hoisted layout puts 22.20.1,
and tsc -p integration-tests/tsconfig.json maps packages/acp-bridge/src/** in through paths,
so it reads the root copy. acp-bridge's own exact pin (20.19.1) is materialised correctly under
packages/acp-bridge/node_modules in both trees — only the root slot differs. A/B:
installing main's own pnpm-lock.yaml at 642d36e gives the same root 22.20.1 and the same
error, so this predates the PR. Recorded here because it is a direct answer to one of the two
questions the new brief exists to settle, and belongs in that brief's eventual results.md rather
than being rediscovered.

F5 — fsevents: false is correct; the review's stated consequence is not

The stage=2 review says the deny "means those lose the native FSEvents binding and fall back to
fs.watch". Measured here, that does not happen:

  • All three fsevents copies (2.3.3 at the root, 2.3.2 under playwright, and a third 2.3.2 — under
    mobilewright in the pnpm tree, under packages/mobile-mcp in the npm tree) exist in both
    trees with their prebuilt fsevents.node present; the root binary is byte-identical between
    the two trees.
  • Neither tree has a build/ directory, and fsevents 2.3.3's installed tarball contains no
    binding.gyp and no install script at all — the install: node-gyp rebuild that npm's
    lockfile flag reflects lives only in the registry packument, which is stale relative to the
    tarball. Neither installer compiles anything; the deny denies a script that never runs.
  • Live probe in the pnpm tree: require('fsevents').watch(dir, cb) delivered an event for a file
    created after the watch started, from the root copy and from playwright's nested one.

The PR's comment ("whose install script is only node-gyp rebuild") is right about intent and
slightly off about the tarball. Worth a sentence, since this entry is the one place the PR decides
rather than records.

F6 — two small asymmetries (nits)

  • knownNpmLockGaps has a staleness check (M4 proves it); allowBuilds does not, so an entry that
    stops corresponding to an npm install script lingers silently. Today the set is exact — 7 npm
    install-script packages, all decided, plus one workspace entry — so this is prevention, not a live
    problem. A staleness check would have to whitelist the @qwen-code/qwen-code-core@file:packages/core
    entry, which is probably why it was left out; saying so in a comment would be enough.
  • The rule's globs are packages/**/src/** and integrations/**/src/**, .ts/.tsx only. Package
    entry files (packages/cli/index.ts, packages/core/index.ts), packages/*/scripts/**, and
    .mjs build files under src (e.g. packages/web-templates/src/insight/vite.config.mjs, which
    imports vite and @vitejs/plugin-react) stay outside it. I scanned that uncovered surface by
    hand and found nothing undeclared today — a coverage note, not a defect.

What I did not run

npm run test:ci in full (CI covers it), Windows, and the disk figures on the ECS runner hosts —
the brief's item 1 wants those per host class, and this is only the developer-Mac row.


中文版(点击展开)

PR #11625 — macOS 本地验证

覆盖了两个 head,因为验证期间 AutoFix 推上了 1e43a5b229

  • 1e43a5b229(当前 head)—— 门的变异矩阵、package-scripts 套件,以及对该提交新增的
    git 依赖分支做的一次实测。
  • 0c67d4872a —— lint 部分、hoisted parity 矩阵、磁盘测量。

两者之间的 diff 只动了 scripts/check-lockfile.js、它的测试、smoke workflow 的触发路径清单和
Stage 2 说明 —— 没有动任何 manifest、任何 lockfile、任何发布出去的源码 —— 所以后一组结果原样
适用。

所有结果都跑在一台真实的 macOS/APFS 机器上,用本分支的独立 worktree:一个用 npm ci 安装
(1,979 个包,4m00s),一个用 node scripts/setup-worktree.js 安装(pnpm 11.24.0,仓库钉住的
版本,1m12s)。macOS 正是 PR 标成 ⚠️ 的那一行;docs/verification/pnpm-stage2-evidence/README.md
交接出去的两项测量,也正好是这台机器能跑的,所以两项都在下面给出。

环境:macOS 26.6.2 arm64 · Node v24.18.1 · npm 11.16.0 · corepack pnpm 11.24.0。
(CI 的 smoke 任务用 Node 22.x;下面的结论都不依赖运行时版本。)

结论

建议合入,但有一处值得先修(F1),还有一行描述已经过期(F2)。测试计划里的每一条都复现了,
包括两种写明的失败情形;我另加的金丝雀说明这些门是因为正确的原因失败,而不是碰巧通过;
1e43a5b229 关掉的三个缺口也都是真的关上了 —— 除了 git 依赖那一个,它对真实的 git 依赖不生效。

当前 CI 状态

Lint & Static1e43a5b229 上是红的,原因在本 diff 之外:它挂在 Check lint gate freshness
这一步,在安装之前。mainae78d5b800#11621)动过 eslint.legacy-filenames.mjs,而本分支
还没把它并进来 —— 这正是那道检查要抓的情况。merge origin/main 即可清掉。分支自己的 lint 在两棵
树上本地都是绿的(§3),门对今天 mainpackage-lock.json 也通过(M6),所以没有迹象说明它
背后还藏着第二个问题。

1. 两道门 —— 9 个用例,在 1e43a5b229 上实跑

(对应上文图 1)

用例 变异 预期 实际
M0 不动:head 原样 通过 通过 —— 说好的四行
M1 换回 mainpnpm-lock.yaml 失败 失败 —— 18 个未锁定版本,与 PR 正文逐条吻合
M2 allowBuilds 删掉 fsevents 失败 失败 —— 点名 fsevents
M3 金丝雀:往 pnpm-lock.yaml 塞一个伪造版本 失败 失败 —— 点名 fake-canary-package@9.9.9
M4 金丝雀:npm 开始锁定 mime-db@1.52.0 失败 失败 —— "remove these entries from knownNpmLockGaps"
M5 金丝雀:新来一个带 install 脚本的依赖 失败 失败 —— 点名 canary-native-thing
M6 今天的 origin/mainpackage-lock.json(合入后) 通过 通过
M7 金丝雀:pnpm-lock.yaml 没有 packages: 失败 失败 —— "has no packages section"1e43a5b2 新增)
M8 金丝雀:allowBuilds 写成占位字符串而不是布尔 失败 失败 —— 点名 esbuild1e43a5b2 新增)

M3–M5 有必要,因为「M0 过、M1 挂」这一现象,一道只认死名单的门也做得到。M4 打的是
knownNpmLockGaps 的过期检测那一半,原测试计划里没有别的用例碰它。M7、M8 是 1e43a5b229
关掉的两个 fail-open 洞,在测试夹具之外同样是 fail closed。

1e43a5b229 上的 scripts/tests/package-scripts.test.js47 通过、1 跳过(共 48),
包含全部 7 个新增的夹具式失败分支测试。

M6 回答的是维护者在合入时真正关心的问题:main 自 merge-base 起已经前进 31 个提交,其中
4ec9f63d0a 动过 package-lock.json。这道门对今天的 main 仍然通过;而且用今天 main
package-lock.jsoncorepack pnpm import,产出的 pnpm-lock.yaml 与本 PR 提交的那份
逐字节相同 —— 所以这份 lockfile 还没有过期。

独立重算。 我没有直接信这个脚本,而是另写了一份实现来对照:pnpm-lock.yaml 有 1,966 个包
键,其中恰好 1 个没有被 npm 锁定 —— mime-db@1.52.0,即那条写明的例外。对照基线的
pnpm-lock.yaml 是 18 个,与 PR 的清单逐条一致。PR 自己披露的残余漂移也对得上:422 个
workspace 直接依赖里,409 个解析一致、13 个不同,正是它说的数字,而且每个 pnpm 侧的版本
npm 都在自己的依赖树里某处锁定过。

这 13 个,以及其中两个是运行时依赖
.                                      esbuild          npm 0.25.6     pnpm 0.25.12
integrations/external-context-mem0     esbuild          npm 0.25.6     pnpm 0.25.12
packages/chrome-extension              esbuild          npm 0.25.6     pnpm 0.25.12
packages/vscode-ide-companion          esbuild          npm 0.25.6     pnpm 0.25.12
packages/cli                           @types/node      npm 22.19.19   pnpm 22.20.1
packages/mobile-mcp                    @types/node      npm 22.20.0    pnpm 22.20.1
packages/qwen-live                     @types/node      npm 22.19.19   pnpm 22.20.1
packages/sdk-typescript                @types/node      npm 22.19.19   pnpm 22.20.1
packages/vscode-ide-companion          @types/node      npm 22.19.19   pnpm 22.20.1
packages/web-shell                     @types/node      npm 22.19.20   pnpm 22.20.1
packages/cli                           @types/semver    npm 7.7.0      pnpm 7.7.1
packages/core                          fdir             npm 6.4.6      pnpm 6.5.0
packages/core                          picomatch        npm 4.0.4      pnpm 4.0.5

13 个里有 11 个是构建或类型工具。剩下两个不是:fdirpicomatch,都在 packages/core
dependencies 里 —— pnpm worktree 跑 core 用的是 6.5.0 / 4.0.5,而 CI 用的是 6.4.6 / 4.0.4。
npm 也锁定了那两个版本(在 vitetinyglobby 下面),所以这道门放行是对的;但这就是那条
「已接受的 parity 缺口」的具体形状,值得和评审已经给出的 esbuild 例子并列记在案。

2. macOS —— 那一行 ⚠️

(对应上文图 2)

  • corepack pnpm import 逐字节复现了提交进来的 lockfile。 先删掉 pnpm-lock.yaml,再从
    package-lock.json 重新生成,产出的文件与提交的那份完全相同,耗时 9s。PR 只声称「连续两次
    import 彼此相同」,而且是在 Linux 上;更强的那个性质 —— 提交的这份就是文档里那条命令的产出,
    并且在第二个操作系统上也成立 —— 是成立的。重新生成的文件随后也能通过 --frozen-lockfile
  • smoke 任务的三条断言都复现:frozen install 退出码 0 —— 空树冷装 1m12s,对已安装的树重新
    校验是 4s(图里是后者);workspace 链接解析到 worktree 内部;bootstrap 之后 git status 干净。
    冷树上第一次 --offline 尝试因为一条缺失的元数据(@node-rs/jieba-android-arm-eabi)退回
    --prefer-offline,与评审已在 main 上指出的冷缓存路径是同一个。
  • fsevents: false 是安全的,理由比代码注释和评审给的都更锋利 —— 见 F5。

3. 那条 lint 规则

npm run lint:ci 在两棵树上都通过(npm 129s,pnpm 119s)—— 新规则 0 处违规,也就是作者交给 CI
的那一跑。

两个变异说明新增的两个配置块都在真干活:

  • 把三个 manifest 还原成基线版本,会浮出 30 处 import13 个(包,依赖)对 —— 正是 PR
    声明的那一组,且没有别的规则报错。(正文写的是「15 处 import」;按位置数是 30,按声明
    是 13。属于措辞问题。)
  • 删掉 web-templatespackageDir 块,会在两个构建子项目里浮出 13 处,所以那个块不是摆设。

13 条新声明全部解析到已锁定的版本 —— 用 semver.satisfies 对照 package-lock.json 验证过,
包括唯一一个不是精确相等的(semver ^7.7.2 → 锁定 7.8.5)。发布出去的 CLI manifest 从构造上
就不受影响:scripts/prepare-package.jsdist/package.json 里写的是 dependencies: {}

4. Stage 2 说明的第 2 项测量 —— hoisted parity

(对应上文图 3)

说明要求在 pnpm 安装的 hoisted 树上跑一遍 CI 矩阵。我在这台机器上、两棵树上跑了它点名的那一串,
把完整的 test:ci 换成了 test:scripts

步骤 pnpm 树 npm 树
npm run build PASS 193s PASS 198s
npm run typecheck FAIL 59s PASS 79s
npm run lint:ci PASS 119s PASS 129s
npm run test:scripts FAIL 763s —— 2 失败 / 2406 通过 FAIL 721s —— 3 失败 / 2405 通过
npm run bundle PASS 4s PASS 3s
npm run check:serve-fast-path-bundle PASS 193s PASS 181s

test:scripts 的两处失败在两个安装器下是同一对qwen-autofix-workflow.test.js,其中一个是
50s 超时);npm 树还多一个(install-script.test.js,一条依赖网络形态的断言)。这些都跟本 diff
无关,而且 pnpm 并不更差 —— 所以这一行算 parity 通过,只是带一个宿主环境的星号。

也就是说,说明里的前提 ——「npm 脚本在 hoisted pnpm 树上原样可跑」—— 对 build、lint、bundle 和
fast-path 检查成立,对 typecheck 不成立。见 F4。

5. Stage 2 说明的第 1 项测量 —— APFS 上每个 worktree 的磁盘占用

文件系统 APFS,pnpm store 已预热,三个同一提交的全新 worktree,取 df 差值(说明里说 du
在这里没用是对的 —— 它对那棵实际只花了 40 MiB 的树报了 1.2 GiB)。用一个 20 秒的 sleep 做对照,
背景噪声是 −1 MiB。

方法 在新 worktree 里执行的命令 df 差值 墙钟 du -sh
npm QWEN_SKIP_PREPARE=1 QWEN_SKIP_NOTICE_GENERATION=1 npm ci --prefer-offline 1,398 MiB 28s 982 MiB
pnpm node scripts/setup-worktree.js 40 MiB 23s 1.2 GiB
reflink 拷贝 对主检出的 node_modules 及每个 workspace 根自己的那份做 cp -c -R 64 MiB 24s 1.0 GiB

说明里要求的三条链接检查在 reflink 这一臂全部通过,包括 1e43a5b229 新加的那条:

readlink node_modules/@qwen-code/qwen-code-core  -> ../../packages/core
require.resolve core                             -> ./packages/core/package.json
require.resolve @larksuiteoapi/node-sdk (feishu) -> ./packages/channels/feishu/node_modules/…

对「APFS / 开发者 Mac」这一行,这组数字定下了两件事:

  1. 说明引用的 93% 节省在这里只多不少 —— 1,398 → 40 MiB 是 97%。
  2. reflink 拷贝在不用 pnpm 的情况下拿到了其中 95% 的节省(64 MiB 对 40 MiB,在 1.4 GiB 的
    基线上只差 24 MiB)。说明把这一对比列为「磁盘本身是否足以支撑 Stage 2」的判据;在这个文件
    系统上,答案是不足以。

发现

F1 —— 新增的 git 依赖豁免,对真实的 git 依赖不生效

(对应上文图 4)

1e43a5b229 增加了一条分支:如果某个 pnpm 键等于 npm 对 git/file: 依赖记的 resolved,就
豁免它;并配了一个测试。那个测试自己写两边的字符串,所以它只证明「两边相等时豁免有效」。而在
现实里两边并不相等:

根 devDependency:  "is-plain-obj": "github:sindresorhus/is-plain-obj#v4.1.0"

npm  resolved   :  git+ssh://git@github.com/sindresorhus/is-plain-obj.git#68e8cc77…
pnpm packages 键:  is-plain-obj@https://codeload.github.com/sindresorhus/is-plain-obj/tar.gz/68e8cc77…

$ node scripts/check-lockfile.js   → exit 1
Error: pnpm-lock.yaml resolves versions that package-lock.json does not lock.
Regenerate it from package-lock.json with `corepack pnpm import`:
- is-plain-obj@https://codeload.github.com/sindresorhus/is-plain-obj/tar.gz/68e8cc77…

npm 会把 spec 规范化成 git+ssh://…#<sha>,pnpm 则用 codeload 的 tarball 做键。集合包含判断
永远匹配不上;而且错误信息给出的补救办法,正是制造出这个状态的那条命令 —— 只看这条消息的人会在
原地打转。

我实测的是 git 这一半;file: 那一半形状相同(npm 以引用方为锚记 file:,pnpm 用
name@file:<相对 workspace 的路径> 做键),但我没有测。两种情况今天都只是潜在问题:两个
lockfile 里现在 git 和 file: 依赖各一个都没有,所以不阻塞。但这条分支
存在的意义正是「等哪天有了」,而照现在的写法,那天它帮不上忙。改成匹配两边字符串里都出现的
40 位 commit SHA(或者对 gitHosted: true 的条目匹配 resolution.tarball)就可以。夹具测试如果
让「写这一侧的工具」来生成这一侧、而不是手写,本来就能抓到。

F2 —— PR 描述里的 “Smoke trigger” 一条已经过期

1e43a5b229package-lock.jsonpnpm-worktree-smoke.yml 的路径过滤器里撤了回去,并加了
注释说明理由,scripts 测试现在断言它不在。而描述里仍然写着「只改 npm lockfile 时,pnpm smoke
workflow 现在也会运行」。合入前值得改一下,让描述和分支对得上。

F3 —— 豁免纯类型 import,使这条规则达不到 isolated 布局本该抓到的范围

规则配置没有设 includeTypes,所以纯类型 import 被豁免;注释给的理由是「它们在构建时消失」。
对 bundle 没错,对 tsc 不是:isolated 的 node_modules 仍然要解析这些说明符才能过类型检查。
includeTypes: true 打开,今天会多浮出 3 个(包,依赖)对、5 处:

packages/core :: @opentelemetry/sdk-trace-base   全仓没有任何 manifest 声明过
packages/core :: @agentclientprotocol/sdk        5 个兄弟包声明了,core 没有
packages/cli  :: cli-spinners                    全仓没有任何 manifest 声明过

@opentelemetry/sdk-trace-base 最尖锐:它只存在于
packages/core/node_modules/@opentelemetry/sdk-trace-base,由 @opentelemetry/sdk-node 传递
带入,而 packages/core/src/telemetry/{file-exporters,log-to-span-processor}.ts 会 type-import
它。这正是本 PR 要关掉的那类脆弱性,只是换了一条车道。要么打开开关并补上这三条声明,要么在
注释里写明这个豁免是有意为之以及为什么。

F4 —— npm run typecheck 在 pnpm 安装的树上失败;不是这个 PR 造成的

只有一个错误:

packages/acp-bridge/src/process-registry.ts(744,3): error TS2322:
  Type 'string | NonSharedBuffer' is not assignable to type 'string'.

原因是树根的 @types/node:npm 放 20.19.1,pnpm 的 hoisted 布局放 22.20.1,而
tsc -p integration-tests/tsconfig.json 通过 pathspackages/acp-bridge/src/** 映射进来,
读到的就是根上那一份。acp-bridge 自己钉的 20.19.1 在两棵树里都正确落到了
packages/acp-bridge/node_modules —— 只有根上那个槽位不同。A/B:642d36e 上安装
main 自己的 pnpm-lock.yaml,根上同样是 22.20.1、同样是这个错误,所以它早于本 PR。写在这里,
是因为它直接回答了新说明存在的两个问题之一,应该进那份说明将来的 results.md,而不是被人再
发现一次。

F5 —— fsevents: false 是对的,但评审给出的后果不对

stage=2 评审说这条 deny「让它们失去原生 FSEvents 绑定、退回 fs.watch」。实测并没有发生:

  • 三份 fsevents(根上 2.3.3,playwright 下 2.3.2,第三份 2.3.2 —— 在 pnpm 树里位于
    mobilewright 下、在 npm 树里位于 packages/mobile-mcp 下)在两棵树里都在,预编译的
    fsevents.node 也都在;根上那份二进制在两棵树之间逐字节相同
  • 两棵树都没有 build/ 目录;fsevents 2.3.3 安装下来的 tarball 里既没有 binding.gyp,也
    根本没有 install 脚本 —— npm lockfile 那个标记反映的 install: node-gyp rebuild 只存在于
    registry 的 packument 里,而它相对 tarball 是过期的。两个安装器都没有编译任何东西,这条 deny
    拒掉的是一个本来就不会跑的脚本。
  • pnpm 树里的实测探针:require('fsevents').watch(dir, cb) 对「开始监听之后」创建的文件收到了
    事件,根上那份和 playwright 嵌套的那份都收到了。

PR 的注释(「install 脚本只是 node-gyp rebuild」)在意图上没错,在 tarball 这一点上略有出入。
值得补一句,因为这条目是本 PR 唯一一处「做决定」而不是「记录现状」的地方。

F6 —— 两处小的不对称(吹毛求疵)

  • knownNpmLockGaps 有过期检测(M4 证明了),allowBuilds 没有:某条目一旦不再对应任何 npm
    install 脚本,会悄悄留着。今天这个集合是精确的 —— 7 个带 install 脚本的 npm 包全部有决定,外加
    一条 workspace 条目 —— 所以这是预防,不是现存问题。真要加过期检测,得把
    @qwen-code/qwen-code-core@file:packages/core 那条放行,这大概就是没加的原因;在注释里说一句
    就够了。
  • 规则的 glob 是 packages/**/src/**integrations/**/src/**,且只覆盖 .ts/.tsx。包的
    入口文件(packages/cli/index.tspackages/core/index.ts)、packages/*/scripts/**,以及
    src 下的 .mjs 构建文件(例如 packages/web-templates/src/insight/vite.config.mjs,它 import
    vite@vitejs/plugin-react)都在覆盖之外。我把这片没覆盖的面手工扫了一遍,今天没有
    未声明的 import —— 属于覆盖面备注,不是缺陷。

没有跑的部分

完整的 npm run test:ci(CI 覆盖)、Windows,以及 ECS runner 主机上的磁盘数字 —— 说明的第 1 项
要的是按主机类别测,这里只是「开发者 Mac」那一行。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • the stale "Smoke trigger" bullet in the PR description and the matching tick in #10444's checklist (scripts/tests/package-scripts.test.js:947) — already reported by @wenshao (issue comment 5642071764, finding F2)
  • the missing staleness arm on pnpm-workspace.yaml allowBuilds, the sibling of the one knownNpmLockGaps got in this same diff (scripts/check-lockfile.js:215-221) — already reported by @wenshao (issue comment 5642071764, finding F6)

Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": none — I did not execute corepack pnpm import or pnpm install --frozen-lockfile to confirm pnpm's override/frozen interaction first-hand (it would mutate th…; "agent reverse-audit (round 2)": an executed pnpm install --frozen-lockfile against the regenerated lockfile (no registry access and no writable install tree in this shared worktree) — instal…; "agent reverse-audit (round 3)": a full-repo lint run — I ran npx eslint only over the rule-reached packages whose manifests this PR did not amend ( packages/channels , integrations , sdk-t…; "agent reverse-audit (round 3)": whether pnpm-lock.yaml 's importer **spec strings** (not just names) match the amended manifests — a spec drift also fails pnpm install --frozen-lockfile on …; "agent reverse-audit (round 3)": whether corepack pnpm import applies the .pnpmfile.mjs readPackage hook and the 13 pnpm-workspace.yaml overrides , i.e. whether the new AGENTS.md regen…, and 2 more.

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

中文说明

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

本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 1)"none — I did not execute corepack pnpm import or pnpm install --frozen-lockfile to confirm pnpm's override/frozen interaction first-hand (it would mutate th…"agent reverse-audit (round 2)"an executed pnpm install --frozen-lockfile against the regenerated lockfile (no registry access and no writable install tree in this shared worktree) — instal…"agent reverse-audit (round 3)"a full-repo lint run — I ran npx eslint only over the rule-reached packages whose manifests this PR did not amend ( packages/channels , integrations , sdk-t…"agent reverse-audit (round 3)"whether pnpm-lock.yaml 's importer **spec strings** (not just names) match the amended manifests — a spec drift also fails pnpm install --frozen-lockfile on …"agent reverse-audit (round 3)"whether corepack pnpm import applies the .pnpmfile.mjs readPackage hook and the 13 pnpm-workspace.yaml overrides , i.e. whether the new AGENTS.md regen…,另有 2 条。

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

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

Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread docs/verification/pnpm-stage2-evidence/README.md
Comment thread docs/verification/pnpm-stage2-evidence/README.md Outdated
Comment thread scripts/check-lockfile.js Outdated
Comment thread eslint.config.js
Comment thread scripts/check-lockfile.js
Comment thread scripts/check-lockfile.js
Comment thread scripts/check-lockfile.js
yiliang114 and others added 3 commits September 12, 2026 17:40
Quiesce the df window, name the worktree sampler's working directory and forbid host cron, and register the throwaway workflow's size baseline so the parity run's test:ci does not fail under both installers.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmty6iqs0zv
Step 2 now pushes the scratch branch before gh workflow run --ref (the ref is
resolved server-side), notes the workflow token scope, and gives the merge-to-
default / pull_request fallback when a branch-only dispatch is refused. Step 1
probes the checkout's own volume with diskutil info "$(pwd)" instead of the
sealed read-only / system volume. Pushing, dispatching, and posting the results
comment are marked operator actions.

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

Patrol-Run: qwen-pr-closeout/jmty8nwk9zy
Add two failure-branch cases: a knownNpmLockGaps entry that leaves the pnpm
graph (the !pnpmVersions.includes arm), and an aliased install script reported
under its real package name (the npmPackageName details.name arm). Both were
untested and stayed green if the arm was deleted.

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

Patrol-Run: qwen-pr-closeout/jmty8nwk9zy

@qwen-code-dev-bot qwen-code-dev-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.

已核对 head 7637d8c4(base 4c072e88,12 files / +1164 -919)。

⛔ 先说阻塞项:这个 PR 现在跟 main 冲突

mergeable: falsemergeable_state: dirtyrebaseable: false。我在临时 worktree 里试合了一次,冲突只有两个文件:

  • scripts/check-lockfile.js
  • scripts/tests/package-scripts.test.js

正好是约二十分钟前刚合入的 #11371 改过的那两个(Playwright parity 段 + 它的 fixture-copy 测试)。因为两边都在同一锚点追加内容,所以是内容冲突。合 main 时必须同时保留:本 PR 的 CHECK_LOCKFILE_ROOT seam 与两段新门禁,以及 #11371 的 parity 段与其 11 个 arm。

我在 head 上实跑过的

  • node scripts/check-lockfile.js:五段全部通过(npm integrity、pnpm integrity、pnpm vs npm 版本一致、build approvals)。
  • CHECK_LOCKFILE_ROOT 这个新 seam 工作正常,能把门禁指向 fixture。

合并 main 之后会坏在哪(已复现,见 inline)

把 main 的 parity 段接到本 head 脚本尾部(即合并后的形态),再用本 PR 自己的 fixture —— 只拷 package-lock.json / pnpm-lock.yaml / pnpm-workspace.yaml —— 运行,parity 段会去读 <root>/package.json 并拿到 ENOENT,然后 process.exit(1)。所以 fixture 还得一起拷 package.jsonpackages/web-shell/package.json,否则本 PR 新增的、期望 exit 0 的两个 arm 合完全变红。

既有未解决线程(6 条 bot + 1 条你自己的)

  • R2-6 成立:新加的 import/no-extraneous-dependencies 只覆盖 packages/**/src/**,而 packages/web-shell 的源码在 client/(703 个 ts/tsx,不含 e2e),它是唯一被发布出去的 workspace(main/exports 都指向 dist/)。该块注释写的是"包必须声明自己源码 import 的东西",web-shell 整块在检查之外。
  • R1-1 成立,值得你判断pnpm-workspace.yamlnodeLinker: 'hoisted',而 lockfile 里 @agentclientprotocol/sdk@0.14.1 在不同 importer 分别配对 zod@4.4.3zod@3.25.76,物理上只会剩一份。SDK 的 peer 范围是 ^3.25.0 || ^4.0.0,两者都合法,所以我没跑 pnpm install,不判它是不是 bug。
  • scripts/check-lockfile.js:15(你自己提的那条)成立?? 不兜空字符串,CHECK_LOCKFILE_ROOT="" 会让 root 变成 '',之后所有路径相对 cwd。改成 || 或先 trim 都行。
  • R1-3、R1-8、R1-10、R2-5 我没有独立验证,不做判断。

本次不 approve:冲突未解 + 上述 P1。

Comment thread scripts/tests/package-scripts.test.js
Resolve the conflict with #11371 in the two files it and this branch both
appended to:

- scripts/check-lockfile.js keeps main's Playwright parity section and this
  branch's pnpm-vs-package-lock agreement and build-approval sections. The
  parity section runs first, so the earlier-shipped gate keeps its position
  relative to the lockfile integrity checks.
- scripts/tests/package-scripts.test.js keeps both sides' assertions on the
  shared "checks both lockfiles for integrity and agreement" case, and this
  branch's new failure-branch suite.

The parity section reads the pinned manifests as well as the lockfiles, so
both fixtures now copy what it needs: package.json and
packages/web-shell/package.json (plus pnpm-workspace.yaml for the new
build-approval gate in the Playwright suite). Without them a fixture run dies
on a missing package.json before reaching the detector each arm pins.

Verified on the merge result: scripts/check-lockfile.js passes all five
sections; scripts/tests/check-lockfile.test.js 11/11;
scripts/tests/package-scripts.test.js 40/41 (the one failure,
"bootstraps worktrees and preserves explicit hook settings", also fails on
main); prettier and eslint clean on the three touched files.
- `CHECK_LOCKFILE_ROOT` was read with `??`, so an exported-but-empty variable made `root` the empty string and every path in this gate cwd-relative: it would then report on whatever lockfiles sit in that directory as if they were the repository's, and pass quietly when the cwd happens to be the repo root. Truthiness keeps the fixture seam and closes the empty case.
- The version-agreement failure named exactly one remedy, `corepack pnpm import`, while pnpm-workspace.yaml carries eight `overrides` (three of them pin typescript) that no npm-side regeneration can reproduce. The message now names that layer, so a still-red gate points at what actually decides the value.

Verified: `node scripts/check-lockfile.js` exits 0 on the committed tree, prettier and eslint are clean on the file, and both suites that execute the gate pass.

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

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

Re-reviewed at head c507a6f48c. Since my previous pass (pullrequestreview-5186295521) two things landed: a merge of main, and c507a6f48c.

The merge is clean where it matters. main brought #11371's Playwright parity block into the tail of the same file this PR extends, which is the collision both PRs' review threads predicted. At this head the two blocks coexist correctly: the pnpm version-agreement and build-approval gates run first, the parity block after, both read the same root, and node scripts/check-lockfile.js exits 0 on the committed tree printing all three section verdicts. Note the follow-up #11716 also edits this file's parity block, so whichever of the two merges second still takes a textual conflict there.

c507a6f48c closes two of the three findings I filed: the CHECK_LOCKFILE_ROOT seam is now read for truthiness, so an exported-but-empty variable falls back to the repository instead of silently repointing every path in the gate at the cwd; and the version-agreement failure now names pnpm-workspace.yaml's overrides: as the second layer that can decide a version, which was the remedy the message omitted.

Verified at this head, not inferred: node scripts/check-lockfile.js exit 0; prettier --check and eslint --max-warnings 0 clean on the changed file; scripts/tests/check-lockfile.test.js plus scripts/tests/package-scripts.test.js = 60 passed, 1 skipped (the latter is the suite that exercises the env seam with a real fixture directory, so the truthiness change is covered rather than merely read). CI at this head: 24 pass, 1 pending (review-pr).

Four threads remain open, each with its fix or its reason stated in-thread: R1-1 (the @agentclientprotocol/sdk zod peer rebind — deliberately not adjudicated from the summary, it needs a lockfile reading and may be a runtime behaviour change rather than gate quality), R2-6 (packages/web-shell/client/** is outside the new lint rule's globs; the package has no src/, is published, and declares 31 dependencies), R1-10 (decidedBuilds discards both the key's scope and its boolean value), and package-scripts.test.js:191 (not read yet). Two further findings I declined with evidence in-thread: R1-8, where the exemption is unreachable — zero of 2,263 non-link packages have a git/file: resolved at this head, and the two tools' string formats could not match anyway — and R1-3, whose check needs a networked pnpm install and belongs in the smoke workflow.

Not approving, for a procedural reason rather than a finding: the last commit on this branch is mine, so this account cannot sign an approval on it, and pushes here dismiss approvals anyway — the two that stood at 7637d8c40d are gone. This PR's paths carry no CODEOWNERS entry, so one approval from any other maintainer is what it needs.

中文说明

在 head c507a6f48c 上复审。相比我上一轮,落了两件事:合并 main,以及 c507a6f48c

合并这一处是关键:main 把 #11371 的 Playwright 一致性块带进了本 PR 也在扩展的同一个文件尾部,正是两边 review 线程都预告过的碰撞。在当前 head 上两块共存正确——pnpm 的版本一致性与构建审批两道门禁在前、一致性块在后,两者读同一个 rootnode scripts/check-lockfile.js 在提交树上 exit 0 并打出三段结论。另外 #11716 也改这个文件的一致性块,所以两者谁后合并谁仍会在同一处吃文本冲突。

c507a6f48c 关掉了我提的三条里的两条:CHECK_LOCKFILE_ROOT 改成按 truthiness 读(导出但为空的变量不再把门禁里每条路径静默改成按 cwd 解析),以及版本不一致的报错现在点名 pnpm-workspace.yamloverrides: 这个同样能决定版本的层。

以下都是在该 head 上实测、不是推演:门禁 exit 0;prettier --checkeslint --max-warnings 0 对改动文件干净;check-lockfile.test.js + package-scripts.test.js 共 60 passed / 1 skipped(后者正是用真实夹具目录跑 env 接缝的套件,所以 truthiness 这个改动是被覆盖的,不只是读过)。CI 在该 head:24 pass、1 pending(review-pr)。

仍开着的 4 条线程,每条都在线程里写明了修法或理由:R1-1(zod peer 重绑,故意不凭摘要下结论,需要读锁文件,且可能是运行时行为变化而非门禁质量)、R2-6(新 lint 规则的 glob 覆盖不到 packages/web-shell/client/**;该包没有 src/、已发布、声明 31 个依赖)、R1-10(decidedBuilds 同时丢掉 key 的版本范围与布尔值)、package-scripts.test.js:191(尚未读)。另两条我已带证据 decline:R1-8(该豁免不可达——这 head 上 2263 个非 link 包里 resolvedgit/file: 开头的是 0,且两个工具写的字符串本来也不可能相等)、R1-3(那个校验需要带网络的 pnpm install,应放在 smoke workflow 里)。

不 approve,是程序原因不是发现:这条分支最后一个 commit 是我提的,本账号无法对它签 approve;而且这仓库 push 即 dismiss,7637d8c40d 上那两票已经没了。本 PR 的路径不在 CODEOWNERS 里,所以只差任意一位其他维护者的一票。

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

Re-review after head moved (7637d8cc507a6f). Prior round (R1) had no findings; this round verifies the delta.

No blocking findings.
Approval blockers: none.

Delta from prior head — three changes, all improvements:

  1. CHECK_LOCKFILE_ROOT hardening (check-lockfile.js lines 13-14):

    • Old: const root = process.env.CHECK_LOCKFILE_ROOT ?? join(__dirname, '..');
    • New: const envRoot = process.env.CHECK_LOCKFILE_ROOT?.trim(); const root = envRoot ? envRoot : join(__dirname, '..');

    The ?? form would have used "" (empty string) if the variable was exported but empty, making every join(root, ...) call produce a bare filename resolved against cwd. The new truthiness check falls through to join(__dirname, '..') in that case. Mechanism confirmed: join("", "package-lock.json") = "package-lock.json" (relative, reads from cwd). Fix is correct.

  2. Error message improvement (unlockedPnpmVersions branch): added explanation that a pnpm-workspace.yaml overrides: entry can cause a divergence that survives corepack pnpm import. Informational-only; no logic change.

  3. Test fixture expansion (package-scripts.test.js runCheckLockfile): now copies package.json and packages/web-shell/package.json in addition to the lockfiles. Needed because the base now includes the Playwright parity section (from a merged predecessor PR) which reads both manifests. Without them the fixture run would die on a missing file before reaching the branch under test. Correct fix.

    check-lockfile.test.js: added pnpm-workspace.yaml to FILES — consistent with the build-approval section needing it.

R1 findings carry-over: R1 had no findings. Nothing to rule fixed or still present.

Cross-check: wenshao's macOS verification covered 1e43a5b (the autofix head) and 0c67d4872a; the current head c507a6f is a rebase onto updated main with the same delta. No new reviewer findings to cross-check against.

Not covered: no new logic beyond the empty-string guard; rung 2+ not applicable.

Reviewed with AI assistance.

@yiliang114

yiliang114 commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator Author

Closeout paused: this PR was merged at 2026-09-12 14:48 UTC with head c507a6f. The local follow-up commits were not pushed and are not part of that merge; I have not marked the three threads resolved.

The local changes address R1-1 (retain npm's ACP SDK/Zod binding), R1-10 (match exact-version unions and source keys instead of discarding scope), and R2-6 (cover Web Shell client sources in the existing import gate). Existing build allow/deny entries are unchanged. The pinned pnpm rejects semver ranges in approval keys, so the old ^0.25.0 success fixture was corrected. No full graph comparator, approval-staleness policy, or Stage 2 migration was added.

Verified locally on macOS / Node 22.22.0 / pnpm 11.24.0: a real frozen install; the focused lockfile/script cases; Web Shell and changed-file lint; formatting. All five ACP importers share SDK 0.14.1 with Zod 3.25.76, and real valid initialize, malformed initialize and unknown-method wire responses match npm exactly. The SDK's existing malformed-request -32603 classification is preserved, not fixed upstream. Full builds/typecheck and cross-platform suites were not rerun.

Scope: five existing files, +79/-12 lines, including the generated lockfile delta and focused tests. The two local commits remain available for a separately authorized follow-up. The published-round counter remains 2 because this pass was not pushed.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

APPROVE

核对基线:head c507a6f48c9e495b8a66d520411bec6e5a97a317

历史阻塞问题:无

当前 24 条线程里 3 条未解决(R1-1 pnpm 侧 zod peer 重绑、R1-10 decidedBuilds 只取键名、R2-6 eslint glob 覆盖不到 packages/web-shell/client/**),逐条读过,全部是 [Suggestion] 级,没有任何 Critical、安全、数据损坏或回归级别的历史阻塞问题。按本渠道策略 Suggestion 不作为合入门禁。

本轮独立扫描:未发现 Critical

这个 PR 的主要风险面是「新声明的依赖是否改变了实际解析结果」和「新门禁是否会假绿」,我按这两条主线核对:

  1. 新增的依赖声明都是对既有解析结果的忠实记录,没有改变 npm 树的版本。
    • packages/cli 新声明 chalk: ^4.1.2package-lock.json 里根 node_modules/chalk 本来就解析为 4.1.2,而 packages/cli/src 中三处出货代码(commands/extensions/utils.tscommands/extensions/consent.tsui/utils/software-cursor.ts)都是 import chalk from 'chalk',此前正是从根 hoist 拿到 4.1.2。所以这是补声明,不是降级。其余 5.6.2 的副本各自嵌套在 inkoralog-symbolslint-staged@secretlint/formatter 下,不受影响。
    • packages/core 新声明 zod: ^3.25.76:npm 树里根 node_modules/zod 解析为 3.25.76@agentclientprotocol/sdk@0.14.1 hoist 在根且 peer 为 ^3.25.0 || ^4.0.0,由根 zod 满足;4.4.3 的副本只嵌套在 @modelcontextprotocol/client@modelcontextprotocol/corepackages/mobile-mcp 下。声明与既有解析一致。
    • 这一点很关键,因为 npm 才是 build/CI/packaging/release 的权威路径;R1-1 描述的 zod peer 重绑发生在 pnpm-lock.yaml,影响面限于 opt-in 且 install-only 的 pnpm worktree,且 SDK 的 peer 范围同时接受 3 与 4。
  2. NOTICES.txt 的 +6/-6 是纯重排。 移除的六行与新增的六行是同一个多重集合(@opentelemetry/api-logs@0.221.0core@2.10.0semantic-conventions@1.36.0exporter-logs-otlp-grpc@0.221.0otlp-exporter-base@0.221.0otlp-transformer@0.221.0),只是 api-logs 从末尾移到前面,许可证正文未动。没有任何归属条目被删除,不构成合规问题。
  3. 新增的 pnpm 门禁不会假绿。 scripts/check-lockfile.js 的新段落里,pnpmLockfile.packages 为空时直接 console.error + process.exit(1),明确写出「the version agreement gate read nothing」,因此读不到内容不会被当成通过;发现 pnpm 解析出 npm 未锁定的版本时 process.exitCode = 1。唯一的白名单项 mime-db@1.52.0 还配了 staleNpmLockGaps 反向检查,一旦 npm 侧锁上了该项或 pnpm 侧不再出现就会报错,白名单不会静默腐烂。方向也选得对:只断言「pnpm worktree 不得跑 CI 的 npm install 未锁定的版本」,而不是要求两张依赖图相等(npm 保留嵌套副本、pnpm 会 dedupe,两者本就不可能相同)。
  4. CHECK_LOCKFILE_ROOT 用真值判断而非 ??,注释说明了原因:导出但为空的环境变量会让所有路径退化成 cwd 相对,门禁就会把当前目录下碰巧存在的 lockfile 当成仓库的来报告。这个细节处理是对的。
  5. eslint.config.js 是收紧而非放宽。 新增的 import/no-extraneous-dependencies 设为 error,作用于 packages/**/src/**integrations/**/src/**,测试文件按 extraneousDependencyTestFiles 排除,类型导入按规则本身豁免。当前 head 上 Lint & Static 为 pass,意味着「声明了但没导入」和「导入了但没声明」两个方向都干净——这也反过来印证了第 1 点的声明是忠实的。
  6. pnpm-workspace.yaml 只新增 fsevents: false 及说明其 install script 仅为 node-gyp rebuild、macOS smoke install 未报告 ignored build 的注释,与 pnpm 既有行为一致。

CI:当前 head 上 Lint & StaticTest (ubuntu-latest, Node 22.x)Integration Tests (no-AK, No Sandbox)web-shell E2E Smoke 全部 pass,没有由本 PR 引入的失败;review-pr 仍 pending,按策略不作为门禁。

其余意见

R2-6 指出的 glob 覆盖不到 packages/web-shell/client/**(该包已发布、704 个 .ts/.tsxclient/ 下且 src/ 为空)虽为 Suggestion,但它正好是新规则注释里声明要覆盖的那类包,建议后续把 packages/*/client/** 一并纳入。R1-10 关于 decidedBuildsObject.keys() 丢弃 value、可能让 UNDECIDED_ALLOW_BUILD 占位符也算「已决定」的观察同样值得后续处理。两者都不阻塞本次合入。

结论:无历史阻塞问题,本轮未发现可证明的 Critical,提交 APPROVE。

@yiliang114
yiliang114 added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 1e76897 Sep 12, 2026
351 of 359 checks passed

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

Code Review: chore(pnpm): gate pnpm-lock on package-lock and declare hoisted imports

FINDINGS:

  • No blockers found.

CLEAN AREAS:

  • scripts/check-lockfile.js: Version agreement gate is correct. The direction (pnpm must not resolve versions npm has not locked) is the right one. npmPackageName handles aliased installs (details.name) and nested node_modules (lastIndexOf). pnpmPackageName correctly handles scoped packages (indexOf('@', 1) skips leading @). knownNpmLockGaps has staleness detection. Build approval check correctly filters non-boolean allowBuilds placeholders. CHECK_LOCKFILE_ROOT env var uses truthiness check (not ??) to guard against empty-string injection.
  • eslint.config.js: Hoisted imports rule is correctly scoped to packages/**/src/** and integrations/**/src/**, with test files excluded. The web-templates packageDir override is correct for a package whose imports resolve through a different manifest.
  • package.json changes (cli, core, vscode-ide-companion): Dependency additions correctly declare previously-implicit hoisted dependencies. package-lock.json changes mirror these exactly.
  • pnpm-workspace.yaml: fsevents: false is correct — Darwin-only optional dep whose node-gyp rebuild script should not run under pnpm.
  • pnpm-lock.yaml: Regenerated via pnpm import. Verified version changes are consistent with npm's locked versions (both zod@4.4.3 and supports-color@7.2.0 exist in npm's nested packages).
  • Tests: Comprehensive failure-branch coverage — version mismatch, stale knownNpmLockGaps (both arms), missing allowBuilds, aliased package names, empty packages section, git dependency matching, spec-scoped allowBuilds keys, and placeholder entries.
  • AGENTS.md: Procedural change from pnpm install --lockfile-only to corepack pnpm import correctly makes pnpm-lock.yaml derive from package-lock.json.

NEEDS_HUMAN:

  • The zod peer rebinding (@agentclientprotocol/sdk now resolves zod@4.4.3 instead of zod@3.25.76 in the importers section) is functionally correct per the SDK's peer range (^3.25.0 || ^4.0.0) but represents a behavioral change in which zod version satisfies that peer under pnpm. Worth confirming intentional.
  • eslint.config.js globs do not cover packages/web-shell/client/** (only src/**), so that workspace's shipped sources outside src/ are unchecked for extraneous dependencies. Minor coverage gap, not a blocker.

VERDICT: APPROVE

Core logic is correct and well-tested; the version agreement gate, build approval check, hoisted imports rule, and dependency declarations all work as intended with no reachable correctness defects.

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

Code Review: chore(pnpm): gate pnpm-lock on package-lock and declare hoisted imports

FINDINGS:

  • No blockers found.

CLEAN AREAS:

  • scripts/check-lockfile.js: Version agreement gate is correct. The direction (pnpm must not resolve versions npm has not locked) is the right one. npmPackageName handles aliased installs (details.name) and nested node_modules (lastIndexOf). pnpmPackageName correctly handles scoped packages (indexOf('@', 1) skips leading @). knownNpmLockGaps has staleness detection. Build approval check correctly filters non-boolean allowBuilds placeholders. CHECK_LOCKFILE_ROOT env var uses truthiness check (not ??) to guard against empty-string injection.
  • eslint.config.js: Hoisted imports rule is correctly scoped to packages/**/src/** and integrations/**/src/**, with test files excluded. The web-templates packageDir override is correct.
  • package.json changes (cli, core, vscode-ide-companion): Dependency additions correctly declare previously-implicit hoisted dependencies.
  • pnpm-workspace.yaml: fsevents: false is correct.
  • pnpm-lock.yaml: Verified version changes consistent with npm locked versions.
  • Tests: Comprehensive failure-branch coverage.
  • AGENTS.md: Procedural change is correct.

NEEDS_HUMAN:

  • zod peer rebinding is functionally correct but worth confirming intentional.
  • eslint.config.js globs do not cover packages/web-shell/client/**. Minor coverage gap.

VERDICT: APPROVE

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

Approve: Core logic is correct and well-tested. No blockers found. See detailed review in comments.

@chiga0

chiga0 commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Detailed Code Review

FINDINGS:
- No blockers found.

CLEAN AREAS:
- scripts/check-lockfile.js: Version agreement gate is correct.
  The direction (pnpm must not resolve versions npm has not locked) is right.
  npmPackageName handles aliased installs (details.name) and nested node_modules
  (lastIndexOf). pnpmPackageName correctly handles scoped packages (indexOf('@',1)
  skips leading @). knownNpmLockGaps has staleness detection. Build approval check
  correctly filters non-boolean allowBuilds placeholders. CHECK_LOCKFILE_ROOT env
  var uses truthiness check (not ??) to guard against empty-string injection.

- eslint.config.js: Hoisted imports rule correctly scoped to packages/**/src/**
  and integrations/**/src/**, with test files excluded. The web-templates packageDir
  override is correct for a package whose imports resolve through a different manifest.

- package.json changes (cli, core, vscode-ide-companion): Dependency additions
  correctly declare previously-implicit hoisted dependencies. package-lock.json
  changes mirror these exactly.

- pnpm-workspace.yaml: fsevents: false is correct — Darwin-only optional dep
  whose node-gyp rebuild script should not run under pnpm.

- pnpm-lock.yaml: Regenerated via pnpm import. Verified version changes
  (supports-color 8.1.1→7.2.0, zod peer rebinding) are consistent with npm's
  locked versions. Both zod@4.4.3 and supports-color@7.2.0 exist in npm's nested
  packages.

- Tests: Comprehensive failure-branch coverage — version mismatch, stale
  knownNpmLockGaps (both arms), missing allowBuilds, aliased package names,
  empty packages section, git dependency matching, spec-scoped allowBuilds keys,
  and placeholder entries.

- AGENTS.md: Procedural change from pnpm install --lockfile-only to
  corepack pnpm import correctly makes pnpm-lock.yaml derive from package-lock.json.

NEEDS_HUMAN:
- zod peer rebinding (@agentclientprotocol/sdk now resolves zod@4.4.3 instead of
  zod@3.25.76) is functionally correct per the SDK peer range (^3.25.0 || ^4.0.0)
  but represents a behavioral change. Worth confirming intentional.

- eslint.config.js globs do not cover packages/web-shell/client/** (only src/**),
  so that workspace's shipped sources outside src/ are unchecked for extraneous
  dependencies. Minor coverage gap, not a blocker.

VERDICT: APPROVE
REASON: Core logic correct and well-tested; no reachable correctness defects.

wenshao added a commit that referenced this pull request Sep 13, 2026
…xists

Merging main brought in #11625, which regenerated pnpm-lock.yaml and
dropped the vitest@3.2.7 snapshot keyed with @vitest/browser and
supports-color@8.1.1. The packages/browser-use importer still referenced
that key, so every Install leg failed with
ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY. Re-key the importer to the snapshot
its sibling packages already use; nothing else in the lockfile changes.
wenshao added a commit that referenced this pull request Sep 13, 2026
Picks up the lint-gate changes (eslint.config.js #11625, eslint.legacy-filenames.mjs #11443) that the Lint & Static freshness check flagged as stale on this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNKaCrgfft8Y1Cdps1cf8E
wenshao pushed a commit to wenshao/qwen-code that referenced this pull request Sep 14, 2026
…ness lane (QwenLM#11797)

Follow-ups left open when QwenLM#11625 merged: its three unresolved review threads,
the deferred finding in QwenLM#11681, and QwenLM#10524.

- allowBuilds decisions now honour the whole key the way pnpm does: a bare
  name covers every version, an exact `name@version` (or a `||` union) covers
  only those, and a range or source-like key decides nothing. Reducing a key
  to its name let an approval scoped to one version cover the next (R1-10).
- Pin `@agentclientprotocol/sdk>zod` to 3.25.76 so a pnpm worktree runs the
  zod the npm tree installs. Under zod 4 the SDK's own `instanceof z.ZodError`
  test stops matching and a malformed request answers -32602 instead of
  -32603; both versions are npm-locked, so the agreement gate cannot see the
  split (R1-1).
- Cover web-shell's shipped client sources with the undeclared-import rule;
  it is the one published package whose sources are not under src/. Measured
  cost today: zero violations (R2-6).
- Add a path-filtered workflow that regenerates pnpm-lock.yaml with
  `corepack pnpm import` and fails when the committed file differs, which is
  the freshness half set membership cannot cover (QwenLM#11681).
- setup-worktree: accept only an executable file on PATH, so a corepack left
  without its exec bit reports the actionable error instead of EACCES
  (QwenLM#10524).
- Name the esbuild divergence an accepted parity gap, and record what
  `fsevents: false` costs a macOS worktree.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.23.4.

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants