perf(cli): let tests resolve core modules individually - #10917
perf(cli): let tests resolve core modules individually#10917yiliang114 wants to merge 15 commits into
Conversation
…files importing the whole package Importing from the core package root pulls in its entire export graph — a bit over six hundred modules — however little of it a file actually uses. In a release run the cli workspace spent 2223s collecting modules against 1372s running tests, and a file that imports the package root costs about 11.5s before its first assertion where one importing a single module costs about 2s. cli's tsconfig already maps a wildcard subpath onto core's sources, so esbuild resolves per-module imports when it bundles. Vitest does not read tsconfig paths, and the alias list that stands in for them named only four subpaths, so those imports did not resolve under test at all. This adds the wildcard there. Expressing the alias list as an ordered array is what allows a pattern entry. The package root has to become an exact match in the process: as a string it would also match everything beneath it and rewrite each subpath into a path under index.ts. Two files move to per-module imports as a first check that the mapping holds end to end. Both were picked because nothing that depends on them replaces the core package with a mock factory — where a test does that, the mock stops intercepting once the code under test imports the module directly, so those call sites need their mocks moved in the same change and are left alone here.
|
Thanks for the PR — the motivation here is real and well measured. Template ✓ — all sections present, bilingual body complete. Problem: observed, not theoretical. #10908 documents concrete numbers (release run 33713579913: cli spends 2223s collecting vs 1372s running; a package-root import costs ~11.5s before the first assertion vs ~2s for a per-module import). Quantified and reproducible from CI data. Direction: aligned. CI collect-time is an active concern in this repo (#10908, and #10870 / #10869 landed in the same area this week). esbuild already resolves per-module imports through tsconfig paths while Vitest cannot — that gap is genuine, and closing it is the right move. Size: ~6 production source lines (two import swaps in Approach: the described change — ordered alias array, a wildcard mirroring the
Risk: no elevated-risk-path signals — none of the three changed files match the revert-correlated paths. Flagging the scope mismatch before diving deeper — moving on to code review. 🔍 中文说明感谢贡献!这个 PR 的动机是真实且有数据支撑的。 模板 ✓ —— 各节齐全,中英双语完整。 问题: 已观测到,而非理论问题。#10908 记录了具体数据(release run 33713579913:cli 模块收集 2223s 对比执行 1372s;包根导入到达第一条断言前约 11.5s,按模块导入约 2s)。量化且可从 CI 数据复现。 方向: 对齐。CI 收集耗时是仓库当前关注点(#10908,本周 #10870 / #10869 也落在同一领域)。esbuild 已通过 tsconfig paths 解析按模块导入,而 Vitest 不能——这个缺口真实存在,补上它是正确的。 规模: 约 6 行生产源码改动(两个文件的导入替换);其余约 340 行改动在 方案: 描述中的改动——有序 alias 数组、对齐
风险: 无高风险路径信号——三个改动文件均不命中与 revert 相关的路径。 先提出范围不一致的问题,再深入——进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewIndependent baseline first: for "let tests resolve core modules individually", the minimal change is one wildcard alias mirroring the Blockers
Minor
TestingEvidence carried: the PR's own CI, read via the API at review time (unattended run — no PR code executed here). The unit suite is still in flight on this commit (~30-minute suite; not polling — the finalize job updates the table below once CI settles). Static read of Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Once the diff is reduced to the intended change, the perf claim's oracle is CI's own timing — the collect-vs-run split from #10908 — rather than a 中文说明代码审查先说独立基线:要让测试按模块解析 core,最小改动是加一条对齐 tsconfig 阻塞项
次要
测试证据说明:本节引用的是 PR 自身 CI 在审查时刻经 API 读取的真实状态(无人值守运行——此处未执行任何 PR 代码)。该提交的单测套件仍在运行(约 30 分钟;不轮询——finalize 任务会在 CI 结束后更新下表)。对 上表由 finalize 任务在 CI 结束后原地更新。待 diff 缩减为预期改动后,性能结论的判据是 CI 自身的时间数据(#10908 中"收集 vs 执行"的拆分),而非 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 2/5 — the idea is right and the alias-array half is well done, but the diff as committed regresses several deliberate, recently-landed settings and breaks resolution in two places; it can't merge as-is. Stepping back: the motivation is genuine — #10908's numbers are concrete, and the gap between esbuild (reads tsconfig paths) and Vitest (doesn't) is real. My independent proposal for this problem was almost exactly the additive half of this PR: one wildcard alias mirroring the tsconfig rule, the root kept exact, existing named aliases untouched, two files migrated as proof. That part here is clean and even comes with the right ordering rationale. What I can't sign is the rest. This commit's The fix is small: rebuild the commit on current — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
@yiliang114 Needs some rethinking before this can merge — full notes in the review comment above. The short version: the alias-array change itself is good, but this commit also drops settings that are live on main (ECS timeouts/maxWorkers, node-default environment, the #9149 globalSetup guard, coverage gating, the unhandled-error exemption — several pinned by scripts/tests/unit-vitest-configs.test.ts), removes three core aliases that still have import sites, and the new path-style imports don't resolve under npm run dev (no core exports entries). Rebuilding the commit on current main with only the alias restructuring + the two migrations should get it home. 🙏
中文说明
合并前需要重新考虑——完整说明见上方审查评论。简版:alias 数组改动本身没问题,但本提交同时删掉了 main 上生效的配置(ECS 超时/maxWorkers、node 默认环境、#9149 的 globalSetup 守卫、覆盖率门控、未处理错误豁免——其中数项被 scripts/tests/unit-vitest-configs.test.ts 钉住),移除了三个仍有导入点的 core alias,且新的路径风格导入在 npm run dev 下无法解析(core exports 没有对应条目)。基于当前 main 重建提交,只保留 alias 重构和两个文件的迁移,应该就能过。🙏
The previous commit was assembled from a working copy that predated main by several weeks, so it silently reverted this file to that older state. Four named core subpaths added since — envVarResolver, noFollowOpen, subSessionConstants and toolWriteOrigin — disappeared with it, and the new wildcard then claimed those specifiers and pointed them at files that do not exist. 257 test files failed to load as a result. All eight named subpaths are restored and kept ahead of the wildcard, with a comment saying why that order matters and what a contributor adding a ninth has to do. None of the eight can be derived from its specifier, so none of them can be folded into the pattern. The two migrated source files are rebuilt on their current contents for the same reason; one of them had also been reverted by a line.
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 12 scenario(s). — Qwen Code · serve A/B |
|
Cross-linking the plan: #10909 is the document this PR is phase ① of, and it names this exact step. Its §6.1 (「迁移写法(已确定)」) records the resolution chain and then says the wildcard alias has to come first — "vitest alias:目前只有 4 个具名 subpath …… 没有通配。深路径导入今天在测试里会解析失败,必须先补一条通配 alias,这是 phase ① 的第一步". This PR is that step. It also confirms the §6.1 table under vitest rather than only from the tsconfig rule: I checked this PR against §6.3, the plan's largest correctness risk, and it is clean — but the reason is worth writing down. §6.3's failure mode is that a test which mocks the core barrel silently stops intercepting once the code under test imports deeply: the suite stays green while testing something else. On this branch 138 cli test files carry
It is safe, and not by luck of ordering: line 42 replaces the whole The general rule that follows, for whoever writes the codemod: a barrel mock only breaks when the migrated module is the one the mock was meant to intercept through. A module that is itself stubbed is inert regardless of how it imports. That distinction is what makes the 138 tractable — the scope is not "138 files to audit" but "the subset whose barrel mock is intended to reach into a migrated module", and it should be computed per migration batch rather than up front. Why the payoff is worth pushing on. #10909's
中文说明互相链接一下计划侧:#10909 就是本 PR 所属的那份文档的 phase ①,而它点名了这一步。 其 §6.1(「迁移写法(已确定)」)记录了解析链,然后说明通配 alias 必须先做 —— 「vitest alias:目前只有 4 个具名 subpath …… 没有通配。深路径导入今天在测试里会解析失败,必须先补一条通配 alias,这是 phase ① 的第一步」。本 PR 就是这一步。它同时在 vitest 下印证了 §6.1 的表格,而不只是从 tsconfig 规则推导: 我拿本 PR 对着 §6.3(该计划最大的正确性风险)核对过,结论是干净的 —— 但原因值得写下来。 §6.3 的失效模式是:一旦被测代码改成深路径导入,mock 了 core barrel 的测试就会静默失去拦截,套件仍然是绿的,但测的东西变了。在本分支上,有 138 个 cli 测试文件带
它是安全的,而且不是靠顺序上的巧合:第 42 行把 由此得出的一般规则,供写 codemod 的人参考:barrel mock 只有在「被迁移的模块正是该 mock 意图经由其进行拦截的那个模块」时才会失效。 一个自身已被整体 stub 的模块,无论怎么导入都是惰性的。正是这个区分让 138 这个数字变得可处理 —— 范围不是「138 个文件要审」,而是「其 barrel mock 意图伸进某个被迁移模块的那个子集」,并且应当按每一批迁移分别计算,而不是一次性预估。 为什么这个收益值得推进。 #10909 的
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "agent 2": none.**.
中文说明
未探索到全部深度(达到工具调用预算):"agent 2":none.**。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
Note on how this relates to the rest. #10957 now carries the full change against This one stays open because it is the smallest reviewable piece and is independently correct: the resolver mapping plus two files as a smoke test. Merging it first simply shrinks #10957. Merging #10957 first makes this empty. Either order works. Worth carrying across from what the larger run found: this PR's own checks are not the whole story either. A PR based on anything other than 中文说明关于本 PR 与其余部分的关系。 #10957 现已基于 本 PR 保持开启,因为它是最小的可审阅单元且本身正确:解析映射加两个文件作为冒烟验证。先合本 PR 只会让 #10957 的 diff 缩小;先合 #10957 则本 PR 变空。两种顺序都可以。 另外值得带走的一点:本 PR 自己的检查同样不能说明全部问题。在此仓库,base 不是 |
The tipHistory / RemoteInputWatcher migration used .js-suffixed subpath specifiers that match no entry in packages/core/package.json exports, so the built-but-unbundled CLI (npm start / build-and-start, whose tsc dist keeps specifiers) crashed at module load with ERR_PACKAGE_PATH_NOT_EXPORTED while typecheck (tsconfig paths), unit tests (vitest wildcard alias) and the bundle (esbuild paths) all bypassed exports and stayed green. Switch the two files to named subpaths (storage, atomicFileWrite, debugLogger), following the convention of the eight existing entries, and add the matching exports entries and vitest aliases. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtlz4ubdgq
Every core subpath specifier statically imported from packages/cli/src must resolve through packages/core/package.json exports in a real child node process — no vitest aliases, no tsconfig paths. Without the matching exports entries the built-but-unbundled CLI dies with ERR_PACKAGE_PATH_NOT_EXPORTED while every gate that bypasses exports stays green; this guard goes red the moment an entry is removed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtlz4ubdgq
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
— qwen3.8-max via Qwen Code /review (v0.23.0)
The named subpaths cli sources import from @qwen-code/qwen-code-core had no named `paths` entries, so the wildcard composed nonexistent files and esbuild (bundle) plus the dev loader chain fell back to the exports map, loading packages/core/dist copies while every package-root import loads packages/core/src — two instances of barrel-exported, stateful modules (debugLogger, storage, atomicFileWrite, envVarResolver, toolWriteOrigin, memoryScopes) in one process. Add the six missing named entries beside the existing ones so bundle and dev resolve all subpaths into the core src tree, consistent with the vitest alias list. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtme4yqxhl
Complete the resolution guard and harden its probe: - Cover every core subpath statically imported from packages/cli/src (adds toolWriteOrigin and memoryScopes) plus the subpaths npm start reaches through @qwen-code/acp-bridge (subSessionConstants, goalWire, transcriptRecords), instead of the previous five specifiers. - Pin each specifier to its expected dist target: assert the resolved URL equals the pinned path and the target file exists, so a typo'd or redirected exports target fails the guard (import.meta.resolve alone accepts both). This makes a built core dist a prerequisite, which vitest-global-setup already fail-fasts on. - Add a bundle-resolution guard: esbuild-bundle every cli/src core subpath under packages/cli/tsconfig.json and assert no input comes from packages/core/dist, pinning the tsconfig paths entries. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtme4yqxhl
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 4": none — all planned checks completed within budget (~7 tool calls)..
Convergence: round 3 posted 3 inline comment(s), 3 of them reported for the first time; the previous round posted 3 (3 new). Findings keep coming back to the same files: scripts/tests/core-subpath-exports-resolution.test.js (findings in round 2; 3 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent 4":none — all planned checks completed within budget (~7 tool calls).。
收敛情况:第 3 轮发布了 3 条行内评论,其中 3 条是首次提出;上一轮发布了 3 条(其中 3 条首次提出)。发现反复回到同一批文件:scripts/tests/core-subpath-exports-resolution.test.js(第 2 轮已出过发现,本轮又有 3 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
- Add goalWire to the bundle arm and named tsconfig paths entries in packages/cli and packages/acp-bridge: acp-bridge's transcript-replay imports @qwen-code/qwen-code-core/goalWire, and without a named entry the wildcard resolved to a nonexistent ../core/src/goalWire and fell back to the exports map, bundling packages/core/dist/src/goals/ goal-wire.js next to src-resolved core modules — the module-identity split #10908's Known risks name. Verified with an esbuild metafile probe: dist input before the paths entry, src input after. - Normalize esbuild metafile input keys to forward slashes before the dist-leak filter and src-target assertions so the guard behaves the same on Windows runners, where esbuild emits backslash separators. - Correct the header: this lane's vitest config does not wire scripts/vitest-global-setup.js (it is a globalSetup only in the packages/core and packages/cli configs, and its DIST_PREREQUISITES has no key covering this lane), so a missing dist surfaces as the existence assertion naming the absent file. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtmourmvi4
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Convergence: round 4 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 3 (3 new). Findings keep coming back to the same files: scripts/tests/core-subpath-exports-resolution.test.js (findings in round 3; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. No Critical finding is open on this round, so merging and moving the remaining Suggestion threads to a follow-up issue is available as an ending — a merged pull request cannot diverge further. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
收敛情况:第 4 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 3 条(其中 3 条首次提出)。发现反复回到同一批文件:scripts/tests/core-subpath-exports-resolution.test.js(第 3 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。本轮没有未决的 Critical,因此"合入后把剩余 Suggestion 线程转到后续 issue"是一个可选的结束方式——已合入的 PR 不会继续发散。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.0)
The bundle arm seeded goalWire into the cli-routed buildSync case, which passes an explicit packages/cli/tsconfig.json — but the shipped bundle resolves that import under packages/acp-bridge/tsconfig.json (mainBuild in esbuild.config.js carries no tsconfig option, so esbuild discovers the nearest tsconfig per importing file, and goalWire is imported only from packages/acp-bridge/src/transcript-replay.ts). The acp-bridge goalWire paths entry was therefore guarded by no arm, and transcriptRecords / subSessionConstants were probed by none at all: removing the acp-bridge goalWire entry left the test green while a production-shaped build pulled packages/core/dist inputs. Add an acp-bridge-routed buildSync case — no tsconfig option, resolveDir packages/acp-bridge/src, seeding goalWire, transcriptRecords, subSessionConstants and noFollowOpen — asserting no input lands under packages/core/dist and each expected core src target is present. With the new arm, removing the acp-bridge goalWire entry goes red (4 dist leaks) where previously nothing did. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtmxfessij
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R5-1 hand-maintained specifier coverage maps — already reported (R2-1 thread, comment 3929721370)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 1a": executing scripts/tests/core-subpath-exports-resolution.test.js itself — the review worktree has no node_modules and a full monorepo npm ci + build exceed….
Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:
scripts/tests/core-subpath-exports-resolution.test.js:103 — [test] D5-1 missing core dist surfaces as a cryptic URL-spelling mismatch instead of the header's promised named-file diagnostic; the existsSync line is unreachable
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent 1a":executing scripts/tests/core-subpath-exports-resolution.test.js itself — the review worktree has no node_modules and a full monorepo npm ci + build exceed…。
收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.0)
Take the union of core package exports (storage/atomicFileWrite/ debugLogger from this branch plus conversationsRuntimeMarker from main) and register the conversationsRuntimeMarker vitest alias ahead of the wildcard. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtn4km2niw
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- derive-the-specifier-list recommendation (scripts/tests/core-subpath-exports-resolution.test.js) — already reported (R2-1 thread, comment 3929721370)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Deferred under the convergence posture (round 6, not a blocker) — recorded, not requested in this round:
scripts/tests/core-subpath-exports-resolution.test.js:209 — [review] D6-1 esbuild probe-and-assert block duplicated across the two guard arms; a future leak-filter/normalization change mirrored into only one arm leaves the other silently pa…
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.0)
R6-1: @qwen-code/qwen-code-core/conversationsRuntimeMarker is statically imported from packages/cli/src (config/shared-env-keys.ts, serve/run-qwen-serve.ts) but carried no named entry in packages/cli/tsconfig.json paths and was seeded into neither guard map. The wildcard composed a nonexistent ../core/src target and fell back to the exports map, so the cli-routed bundle loaded a packages/core/dist copy next to the core src copy while every guard arm stayed green. Add the named paths entry beside the ones this PR already adds and seed both guard maps with the specifier. Co-authored-by: Qwen-Coder <qwen-coder@alibaba-cloud.com> Patrol-Run: qwen-pr-closeout/jmtngpuscjl
R6-2: the cli bundle reaches sdk sources through the cli tsconfig @qwen-code/sdk/* mapping (ui/utils/export/export-transcript-document.ts imports @qwen-code/sdk/daemon/transcript, which re-exports from daemon/ui/chat-record-transcript.ts), and chat-record-transcript.ts imports @qwen-code/qwen-code-core/transcriptRecords. With no paths in packages/sdk-typescript/tsconfig.json, mainBuild's per-importing-file tsconfig discovery found no mapping and fell back to the exports map, bundling a packages/core/dist copy while the guard suite stayed green. Add the named entry to the sdk tsconfig, mirroring acp-bridge, and a third guard arm probing the sdk route. Co-authored-by: Qwen-Coder <qwen-coder@alibaba-cloud.com> Patrol-Run: qwen-pr-closeout/jmtngpuscjl
The paths entry added for the sdk-routed guard resolves @qwen-code/qwen-code-core/transcriptRecords to the core source file, which in the composite tsc --build graph belongs to the core project. Without a project reference the cli build failed with TS6059/TS6307; declare the core reference, mirroring packages/acp-bridge/tsconfig.json. Verified with npm run build in packages/cli and npm run typecheck in packages/sdk-typescript. Co-authored-by: Qwen-Coder <qwen-coder@alibaba-cloud.com> Patrol-Run: qwen-pr-closeout/jmtngpuscjl
The paths entry added to tsconfig.json for the bundle's esbuild discovery is inherited by tsconfig.build.json; in that plain declaration build it pulled core sources into the program, re-rooted the inferred rootDir above the package, and nested every emitted .d.ts under dist/sdk-typescript/src/, dropping dist/daemon/index.d.ts that web-shell imports (TS7016). Reset paths in tsconfig.build.json so the declaration build resolves core through its exports map. Verified with npm run build in packages/sdk-typescript: dist/daemon/index.d.ts restored and the daemon browser bundle byte-identical (236252 bytes, its pre-existing warning threshold breach is unchanged). Co-authored-by: Qwen-Coder <qwen-coder@alibaba-cloud.com> Patrol-Run: qwen-pr-closeout/jmtngpuscjl
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- the derive-the-specifier-list recommendation for the guard's hand-maintained maps (this round's instance: userPromptSubmitContext probed by no arm) — already reported (R2-1 thread, comment 3929721370)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Deferred under the convergence posture (round 7, not a blocker) — recorded, not requested in this round:
packages/sdk-typescript/src/daemon/ui/chat-record-transcript.ts:11 — [probe] D7-1 sdk-routed acp-bridge subpath imports bundle dist copies while cli-routed imports bundle src — transcript-replay/bridgeTypes/mcpTimeouts doubled in the shippe…packages/sdk-typescript/tsconfig.reference.json:8 — [review] D7-2 added comment justifies the ../core reference with a paths inheritance the same diff removes ("paths": {} in tsconfig.build.json); tsc --showConfig reports paths: {}scripts/tests/core-subpath-exports-resolution.test.js:241 — [review] D7-3 the three esbuild guard arms are near-verbatim copies of one ~35-line block; a shared-logic fix must be applied in three places by handscripts/tests/core-subpath-exports-resolution.test.js:134 — [review] D7-4 the cli-routed arm probes goalWire through packages/cli/tsconfig.json but no production route uses it; the dead cli entry and the probe pin each other and block clean…packages/sdk-typescript/tsconfig.build.json:18 — [review] D7-5 the "paths": {} reset guarding the sdk declaration dist layout has no witness; a regression ships broken ./daemon types with every lane greenscripts/tests/core-subpath-exports-resolution.test.js:108 — [review] D7-6 the header promises the existence assertion names the absent file, but the failure output carries no path
[Critical] R1-2: [fails-closed] [regression] The round-2 fix (b5465637b) closed this thread's bundle lane but the dev lane still stands, verified by probe at this commit: scripts/dev.js's generated loader intercepts only the exact package root ('@qwen-code/qwen-code-core'), and Node never consults the tsconfig paths entries that fix added, so under npm run dev the migrated subpath imports — '@qwen-code/qwen-code-core/debugLogger' in RemoteInputWatcher.ts, '/storage' and '/atomicFileWrite' in tipHistory.ts — fall through to the exports map and load packages/core/dist/** while every package-root import loads packages/core/src. Two instances of debugLogger, storage and atomicFileWrite in one dev process. Observable impact: Config binds the debug session on the src copy (setDebugLogSession(this), packages/core/src/config/config.ts), so RemoteInputWatcher's dist-copy REMOTE_INPUT logger reads an empty session and every debugLogger(...) call there silently no-ops whenever QWEN_DEBUG_LOG_FILE is enabled — it worked before this diff migrated the files. Storage carries split static state (runtimeBaseDir, AsyncLocalStorage), latent today only because tipHistory happens to call the stateless getGlobalQwenDir(). This violates the R1-1 thread's acceptance constraint (comment 3925934275). Witness: a dev-chain probe (loader replicating scripts/dev.js verbatim, run at HEAD) resolved ROOT to packages/core/index.ts (src) and SUBPATH to packages/core/dist/src/utils/debugLogger.js, with 'same module instance? false' and the REMOTE_INPUT (dist) line ABSENT from the session log (silently no-op'd); the flip check with the loader intercepting the subpath into src resolved SUBPATH to packages/core/src/utils/debugLogger.ts with 'same module instance? true' and both log lines written. Fix: extend scripts/dev.js's loader resolve hook so the core subpath specifiers short-circuit to the matching packages/core/src files, or revert the two migrated files to package-root imports and land the migrations together with the scheme reconciliation; keep the new exports entries (the built-but-unbundled CLI needs them) and the tsconfig paths entries (the bundle lane).
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
收敛姿态下延后(第 7 轮,非阻断)——已记录,本轮不要求修改:共 6 条(原文未翻译,列表见上方英文部分)。
[Critical] R1-2: [fails-closed] [regression] The round-2 fix (b5465637b) closed this thread's bundle lane but the dev lane still stands, verified by probe at this commit: scripts/dev.js's generated loader intercepts only the exact package root ('@qwen-code/qwen-code-core'), and Node never consults the tsconfig paths entries that fix added, so under npm run dev the migrated subpath imports — '@qwen-code/qwen-code-core/debugLogger' in RemoteInputWatcher.ts, '/storage' and '/atomicFileWrite' in tipHistory.ts — fall through to the exports map and load packages/core/dist/** while every package-root import loads packages/core/src. Two instances of debugLogger, storage and atomicFileWrite in one dev process. Observable impact: Config binds the debug session on the src copy (setDebugLogSession(this), packages/core/src/config/config.ts), so RemoteInputWatcher's dist-copy REMOTE_INPUT logger reads an empty session and every debugLogger(...) call there silently no-ops whenever QWEN_DEBUG_LOG_FILE is enabled — it worked before this diff migrated the files. Storage carries split static state (runtimeBaseDir, AsyncLocalStorage), latent today only because tipHistory happens to call the stateless getGlobalQwenDir(). This violates the R1-1 thread's acceptance constraint (comment 3925934275). Witness: a dev-chain probe (loader replicating scripts/dev.js verbatim, run at HEAD) resolved ROOT to packages/core/index.ts (src) and SUBPATH to packages/core/dist/src/utils/debugLogger.js, with 'same module instance? false' and the REMOTE_INPUT (dist) line ABSENT from the session log (silently no-op'd); the flip check with the loader intercepting the subpath into src resolved SUBPATH to packages/core/src/utils/debugLogger.ts with 'same module instance? true' and both log lines written. Fix: extend scripts/dev.js's loader resolve hook so the core subpath specifiers short-circuit to the matching packages/core/src files, or revert the two migrated files to package-root imports and land the migrations together with the scheme reconciliation; keep the new exports entries (the built-but-unbundled CLI needs them) and the tsconfig paths entries (the bundle lane).
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
CI attribution for head 4653a34 (Test ubuntu-latest cancelled after exactly 2h0m, run 33924707799): the 'Run tests and generate reports' step was cap-killed with no failing suite output — the shared self-hosted runner saturation shape seen on this branch before (vitest RPC timeouts under load, job hits its 2h ceiling). Not PR-caused; no diff files implicated. Triggering a --failed rerun. |
|
CI attribution for Test (ubuntu-latest) attempt-2 failure on head 4653a34 (job 101218231243, run 33924707799): shared-runner disk pressure, not this PR. The job uploaded the runner's own |
|
Test ubuntu attempt-3 attribution (run 33924707799): not PR-caused.
Rerunning the failed jobs once more; if attempt-4 repeats the all-green-but-exit-1 shape, this leg needs the runner pool looked at, not another rerun. |
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 263 passed · 0 failed · 263 total Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:263 通过 · 0 失败 · 263 总计 抖动门:✅ 1 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10917 — deep verificationVerdict: The central claim is proven load-bearing: 5 of 16 probe specifiers flip from unresolvable to resolved between the base and head alias configs, and nothing else moves. The perf premise is confirmed by measurement (11.30 s package-root premium vs 0.39 s for one module; 15.34 s saved on the migrated files' own suites). No regression reproduced. The findings below are all latent, coverage, or documentation items — none is a live break, and each is bounded with what does not hold. 中文摘要结论: A/B 结论(核心主张成立):见下文「Central claim + A/B」表。base 与 head 两套 alias 配置下跑同一组 16 个探针 import, 性能前提(实测支持):固定开销 2.95 s;单个 core 模块边际成本 0.39 s;包根边际成本 11.30 s(28.8 倍),与 PR 描述的「约 11.5 s / 约 2 s」吻合。两个迁移文件自身的 4 个用例:head 17.72 s vs 还原成 barrel 导入后 33.06 s,省 15.34 s,两侧均全绿。 Findings(均为潜在/覆盖率/文档问题,无线上破坏):
未覆盖范围:逐 commit 归因(快照列 14 个 commit,本地 shallow 只可达 1 个);完整 cli/core 套件; ScopeCentral claim — the cli vitest alias list, restructured as an ordered array carrying a pattern entry, makes per-module core subpath imports resolve under test where they previously could not; the two migrated files behave identically. Secondary claims
Explicitly out of scope: the full cli suite (1011 test files), the full core suite, Central claim + A/B
¹ The load-bearing number: 5 of 16 flip from broken to fixed (the three Mechanism, observed rather than inferredvitest's json reporter carries no message for a collection-time import error — the observable symptom on base is a bare
(✗ = the alias rewrote the specifier to a path that does not exist.) This confirms the head config's comment precisely: a string root alias prefix-matches
Mutation matrix
Perf premise (S3) — confirmed
Ratio 28.8×. The PR's "roughly 11.5 s" and "about 2 s" are corroborated (a first independent run measured 12.18 s and 0.43 s). The real end-to-end delta: reverting only the two migrated source files to their base barrel imports, holding the config at head, and running the four suites that cover them (
15.34 s saved, both arms green — behaviour unchanged, as claimed. Sources restored byte-identically (sha verified). The PR's own named risk (mock-factory bypass) — ruled out, and demonstratedThe description says files whose dependents replace core with a mock factory were deliberately untouched, because a direct subpath import stops being intercepted. That is a real mechanism, and It does not bite this PR:
Pre-existing and not caused by this PR: Guard test is not vacuous (S1)
The typo row proves the pinned-URL check is load-bearing beyond mere file existence. The three new S2 — declaration build
The layout claim reproduces exactly, including a core source being emitted into the sdk output directory. Scratch CorrectionsThese are corrections to the description and to code comments, not requests to change behaviour.
FindingsOrdered by severity. None is a blocker; none reproduced a live break. 1. Nothing in the repo pins the pattern entry — the PR's headline mechanism (Suggestion, test-coverage)Deleting the pattern entry from Reproduce: Cause: all 12 named subpaths have explicit alias entries, and the census ( The pin cannot live in the new guard test, which never reads the cli vitest config (its arms are plain-Node and esbuild). It belongs either in a cli-side test that imports a wildcard-only specifier, or in 2. The pattern entry widens the test route past what
|
| specifier | vitest at head | plain Node |
|---|---|---|
core/storage |
core/src/config/storage.ts |
OK → core/dist/src/config/storage.js |
core/debugLogger |
core/src/utils/debugLogger.ts |
OK |
core/utils/promptIdContext |
core/src/utils/promptIdContext.ts |
ERR_PACKAGE_PATH_NOT_EXPORTED |
core/config/models |
core/src/config/models.ts |
ERR_PACKAGE_PATH_NOT_EXPORTED |
core/src/utils/promptIdContext |
core/src/src/utils/promptIdContext ✗ |
OK (via ./src/*) |
Reproduce: h1d-resolver.mjs (sections B and C).
This is precisely failure mode #1 in the new guard test's own header comment. The guard exists to catch it, but expectedDistTargets is a hardcoded list of 11 specifiers, not derived from the sources — so it pins today's set and cannot see tomorrow's. Before this PR the test route rejected any unnamed subpath, which caught the mistake at authoring time; now the wildcard admits it.
What does not hold (bounds):
- No live break. The census over
packages/{cli,acp-bridge,sdk-typescript}/srcfound 12 distinct core specifiers in use and 0 without anexportsentry. - Typecheck and the bundle agree with tests, not with
npm start: cli's tsconfig has the same@qwen-code/qwen-code-core/*→../core/src/*wildcard, so all three resolve tocore/src. Only the built-but-unbundled CLI (npm start,npm run build-and-start) breaks. The failure is loud — a load-time throw naming the specifier — not silent. - The last row is not a regression:
core/src/*fails under vitest on base too (rewritten toindex.ts/src/…), by a different mechanism.
Measured candidate fix (not applied to the PR)
Keep the pinned list — it pins the dist targets, which a census cannot derive — and add one arm that derives the in-use specifier set from the sources and requires it to equal the pinned set. Full patch: candidate-fix-guard-census.mjs.snippet; trial: h9-fix-trial.mjs. Witness: 06-candidate-fix-trial-hostile-fixture.png. 10/10 assertions.
| state | tests | result |
|---|---|---|
| shipped guard, unmodified tree | 14 | 14 green |
| patched guard, unmodified tree | 15 | 15 green, 0 red — zero collateral, exactly one test added |
| patched guard + hostile fixture¹ | 15 | 14 green, 1 red — the new arm only |
| shipped guard + same hostile fixture | 14 | 14 green, 0 red, exit 0 — gap confirmed |
¹ packages/cli/src/__verify_hostile__/imports-unexported-subpath.ts importing @qwen-code/qwen-code-core/utils/promptIdContext.
The suite is green both with and without the patch on the unmodified tree, which is the expected unpinned-axis signal: only the hostile fixture distinguishes them. The patch should ship together with a fixture that exercises it.
3. tsconfig.reference.json's references entry carries a rationale this PR voided (Nice to have)
The comment names TS6059/TS6307 as the consequence of omitting the entry; measured, the build succeeds without it (exit 0, no TS codes), because the same PR reset paths to {} in the parent config. See Correction 3.
The entry is not a no-op: tsc --build --dry --verbose shows it puts the core project in the build graph (head: ['../core/tsconfig.json', 'tsconfig.reference.json'], mutant: ['tsconfig.reference.json']). Standalone under --force that costs 100.9 s vs 14.2 s — but that is a worst case for a path the repo does not run: root scripts/build.js builds packages/core first by explicit order, and packages/cli/tsconfig.json already references ../core directly, so in the real build path the edge adds graph structure rather than work. Not measured: whether it is required on a fresh clone with no packages/core/dist (that would mean destroying the 64 MB built dist). Suggested action is to fix the comment, not to remove the entry.
4. One named subpath is in the vitest alias list but not in cli's tsconfig paths (Nice to have, pre-existing)
h7-route-census.mjs compared all four lists. 7/7 assertions.
| specifier | core exports |
cli tsconfig | acp tsconfig | sdk tsconfig | cli vitest | guard test |
|---|---|---|---|---|---|---|
| 11 others | X | X | (4 of them) | (1) | X | X |
userPromptSubmitContext |
X | — | — | — | X | — |
Consequence, measured: bundling that specifier under cli's tsconfig lands in core/dist/src/hooks/user-prompt-submit-context.js (plus a core/dist copy of transcript-records), with 0 files from core/src — the two-copies-of-one-module shape the guard's comment #2 names, while cli's vitest alias resolves it to core/src.
What does not hold: nothing under packages/{cli,acp-bridge,sdk-typescript}/src imports it (the only importers are integration-tests/, which has its own named tsconfig entry); and the module is stateless — pure functions and re-exported string constants, no AsyncLocalStorage, no top-level mutable state — so a duplicated copy is not observable in the way the comment fears. Pre-existing, not introduced here: base's cli tsconfig named only 3 subpaths. Worth noting because this PR is the change that systematically closed the gap — it added named cli tsconfig entries for goalWire, storage, atomicFileWrite, debugLogger, envVarResolver, toolWriteOrigin, memoryScopes and conversationsRuntimeMarker, and left this one behind.
5. The exact-match regex for the package root is redundant defence (no action)
Replacing /^@qwen-code\/qwen-code-core$/ with the string '@qwen-code/qwen-code-core' in the same position produces resolution identical to head on all 16 specifiers, because the pattern entry precedes it. The comment justifying the regex ("Spelled as a string it would also match everything beneath it") is mechanically true — proven in h1d section A — but only for an arrangement the shipped ordering does not have. Classification: redundant defence, correct as it stands; it is cheap insurance against a future reorder, and mut-root-string-first shows what that reorder would cost (all 12 subpaths). No change requested.
Not covered
- Per-commit attribution. The snapshot lists 14 commits;
git rev-list HEAD^1..HEAD^2returns 1, andgit rev-parse --is-shallow-repositoryistrue(depth-2 merge-ref checkout). Only the aggregateHEAD^1..HEADdiff was verified. Notegit rev-list --countreturned a plausible1rather than erroring, which is exactly the silent-gap shape — it was cross-checked against the snapshot'scommitsarray, and they disagree. baseRefOiddrift. The snapshot'sbaseRefOidisdff26740f7ff78065a2a6e4b81f8b86653866f7e, not the merge-ref base tip74fe3a65…used for every control here. Per the CI contractHEAD^1is authoritative for a merge-ref checkout; the difference is recorded rather than reconciled.- Trial merge into current
main. Not done — shallow clone, no network. Whethermainhas touched any of these 10 files since the merge base is unknown. - Full cli suite (1011 test files) and full core suite. The PR's CI figures (cli 2223 s collect / 1372 s run, core 546 s / 251 s, release run 33713579913) are author claims and were not reproduced. The
~6269 sceiling printed byh5-perf.mjsis arithmetic on a measured per-file premium × a measured file count, not a measurement, and is optimistic: files share module graphs and vitest evaluates a module once per worker, not once per file. npm run bundle/check:serve-fast-path-bundle. Not run. The bundle route was exercised through the guard test's three realesbuild.buildSyncarms instead, which is the resolution question the bundle raises — but no bundle artifact was produced or byte-compared.- macOS and Windows. The PR's own test matrix is
⚠️ on all three OSes. Only Linux was measured. The guard test's backslash normalization (input.replace(/\\/g, '/')) is untested here on a platform that produces backslashes. - Fresh-clone necessity of the
referencesentry (Finding 3) — would require destroying and rebuilding the 64 MBpackages/core/dist. - The
~/.qwen/tip_history.jsonwrite path inAppContainer.test.tsxwas traced statically (by the mock-bypass sweep), not executed. It is pre-existing and unchanged by this PR either way. packages/coreunit tests were not run; this PR changes onlypackages/core/package.jsonthere, and that change was verified through the plain-Node and esbuild routes.
Methodology
Everything ran in the CI verify container (node:22-bookworm, node v22.23.2, vitest 3.2.7) on the pre-built refs/pull/10917/merge tree at 4653a34. Ten assertion-bearing harnesses (.mjs, in this directory) plus a specifier census (h0) drove real code — no stub of anything under test: real vitest runs of real probe files, Vite's own pluginContainer.resolveId, real child node processes for import.meta.resolve, real esbuild.buildSync, real tsc. Controls differ from head by exactly one hunk each; the base arm is git show HEAD^1:<file> verified byte-identical by diff.
Internal workspace links were checked before any control was trusted: readlink -f node_modules/@qwen-code/{qwen-code-core,acp-bridge,sdk} all resolve into this tree (/__w/qwen-code/qwen-code/packages/…), so in-place mutations of packages/core/package.json and the tsconfigs are visible to the plain-Node and esbuild routes. package.json/package-lock.json dependency trees are untouched by this PR, so reusing the root node_modules for base arms is a clean control. Every harness that mutates the tree restores it and asserts a sha256 match afterwards; git status --porcelain is empty at the end and packages/cli/vitest.config.ts still hashes to bc2aae7f….
Expected base-arm reds are encoded as passing assertions inside the owning harness, so fail counts only unexpected outcomes — hence 0 fail with a findings verdict.
Harness defects found and fixed during the round, recorded because each initially produced false signal and a reader judging the counts is entitled to them:
- a mutation emitted an object-form alias entry into an array-form list, so one arm's config failed to load and every cell read
n/a(fixed; that arm now runs); - a bulk find-and-replace also rewrote a scoring condition, reporting
fail=38against a matrix in which every cell already matched its expectation (fixed; the same run then scored 97/0); - an expectation that the base arm's error text would name the
index.tsrewrite — the rewrite is real but invisible in the symptom, so the mechanism was moved to a resolver-level instrument (h1d) that can see it; - a
vi.mockfactory closing over an unhoisted binding, the gotcha AGENTS.md documents (fixed withvi.hoisted()); - two assertions encoding invariants stricter than the code declares — the PR's "about thirty … for the next batch" read as a total, and the new guard's hardcoded list held to every
exportsentry rather than its stated scope of specifiers actually imported frompackages/cli/src. Both were reframed to assert the measured state, and the underlying observations are reported as Findings 2 and 4 rather than counted as failures.
h8-gates.mjs proves the typecheck gate live by planting a type error and confirming tsc names the file.
Raw per-cell logs, per-arm vitest json reports, tsc and esbuild outputs, and the six evidence PNGs are all in this directory. Assertion counts per harness: h1 97, h1c 18, h1d 42, h3 46, h4 15, h5 8, h6 11, h7 7, h8 9, h9 10 → 263.
Flakiness gate log
rounds=5 files=1 skipped=0
file scripts/tests/core-subpath-exports-resolution.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/core-subpath-exports-resolution.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
scripts/tests/core-subpath-exports-resolution.test.js: PPPPP
verdict: pass
summary: 1 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · scripts/tests/core-subpath-exports-resolution.test.js: P (exit 0)
round 2 · scripts/tests/core-subpath-exports-resolution.test.js: P (exit 0)
round 3 · scripts/tests/core-subpath-exports-resolution.test.js: P (exit 0)
round 4 · scripts/tests/core-subpath-exports-resolution.test.js: P (exit 0)
round 5 · scripts/tests/core-subpath-exports-resolution.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
The dev loader intercepted only the exact package root, so a named core subpath fell through to the package's exports map and loaded packages/core/dist while every package-root import loaded packages/core/src. One dev process then held two instances of the same module: Config binds the debug session on the src copy (setDebugLogSession(this)), so RemoteInputWatcher's dist-copy REMOTE_INPUT logger read an empty session and every debugLogger(...) call there silently no-oped with QWEN_DEBUG_LOG_FILE enabled. Storage split its static state the same way. Derive the interception map from the core exports map so every named subpath short-circuits to its packages/core/src file and stays covered as subpaths are added. The exports entries (built-but-unbundled CLI) and the tsconfig paths entries (bundle lane) are untouched. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtow5pgslz
|
Fixed the round-7 body-only Critical (ledger Fix — 27185b6, The loader's resolve hook now derives its interception map from core's own Kept, as required: the new Confirmed real at 4653a34, and confirmed flipped. Probes ran under the loader Module resolution + identity: The observable failure reproduced and cleared too. Binding the session the way So the silent no-op was real, and it is gone — not merely quieter. Regression test — added to the existing Falsified: reverting No TypeScript was touched, so no package typecheck applies. Repo-wide build/lint/test were not run (this box cannot build
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 2": could not execute scripts/tests/dev.test.js (this review worktree has no node_modules/vitest ), so both findings are established by reading plus a direct fil…; "agent reverse-audit (round 5)": could not execute the real-spawn lane ( packages/cli serve.test.ts dev-entrypoint boundary, and npm run dev end-to-end) — the globalSetup guard stops the….
Deferred under the convergence posture (round 8, not a blocker) — recorded, not requested in this round:
scripts/dev.js:94 — [test] Interception set defined by the derivation rule; the new test's completeness oracle mirrors that same rule, so an unmappable exports entry is silently dropped and invisible — both reject branches measured unpinned…scripts/dev.js:85 — [review] Re-implements distEntryFiles (scripts/vitest-global-setup.js:108) with narrower condition handling; a third copy of the rule lives in eslint-rules/no-core-utils-upward-import.js:69-99scripts/dev.js:95 — [probe] NODE_OPTIONS registration leaks the hook to every grandchild, turning the PR's own plain-Node guard red (11/11) when test:scripts runs inside a dev sessionscripts/tests/dev.test.js:262 — [probe] Completeness arm asserts a location (/packages/core/src/) the launcher never guarantees, so a correctly intercepted root-level core export reds as a fake dist leakscripts/tests/dev.test.js:237 — [probe] Nothing pins the generated loader's delivery to the child; dropping the --import registration leaves the suite 5/5 green while dev stops loading core from source
中文说明
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent 2":could not execute scripts/tests/dev.test.js (this review worktree has no node_modules/vitest ), so both findings are established by reading plus a direct fil…;"agent reverse-audit (round 5)":could not execute the real-spawn lane ( packages/cli serve.test.ts dev-entrypoint boundary, and npm run dev end-to-end) — the globalSetup guard stops the…。
收敛姿态下延后(第 8 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.0)












What this PR does
Teaches the cli test runner to resolve individual core modules, and moves two files off the core package root as a first end-to-end check that the mapping works.
The runner's alias list is expressed as an ordered array so it can carry a pattern entry, mirroring the wildcard subpath rule cli's tsconfig already has. The package root becomes an exact match in the process — spelled as a string it would also match everything beneath it and rewrite each subpath into a path under index.ts.
Why it's needed
Importing from the core package root pulls in its entire export graph, a bit over six hundred modules, however little of it a file actually uses. In release run 33713579913 the cli workspace reported 2223s collecting modules against 1372s running tests; core reported 546s against 251s. A file that imports the package root costs roughly 11.5s before it reaches its first assertion, where the same file importing a single module costs about 2s — and the suites that already replace the package with a mock factory, and so never evaluate it, have always run at about 1.9s.
esbuild reads tsconfig paths, so the bundle already resolves per-module imports. The test runner does not read them, and the alias list standing in for them named only four subpaths, so per-module imports did not resolve under test at all. That gap is what this PR closes; the two migrated files exist to prove it end to end before anything larger moves.
Background and measurements are in #10908.
Reviewer Test Plan
How to verify
The two migrated files should behave identically — the change is which module the same symbols come from. Their own suites cover them, and the rest of the cli suite exercises the alias change, since every test in the package now resolves the package root through a pattern entry rather than a string one. A resolution mistake here fails loudly at import time rather than subtly, so a green cli run is the signal.
Worth a reviewer's eye: the ordering in the alias array. The four named subpaths must stay ahead of the pattern entry because their targets are not derivable from their names, and the package root must remain an exact match.
Evidence (Before & After)
N/A — no user-visible behavior changes.
Tested on
Not run locally; relying on CI across the three platforms.
Risk & Scope
Linked Issues
Refs #10908
中文说明
这个 PR 做了什么
让 cli 的测试运行器能够解析 core 的单个模块,并把两个文件从包根导入改成按模块导入,作为端到端的第一次验证。
alias 列表改成有序数组形式,以便携带一条通配规则,对齐 cli tsconfig 里已有的通配 subpath 映射。包根在此过程中必须改成精确匹配——写成字符串时它同样会匹配其下所有子路径,把每个子路径重写成 index.ts 下的路径。
为什么需要
从 core 包根导入会拉进它的整个导出图,六百多个模块,无论调用方实际只用了多少。在 release run 33713579913 中,cli 的模块收集耗时 2223s、跑测试 1372s;core 是 546s 对 251s。一个从包根导入的文件在到达第一条断言之前要花约 11.5s,而同一文件改成按模块导入只要约 2s——那些本来就用 mock 工厂替换整个包、因而从不求值它的用例,一直是约 1.9s。
esbuild 会读 tsconfig 的 paths,所以打包时已经能解析按模块导入。测试运行器不读 paths,而代替它的 alias 列表只列了四个具名 subpath,因此按模块导入在测试里根本解析不了。本 PR 补的就是这个缺口;两个迁移文件的作用是在更大范围改动之前把链路走通。
背景和测量数据见 #10908。
审查者验证计划
如何验证
两个迁移文件的行为应完全不变——变的只是同一批符号来自哪个模块。它们各自的用例覆盖了自身,而整个 cli 套件则检验了 alias 改动,因为现在包中每个测试都通过通配规则而非字符串规则解析包根。这里若有解析错误会在导入期直接报错而不是悄悄改变行为,所以 cli 跑绿就是信号。
值得审查者留意的是数组里的顺序:四个具名 subpath 必须排在通配规则之前(它们的目标路径无法从名字推导),包根必须保持精确匹配。
证据(前后对比)
N/A —— 无用户可见行为变化。
风险与范围
关联 Issue
Refs #10908