Skip to content

fix(review): screen content filters before the probe tree's restore too (#9558) - #10421

Open
wenshao wants to merge 4 commits into
mainfrom
fix/review-filter-screen-v3
Open

fix(review): screen content filters before the probe tree's restore too (#9558)#10421
wenshao wants to merge 4 commits into
mainfrom
fix/review-filter-screen-v3

Conversation

@wenshao

@wenshao wenshao commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

scratch-tree refuses to create or reset a tree while the repository's local config defines a content filter: a checkout executes filter.<name>.smudge whenever it rewrites a file, and pointing core.hooksPath at nothing covers hooks, not filters. test-efficacy's per-run restore is the same checkout one directory over — checkout --force HEAD -- . rewrites every tracked file in the probe tree, twice per probe run — and it screened nothing.

Three changes: the screen runs before the probe tree's restore; it moves to lib/worktree.ts so both callers share one implementation instead of one growing a corner the other lacks; and filter.<name>.process joins smudge and clean as the third executable key.

Why it's needed

The planting surface is two plain writes a probe can make into the common dir: git config filter.evil.smudge CMD and one line appended to $(git rev-parse --git-path info/attributes). discard and cleanup never wipe the common dir, so a filter planted while reviewing one PR fires on every later matching checkout of the user's own repository. scratch-tree already refuses on that surface; the probe tree's restore ran through it twice per probe run.

Repo-local only, and that is the load-bearing part. git lfs install writes filter.lfs.clean into the user's global config. A screen over merged config would put every contributor with git-lfs into permanent refusal — the same failure as a tripwire that fires on every healthy run, which this pipeline has already shipped once (#9221 round 15: git status reported nothing while the tripwire reported 3 957 paths).

Why a new PR, and why it is smaller

This is #9741 at the size of its own first commit, rebased onto current main. #9741 opened at 5 files / +160 −72 and closed at 19 files / +4741 −469 across 22 commits, with 230 review threads and roughly 100 unresolved Critical findings; its takeover reached the 100/100 round cap and its autofix loop stopped after three agent time-budget exhaustions, asking a human to "split or reduce the PR". The round-16 review said the same thing from the other side: "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 PR."

The growth was not this change. Answering each Critical in turn added guards to fetch-pr.ts, base-tree.ts, lib/platform/aone.ts, run.ts and lib/test-utils.ts — files this change never needed to touch — and each guard opened sibling entrances of its own. Two of the standing Critical findings are regressions the growth itself introduced: the widened refusal regex came to fail closed on repo-local core.fsmonitor, the four proxy key families (R12-3) and any include.path / includeIf.*.path directive (R14-1) — all documented, legitimate per-repo configuration. That is precisely the permanent-refusal failure the paragraph above exists to avoid. At this size those regressions do not exist, because the screen enumerates exactly three keys.

Two further standing findings are properties of main, not of this change: scratch-tree's gitOut (R11-2) and base-tree's gitOut / git (R11-3) carry no timeout/killSignal on origin/main today, and this PR neither introduces them nor touches those helpers. The rest — verdict channel, identity gate, teardown hook, creation screen, refspec grammar, transport-key enumeration — describe code that only ever existed inside #9741's growth and is absent here.

Known gap, tracked rather than fixed here

git config --file does not expand include.path / includeIf — that is git's documented default for --file — while the checkouts this screen authorises read merged config, which does. A filter reached through an include directive therefore passes the screen. Round 1's review live-verified the bypass end-to-end; it is now tracked as #10441.

The obvious fix is not the right one: adding --includes makes the screen follow an include.path that names the user's own global config, which drags filter.lfs.clean back in and reproduces exactly the permanent-refusal failure above — and failing closed on any repo-local include.path key refuses documented, legitimate per-repo configuration, which is what #9741 shipped and had to answer for twice. Doing it correctly means resolving each hit's origin file (--show-origin) and refusing only when that origin is repo-local. #10441 carries that, with the two test arms the review specified.

Issue #9558's own "known limit" section scopes include.path / includeIf out of this screen, so Closes #9558 stands with the gap disclosed.

Round 1 review — what changed

Three of the four other findings were defects in the screen this PR installs, and are fixed:

  • The revert checkout ran unscreened. checkout <base> -- <paths> in the mutation phase rewrites files, so it executes a smudge exactly as the restore does — and everything between the two runs the PR's own test code, which can plant the filter mid-run while the phase's catch deliberately keeps going. Issue review: extend the content-filter screen to test-efficacy, scoped to repo-local config (not global) #9558's triage named this site, so closing that issue means screening it. Both call sites now share one screen.
  • The enumeration failed open on its own spawn. The screened file is attacker-writable and git prints every matching value in full, so padding one smudge past spawnSync's default 1 MiB buffer killed git with ENOBUFS and the screen skipped the only file defining the filter. The spawn now carries the 64 MiB buffer its neighbours use, and anything but exit 0 or 1 refuses. Readability is settled by construction too, because git reports an unreadable --file with exit 1 — indistinguishable from "no key matched".
  • The docstring claimed more than the code does. It opened with "Every checkout in this pipeline EXECUTES these" while enumerating only the screened sites, and said hooks were disabled pipeline-wide when only some spawns pass core.hooksPath. It now names the screened set as the screened set and says the creation checkouts are not in it.

The fourth was a test defect worth its own line: the new test planted an execution canary it never asserted and committed no attributes line selecting the filter, so the restore executed no smudge either way. Fixing it surfaced two more reasons it could not fire — the restore's own clean -ffdx deleted a canary written inside the tree, and a checkout rewrites nothing when the tree already matches HEAD. The canary now lives outside the tree, the fixture is dirty before the run, and the assertion goes red when the screen moves below the checkout. It did not before.

Reviewer Test Plan

How to verify

cd packages/cli && npx vitest run src/commands/review116 files, 5 584 passed, 3 skipped, 0 failed.

New coverage in test-efficacy.test.ts: a repo-local filter.evil.smudge makes the mutant run inconclusive naming the filter and never executes it; the same fixture with the filter moved to the user's global config runs through without a filter refusal; a smudge padded past the default spawn buffer is still seen; a config candidate that cannot be read refuses rather than reporting clean. test-efficacy.integration.test.ts drives the whole command with a fake runner that plants the filter mid-run, and asserts the revert never executes it. scratch-tree.test.ts gains the filter.evil.process arm.

Evidence (Before & After)

N/A — no user-visible or TUI surface. Each property is instead pinned by reverting it alone and watching the test go red:

mutation suite result
remove the screen from test-efficacy's restore test-efficacy.test.ts red — the local filter no longer refuses
widen the screen to merged config (drop --file) test-efficacy.test.ts redexpected … not to contain 'filter.lfs.clean'
drop process from the key regex scratch-tree.test.ts redfilter.evil.process no longer refuses
remove the revert-phase screen test-efficacy.integration.test.ts red — the planted smudge executes; canary exists
drop maxBuffer and the fail-closed branch (the original shape) test-efficacy.test.ts red — the padded filter executes and empties the file
remove the constructive readability gate test-efficacy.test.ts red — an unreadable candidate reports clean
move the screen below the checkout --force / clean spawns test-efficacy.test.ts red — canary exists; the message assertion alone stays green

The last row is the one the round-1 review asked for and the one my first attempt failed: with the canary inside the probe tree, or with a fixture that never dirties a tracked file, that mutation stays green.

Row two is the false-positive direction, and it fails by naming the global git-lfs filter. Without that test, widening the screen is the change a later round makes while "tightening" it — and, in #9741, did.

Tested on

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

Environment (optional)

macOS 26.6 (Darwin 25.6.0), Node 24, packages/cli vitest 3.2.7. Unit and integration tests; the integration suite drives the real command against a fake vitest bin.

Risk & Scope

  • Main risk or tradeoff: one new refusal path in the efficacy probe — a repository whose local config carries a content filter reports inconclusive for that run instead of executing the filter. Confined to packages/cli/src/commands/review; no user-facing surface.
  • Not validated / out of scope: the include.path gap described above (tracked as review: resolve filter-screen hits by origin file so include directives cannot hide a repo-local filter #10441); the pre-existing absence of timeout/killSignal on scratch-tree's and base-tree's git helpers, which is main's state today and not this change's subject.
  • Breaking changes / migration notes: none.

Linked Issues

Closes #9558. Replaces #9741 (which replaced #9566). Follow-up from #9221; the class it belongs to is #9556. Round-1 review gap tracked as #10441.

中文说明

这个 PR 做了什么

scratch-tree 在仓库本地配置定义了内容过滤器时拒绝创建或重置树:checkout 每次重写文件都会执行 filter.<name>.smudge,而把 core.hooksPath 指向空处只覆盖钩子、覆盖不到过滤器。test-efficacy 每次运行前的恢复是同一种 checkout 挪了一个目录——checkout --force HEAD -- . 会重写探针树里每个已跟踪文件、每次探针运行两次——而它什么也没屏蔽。

三处改动:屏蔽在探针树恢复之前执行;移到 lib/worktree.ts 让两个调用方共用一份实现,而不是其中一个长出另一个没有的角落;filter.<name>.process 作为第三个可执行键与 smudgeclean 并列。

为什么需要它

植入面是探针能往共享的 common 目录做的两次普通写入:git config filter.evil.smudge CMD,以及往 $(git rev-parse --git-path info/attributes) 追加一行。discardcleanup 从不清扫 common 目录,因此审查某个 PR 时植入的过滤器,会在此后用户自己仓库的每一次匹配 checkout 上触发。scratch-tree 已经在这个面上拒绝了;探针树的恢复却每次探针运行穿过它两遍。

只看仓库本地,这是承重部分。 git lfs install 会把 filter.lfs.clean 写进用户的全局配置。对合并后配置做屏蔽,会让每个装了 git-lfs 的贡献者永久处于拒绝态——与「每次健康运行都触发的绊线」是同一种失败,而本流水线已经犯过一次(#9221 第 15 轮:git status 一条没有,绊线报 3 957 条路径)。

为什么新开 PR,以及为什么它更小

这是 #9741 回到它自己第一个提交的尺寸,重新基于当前 main#9741 开张时是 5 文件 / +160 −72,关闭时是 19 文件 / +4741 −469、22 个提交、230 条评审线程、约 100 条未决 Critical;它的托管到达 100/100 轮次上限,autofix 循环在三次耗尽 agent 时间预算后停机,并要求人类「split or reduce the PR」。第 16 轮评审从另一侧说了同一件事:「一个不断产出兄弟项的簇,通常意味着修复是在处理同一个根因的各个实例——在下一轮之前先归因,或者把独立的簇拆成它自己的 PR。」

膨胀不是这个改动带来的。逐条回答 Critical 会往 fetch-pr.tsbase-tree.tslib/platform/aone.tsrun.tslib/test-utils.ts 里加护栏——都是本改动从不需要触碰的文件——而每道护栏又打开它自己的兄弟入口。其中两条仍然成立的 Critical,正是膨胀自身引入的回归:被扩宽的拒绝正则最终对仓库本地的 core.fsmonitor、四个 proxy 键族(R12-3)以及任何 include.path / includeIf.*.path 指令(R14-1)fail-closed——而这些都是有文档的、正当的按仓库配置。这精确复刻了上一段所要避免的永久拒绝失败。在当前尺寸下这些回归不存在,因为屏蔽只枚举三个键。

另有两条仍然成立的发现是 main 的性质、而非本改动的:scratch-treegitOut(R11-2)与 base-treegitOut / git(R11-3)在今天的 origin/main 上就不带 timeout/killSignal,本 PR 既不引入也不触碰这些 helper。其余——verdict channel、identity gate、teardown hook、creation screen、refspec 文法、传输键枚举——描述的都是只存在于 #9741 膨胀过程中的代码,在这里并不存在。

已知缺口,跟踪而非在此修复

git config --file 不会展开 include.path / includeIf——这是 git 对 --file 的文档化默认——而本屏蔽所授权的 checkout 读的是合并后配置,是会展开的。因此经由 include 指令到达的过滤器能通过屏蔽。第 1 轮评审已端到端实测该绕过,现跟踪为 #10441

显而易见的那个修法不对:加上 --includes 会让屏蔽跟随一条指向用户自己全局配置的 include.path,把 filter.lfs.clean 拖回来,精确复刻上面那个永久拒绝失败;而对任何仓库本地的 include.path 键直接失败关闭,则会拒绝有文档的、正当的按仓库配置——那正是 #9741 交付过、并为此两度被提出的东西。要做对,需要解析每个命中的来源文件(--show-origin)、仅当来源是仓库本地时才拒绝。#10441 承接此事,并带上评审指定的两条测试臂。

issue #9558 自己的「已知边界」一节就把 include.path / includeIf 划在本屏蔽之外,因此在缺口已披露的前提下 Closes #9558 依然成立。

第 1 轮评审——改了什么

另外四条中的三条是本 PR 所安装屏蔽自身的缺陷,均已修复:

  • 回退 checkout 未被屏蔽。 变异阶段的 checkout <base> -- <paths> 会重写文件,因而与恢复一样执行 smudge——而两者之间跑的是 PR 自己的测试代码,可以在运行中植入过滤器,同时该阶段的 catch 有意继续执行。issue review: extend the content-filter screen to test-efficacy, scoped to repo-local config (not global) #9558 的分诊点名了此处,所以关闭该 issue 就意味着必须屏蔽它。现在两个调用点共用同一道屏蔽。
  • 枚举在自己的 spawn 上失败开放。 被屏蔽的文件是攻击者可写的,且 git 会完整打印每个匹配值,因此把一个 smudge 填充到超过 spawnSync 默认的 1 MiB 缓冲区,就会让 git 被 ENOBUFS 杀掉,屏蔽随即跳过唯一定义过滤器的那个文件。该 spawn 现在带上与邻近 spawn 相同的 64 MiB 缓冲区,且除 exit 0 与 1 之外的任何结果都拒绝。可读性也改为构造性判定——因为 git 对不可读的 --file 报 exit 1,与「无匹配」无法区分。
  • docstring 声称的比代码做到的多。 它以「Every checkout in this pipeline EXECUTES these」开头,枚举的却只有被屏蔽的位置;又声称钩子在整条流水线上被禁用,而实际只有部分 spawn 传了 core.hooksPath。现在它把被屏蔽的集合如实称为被屏蔽的集合,并写明创建 checkout 不在其中。

第四条是测试缺陷,值得单独一行:新测试植入了执行金丝雀却从未断言,也没有提交选中该过滤器的 attributes 行,因此无论有没有屏蔽,恢复都不会执行 smudge。修它的过程中又暴露出两个让金丝雀无法触发的原因——恢复自己的 clean -ffdx 会删掉写在树内的金丝雀,而当树已与 HEAD 一致时 checkout 根本不重写文件。现在金丝雀位于树外、夹具在运行前就是脏的,把屏蔽挪到 checkout 之下会让该断言变红。改之前不会。

审查者验证方案

如何验证

cd packages/cli && npx vitest run src/commands/review116 个文件、5 584 通过、3 跳过、0 失败

test-efficacy.test.ts 新增覆盖:仓库本地的 filter.evil.smudge 让突变体运行判为 inconclusive、点名该过滤器且从不执行它;同一夹具把过滤器改放到用户全局配置后,运行照常通过、不产生过滤器拒绝;被填充到超出默认 spawn 缓冲区的 smudge 仍能被看到;读不到的配置候选会拒绝而不是报干净。test-efficacy.integration.test.ts 用一个在运行中植入过滤器的 fake runner 驱动整条命令,断言回退从不执行它。scratch-tree.test.ts 增加 filter.evil.process 一臂。

证据(Before & After)

N/A——没有用户可见面或 TUI 面。每条性质改为以「单独回退该项 → 对应测试变红」钉住:

变异 套件 结果
test-efficacy 的恢复中移除屏蔽 test-efficacy.test.ts ——本地过滤器不再拒绝
把屏蔽扩宽到合并后配置(去掉 --file test-efficacy.test.ts ——expected … not to contain 'filter.lfs.clean'
从键正则中去掉 process scratch-tree.test.ts ——filter.evil.process 不再拒绝
移除回退相的屏蔽 test-efficacy.integration.test.ts ——植入的 smudge 被执行,金丝雀出现
同时去掉 maxBuffer 失败关闭分支(原始形状) test-efficacy.test.ts ——被填充的过滤器被执行并清空文件
移除构造性可读性门 test-efficacy.test.ts ——读不到的候选被报为干净
把屏蔽挪到 checkout --force / clean 派发之下 test-efficacy.test.ts ——金丝雀出现;仅靠消息断言仍是绿的

最后一行正是第 1 轮评审所要求的、也是我第一次尝试没做到的:金丝雀若留在探针树内、或夹具从不弄脏已跟踪文件,该变异都会保持绿色。

第二行是误报方向,它失败时点名的正是全局的 git-lfs 过滤器。没有这条测试,扩宽屏蔽正是后来某一轮在「收紧」时会做出的改动——在 #9741 里也确实做了。

测试环境

macOS 26.6(Darwin 25.6.0)、Node 24、packages/cli vitest 3.2.7。单元与集成测试;集成套件以 fake vitest bin 驱动真实命令。

风险与范围

  • 主要风险或权衡: 效力探针新增一条拒绝路径——本地配置带内容过滤器的仓库,该次运行报 inconclusive 而不是执行该过滤器。限于 packages/cli/src/commands/review,无用户可见面。
  • 未验证 / 范围外: 上述 include.path 缺口(跟踪于 review: resolve filter-screen hits by origin file so include directives cannot hide a repo-local filter #10441);以及 scratch-treebase-tree 的 git helper 上本就缺失的 timeout/killSignal——那是 main 今天的状态,不是本改动的主题。
  • 破坏性变更 / 迁移说明: 无。

关联 Issue

Closes #9558。替代 #9741(后者替代 #9566)。来自 #9221 的后续;所属类别是 #9556。第 1 轮评审缺口跟踪于 #10441

…oo (#9558)

`scratch-tree` refuses to create or reset a tree while the repository's LOCAL
config defines a content filter, because a checkout executes
`filter.<name>.smudge` whenever it rewrites a file and disabling hooks does not
cover that. `test-efficacy`'s per-run restore is the same checkout one
directory over — it rewrites every tracked file in the probe tree, twice per
probe run — and screened nothing.

The screen moves to `lib/worktree.ts` so both callers share one implementation
rather than one growing a corner the other lacks, which is how the
`config.worktree` scoping bug got in.

`filter.<name>.process` joins `smudge` and `clean`: it is the third executable
key — a long-running filter git speaks a protocol to — and enumerating two of
three is how the first cut read as complete.

**Repo-local only, and that is the load-bearing part.** `git lfs install`
writes `filter.lfs.clean` into the user's GLOBAL config, so a screen over
merged config would put every contributor with git-lfs into permanent refusal —
the same failure as a tripwire that fires on every healthy run, which this
pipeline has already shipped once. Both halves are pinned: removing the screen
turns the new test red, and widening it to merged config turns the same test
red from the other side.
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Aug 28, 2026
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on fde3119 and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— fde3119 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present, with the bilingual <details>.

Problem: observed, not theoretical. This is the remainder of a measured finding from the #9221 adversarial rounds (R14-1, R15-2, and the filter half of R17-6), tracked in #9558 (type/bug, category/security): the probe tree's per-run restore is the same shape of checkout scratch-tree already refuses to reset through, and it screened nothing, twice per probe run. The planting surface (two plain writes into the common dir) is documented in the issue.

Direction: aligned. This is the ongoing #9556 class — hardening the review pipeline against a hostile PR's test code — and the linked issue prescribes exactly this scope: share scratch-tree's existing screen with test-efficacy's restore, repo-local config only. The repo-local scoping is the load-bearing part: a merged-config screen would refuse every contributor whose global config carries filter.lfs.clean (git-lfs) — the permanent-refusal failure this pipeline has already shipped once.

Size: 5 files, +160 −72. Production logic ≈ 159 lines (lib/worktree.ts +74, scratch-tree.ts −71/+1, test-efficacy.ts +13), tests ≈ 73 lines. Not core-module paths; well under any threshold.

Approach: the diff is exactly the prescribed fix, and matches what I'd propose independently — move localFilterCommands to lib/worktree.ts so both callers share one implementation, add process as the third executable key beside smudge/clean, and screen before the restore's file-rewriting spawns. Nothing beyond the minimal set; the include.path gap is deliberately deferred with a stated reason (adding --includes would follow an include into the user's global config and reintroduce the git-lfs refusal). That reasoning holds.

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

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ —— 所有必需章节齐全,含双语 <details>

问题:已观测到,不是理论性的。这是 #9221 对抗性测试轮次中实测 finding 的剩余部分(R14-1、R15-2 以及 R17-6 的 filter 那一半),记录于 #9558type/bugcategory/security):探针树的每次运行前恢复与 scratch-tree 已拒绝穿过其重置的 checkout 同形,却完全没有屏蔽,且每次探针运行执行两次。植入面(往 common 目录的两次普通写入)在 issue 中有记录。

方向:对齐。这属于进行中的 #9556 类别——加固 review 流水线以抵御恶意 PR 的测试代码——关联 issue 明确指定了这个范围:让 test-efficacy 的恢复复用 scratch-tree 现有的屏蔽,且只看仓库本地配置。只看本地是承重部分:对合并后配置做屏蔽会让每个全局配置里有 filter.lfs.clean(git-lfs)的贡献者永久被拒——本流水线已经犯过一次的永久拒绝失败。

规模:5 个文件,+160 −72。生产逻辑约 159 行(lib/worktree.ts +74、scratch-tree.ts −71/+1、test-efficacy.ts +13),测试约 73 行。非核心模块路径,远低于任何阈值。

方案:diff 恰是指定的修法,也与我独立会提出的方案一致——把 localFilterCommands 移到 lib/worktree.ts 让两个调用方共用一份实现,把 process 作为第三个可执行键加入 smudge/clean,并在恢复的文件重写 spawn 之前屏蔽。没有超出最小集合的改动;include.path 缺口有意推迟且给出了理由(加 --includes 会跟随 include 进入用户全局配置,重新引入 git-lfs 拒绝)。这个理由成立。

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

进入代码审查。🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

Reviewed the full diff and read each changed file at the reviewed commit. No blockers — the change is faithful to the fix #9558 prescribes and integrates cleanly:

  • The move to lib/worktree.ts is clean: the function body is carried over with only the regex widened (process joins smudge/clean as the third executable key), every dependency it needs (spawnSync, readdirSync, existsSync, resolve, join, sanitizedGitEnv) already exists in the host module, scratch-tree.ts drops its local copy and the now-unused readdirSync import, and its call site ordering is untouched — the screen still runs before both the reuse reset and the rebuild worktree add.
  • In restoreProbeTreeTracked the screen sits after the symlink/toplevel/backpointer integrity checks and before the only two file-rewriting spawns (checkout --force HEAD -- . and clean -ffdx); the one spawn ahead of it is rev-parse, which executes nothing. All four call sites of the function inherit the screen through the existing string | null contract — an inconclusive verdict naming the filter for the mutant/hunk paths, a skipped control for the control path.
  • The tests pin the change rather than just covering it: the refusal detail the test matches (filter.evil.smudge) is only reachable through the new screen, so the first arm fails without the wiring; the second arm (filter moved to the user's global config) fails the wrong way — not.toContain('content filter') — if the screen is ever widened past --file, which is exactly the git-lfs permanent-refusal regression the issue warns against. The scratch-tree.test.ts arm adds filter.evil.process and asserts the planted command never ran. These are the right tests.

One standing observation, non-blocking and pre-existing on main: in runTestEfficacy's revert phase, git checkout <base> -- <modified files> is a third file-rewriting checkout that is not screened, and it runs after the baseline/mutant/hunk phases have executed the PR's own test code — the very phase that can plant filter.evil.smudge into the common-dir config plus one line in info/attributes (the planting surface the issue itself documents). Every mutant/hunk run starts with the now-screened restore and git apply --reverse executes no filters, so as far as I can see this revert checkout is the one remaining unscreened file rewrite in test-efficacy. That is outside the scope #9558 prescribes ("apply it in test-efficacy.ts before its restore checkout") and needs its own design decision (what a refusal there means for the revert run's verdict), so it belongs in a follow-up under the #9556 class rather than blocking this PR.

Testing evidence

Evidence carried: the PR's own CI check results for the reviewed commit, fetched once via the API at review time (per policy this review never builds or runs PR code). The unit suite and the no-AK integration run were still in progress at fetch time — not verified: their final result on fde3119916edc47a1c89ff1b621d49a11e005797; the finalize job updates the table below when CI settles. No sandboxed-lane line: the central claim (restore refuses on a repo-local filter, passes a global one) is exactly what the new unit tests assert, and the matched detail string is only reachable through the new code path — CI landing green on this commit settles it. Real-scenario (tmux) testing: N/A — no user-visible or TUI surface, and this is an unattended CI run.

Final CI results for fde3119 (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
Integration Tests (no-AK, No Sandbox) ✅ success
Post Coverage Comment (ubuntu-latest, 22.x) ✅ 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,失败项排在最前。

中文说明

代码审查

审阅了完整 diff,并在被审提交上逐文件阅读。无阻塞项——改动忠实实现了 #9558 指定的修法,且集成干净:

  • 移入 lib/worktree.ts 干净利落:函数体原样搬移,仅放宽正则(process 作为第三个可执行键加入 smudge/clean);宿主模块已具备它所需的全部依赖(spawnSyncreaddirSyncexistsSyncresolvejoinsanitizedGitEnv);scratch-tree.ts 删除本地副本与不再使用的 readdirSync 导入,调用点顺序未动——屏蔽仍在复用路径的重置与重建路径的 worktree add 之前。
  • restoreProbeTreeTracked 中,屏蔽位于符号链接/顶层目录/反向指针完整性检查之后、仅有的两个文件重写 spawn(checkout --force HEAD -- .clean -ffdx)之前;其前唯一的 spawn 是 rev-parse,不执行任何东西。函数的四个调用点经由既有 string | null 契约自动获得屏蔽——突变体/分块路径得到点名过滤器的 inconclusive,对照路径跳过对照运行。
  • 测试钉住改动而非仅覆盖它:测试匹配的拒绝详情(filter.evil.smudge)只能经由新屏蔽到达,缺少接线时第一条必然失败;第二条(过滤器移到用户全局配置)以错误方向失败方式钉住作用域——若屏蔽被放宽出 --filenot.toContain('content filter') 会失败——这正是 issue 警告的 git-lfs 永久拒绝回归。scratch-tree.test.ts 增加 filter.evil.process 一臂,并断言植入的命令从未执行。这些是对的测试。

一条长期观察项,非阻塞且在 main 上既存:runTestEfficacy 的回滚阶段里 git checkout <base> -- <modified files> 是第三个文件重写 checkout,它没有被屏蔽,且运行在基线/突变体/分块各阶段执行过 PR 自己的测试代码之后——而那正是能往 common 目录配置植入 filter.evil.smudge 并往 info/attributes 追加一行的阶段(issue 自己记录的植入面)。每次突变体/分块运行都从现在已屏蔽的恢复开始,git apply --reverse 也不执行过滤器,所以据我所见这个回滚 checkout 是 test-efficacy 里唯一剩余未被屏蔽的文件重写。它超出 #9558 指定的范围("在 test-efficacy.ts 的恢复 checkout 之前施加"),且需要它自己的设计决策(该处拒绝意味着回滚运行怎样的判定),因此属于 #9556 类别下的后续 issue,不阻塞本 PR。

测试证据

本节携带的证据:被审提交上 PR 自己的 CI 检查结果,审查时经 API 一次性获取(按策略本审查从不构建或运行 PR 代码)。获取时单元测试套件与 no-AK 集成测试仍在进行中——未验证:fde3119916edc47a1c89ff1b621d49a11e005797 上的最终结果;CI 落定后 finalize 任务会更新下表。未列沙箱验证通道:核心主张(恢复对仓库本地过滤器拒绝、对全局过滤器放行)恰是新单元测试所断言的内容,且被匹配的详情字符串只能经由新代码路径到达——该提交上 CI 变绿即可定论。真实场景(tmux)测试:N/A——无用户可见或 TUI 面,且本次为无人值守 CI 运行。

(CI 结果表见上方英文部分,由 finalize 工作流在 CI 完成后原地更新。)

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — minimal, exactly-prescribed security fix whose tests pin both directions; the one reservation is a pre-existing sibling surface named for follow-up, not a defect of this diff.

This is what #9741's first commit should have stayed: five files that do the one thing #9558 prescribes — share the existing filter screen with the probe tree's restore, and enumerate the third executable key. My independent proposal before reading the diff was exactly this shape, and I found no simpler path the PR missed. The scoping decision is the substance of the change and it is right: a screen over merged config would refuse every contributor with git-lfs installed, trading a measured hole for a permanent false positive. The tests are the strongest part — each arm fails in the direction it exists to guard, including the false-positive arm that names filter.lfs.clean, which is precisely the regression the wider #9741 grew into.

The one thing I'd want the maintainer to see before this class is called closed is not in this diff: runTestEfficacy's revert phase still runs an unscreened file-rewriting checkout after the PR's own test code has executed (details in my review comment). That is pre-existing on main and outside the scope #9558 prescribed, so it is a follow-up under #9556, not a reason to hold this PR.

Verdict: approve. The unit suite was still in flight at review time, so approval is deferred until CI lands green on fde3119916edc47a1c89ff1b621d49a11e005797.

中文说明

置信度:4/5 —— 最小化、严格按指定范围实施的安全修复,测试钉住了两个方向;唯一的保留项是一处既存的同族面,点名留作后续跟进,并非本 diff 的缺陷。

这就是 #9741 的第一个提交本应保持的样子:五个文件,做 #9558 指定的那一件事——让既有的过滤器屏蔽与探针树的恢复共用,并把第三个可执行键纳入枚举。读 diff 之前我独立提出的方案正是这个形状,我也没有找到比这更简单的路径。作用域决策是这个改动的实质,而且是对的:对合并后配置做屏蔽会让每个装了 git-lfs 的贡献者被永久拒绝,用一个实测存在的漏洞换一个永久的误报。测试是最强的部分——每一臂都朝它要防的方向失败,包括点名 filter.lfs.clean 的误报臂,而那正是更臃肿的 #9741 后来长出来的回归。

在这个类别被称为关闭之前,我希望维护者看到的一件事不在这个 diff 里:runTestEfficacy 的回滚阶段仍然在 PR 自己的测试代码执行之后运行一个未被屏蔽的文件重写 checkout(详见我的审查评论)。那在 main 上既存、且超出 #9558 指定的范围,所以是 #9556 下的后续事项,不是扣住本 PR 的理由。

结论:批准。审查时单元测试套件仍在运行,因此批准推迟到 fde3119916edc47a1c89ff1b621d49a11e005797 上 CI 变绿之后。

Qwen Code · qwen3.8-max

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

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI N/A% N/A% N/A% N/A%
Core 88.76% 88.76% 90.53% 87.2%
CLI Package - Full Text Report
CLI full-text-summary.txt not found at: coverage_artifact/cli/coverage/full-text-summary.txt
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   88.76 |     87.2 |   90.53 |   88.76 |                   
 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.38 |    80.91 |   81.25 |   90.38 | ...2550,2596-2598 
  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.8 |   91.09 |   84.84 |                   
  TeamManager.ts   |   78.24 |    83.78 |   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.19 |    88.32 |    78.3 |   86.19 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   84.88 |    87.73 |   76.27 |   84.88 | ...9619,9623-9625 
  ...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.39 |    88.27 |   91.83 |   92.39 | ...4564,4662-4663 
  ...tGenerator.ts |   87.45 |    88.09 |   88.88 |   87.45 | ...08-509,554-560 
  ...lScheduler.ts |   90.22 |    84.96 |   94.73 |   90.22 | ...6488,6516-6532 
  ...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 |    91.89 |     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.1 |    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 |    89.13 |     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        |    89.3 |    85.28 |   92.03 |    89.3 |                   
  ...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 |    91.3 |    83.73 |   91.66 |    91.3 | ...07,416-417,421 
  memoryAge.ts     |   90.47 |    83.33 |     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 |   79.76 |    76.84 |      80 |   79.76 | ...69-473,476,482 
  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.33 |    91.62 |   71.54 |   84.33 |                   
  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 |    90.19 |     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 |   88.26 |     91.9 |   82.35 |   88.26 | ...1374,1480-1484 
  rule-parser.ts   |    94.9 |    92.81 |     100 |    94.9 | ...1552,1586-1588 
  ...-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.64 |    86.19 |   96.55 |   90.64 |                   
  ...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.12 |   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 |   95.52 |    90.99 |     100 |   95.52 | ...37,346-347,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 
  ...l-registry.ts |   92.99 |    83.19 |     100 |   92.99 | ...66-367,377-378 
  ...reeCleanup.ts |   14.42 |      100 |   33.33 |   14.42 | 58-186            
  ...ionService.ts |   88.36 |     87.8 |     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 | 289-290           
 ...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.7 |      70 |   74.23 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   74.23 |    80.53 |   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.55 |    86.07 |   90.22 |   87.55 |                   
  ...erQuestion.ts |   89.71 |    81.48 |   92.85 |   89.71 | ...81-382,389-390 
  ...-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.13 |    93.93 |    92.3 |   99.13 | 256-258           
  ...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.72 |    82.95 |   86.53 |   80.72 | ...1106,1114-1115 
  ...-finalizer.ts |    98.1 |     92.3 |   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.64 |    84.87 |    92.3 |   91.64 | ...00,415-420,580 
  ...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.51 |     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.83 |     92.7 |     100 |   96.83 | ...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.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Comment on lines +632 to +638
const r = spawnSync(
'git',
[
'config',
'--file',
file,
'--get-regexp',

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: The screen reads each candidate config file with git config --file, which does not expand include.path / includeIf directives (git's documented default for --file), while the checkouts this screen authorises read merged config, which does. A filter planted behind an include directive is invisible to the screen and executed by the checkout: append [include] path = evil.inc to <common>/config, write [filter "evil"] smudge = CMD into the included file, add one attributes line — localFilterCommands returns [] and reports clean, and the authorised checkout --force HEAD -- . executes CMD on the host. Reproduced end-to-end at this commit (git 2.43.0), defeating both call sites the screen protects:

CASE A (include.path plant) localFilterCommands => [] => SCREEN BLIND (bypass confirmed)
CASE B (plain filter)       localFilterCommands => ["filter.evil.smudge"]   <- oracle flips
checkout --force HEAD -- .  executed the included smudge (canary created)

The PR description's "Known gap" section documents this deferral, and the refusal of bare --includes is sound (it would follow a repo-local include pointing at the user's global config, re-import filter.lfs.clean, and reproduce the permanent-refusal failure). But the mechanism is live-verified at this commit, so the residual gap should be tracked and closed with the variant the description itself sketches — resolve each hit's origin file with --show-origin and refuse only when that origin is repo-local — or fail closed on any repo-local include.path / includeIf.*.path key. When that fix lands, a test must plant the filter via [include] path + an external file + an attributes line and assert refusal naming the key; it must be red against the current code and red again if the include handling is removed.

中文说明

屏蔽用 git config --file 读取每个候选配置文件,而这不会展开 include.path / includeIf 指令(git 对 --file 的文档化默认);本屏蔽所授权的 checkout 读取的却是合并后配置,会展开 include。经由 include 指令植入的过滤器对屏蔽不可见,却会被 checkout 执行:向 <common>/config 追加 [include] path = evil.inc、在被包含文件中写入 [filter "evil"] smudge = CMD、再追加一行 attributes,localFilterCommands 即返回 [] 报干净,随后被授权的 checkout --force HEAD -- . 在宿主机上执行 CMD。已在本提交上端到端复现(git 2.43.0),两个被该屏蔽保护的调用点双双失守(见上方实测输出)。PR 描述的「已知缺口」一节记录了这一推迟,拒绝裸 --includes 的理由成立(它会跟随指向用户全局配置的仓库本地 include,把 filter.lfs.clean 拖回来,复刻永久拒绝失败)。但机制已在本提交上实测成立,这一残余缺口应被跟踪,并以描述自己勾画的变体关闭:用 --show-origin 解析每个命中的来源文件、仅当来源是仓库本地时才拒绝;或对任何仓库本地的 include.path / includeIf.*.path 键直接失败关闭。修复落地时,测试应通过 [include] path + 外部文件 + 一行 attributes 植入过滤器并断言点名该键的拒绝;该测试对当前代码必须为红,移除 include 处理后也必须为红。

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Tracked as #10441, not fixed here — and I want to be precise about which half I am accepting.

Your reproduction stands; I am not disputing the mechanism. What I am declining is landing the fix in this PR, for the reason your own finding grants: bare --includes re-imports filter.lfs.clean through a repo-local include naming the user's global config, and failing closed on any include.path / includeIf.*.path key refuses documented, legitimate per-repo configuration. #9741 shipped exactly that second variant and it came back as two standing Critical findings (R12-3, R14-1) about permanent refusal. This PR is #9741 re-opened at its original size specifically to stop absorbing adjacent fixes.

So the correct fix is the one you and the description both sketch — resolve each hit's origin with --show-origin and refuse only when that origin is repo-local — and #10441 carries it, including the --null requirement, the path-containment (not string-prefix) origin test, and the two test arms you specified: refusal named on a repo-local include plant, and NO refusal when the include lands in the user's global config.

Issue #9558's own "known limit" section scopes include.path / includeIf out of this screen ("this narrows rather than closes — include.path/includeIf, core.fsmonitor and core.hooksPath are each handled separately today"), so Closes #9558 remains accurate with the gap disclosed and tracked. The other three findings from this round are fixed in a426b4af / 377b5a87.

Comment thread packages/cli/src/commands/review/test-efficacy.ts
Comment thread packages/cli/src/commands/review/lib/worktree.ts Outdated
Comment thread packages/cli/src/commands/review/lib/worktree.ts Outdated
Comment thread packages/cli/src/commands/review/test-efficacy.test.ts Outdated
Comment thread packages/cli/src/commands/review/lib/worktree.ts Outdated
…config

Round 1 review, three defects, all in the screen this PR installs.

The revert checkout ran unscreened. `checkout <base> -- <paths>` in the
mutation phase rewrites files, and a rewrite EXECUTES `filter.<name>.smudge`
exactly as the restore's does. Screening once at the restore is not enough:
everything between the two runs the PR's own test code, which can plant the
filter mid-run, and the mutation phase's catch deliberately continues so the
revert still runs — so a refused restore reaches the revert with the plant
live. Issue #9558's triage named this site; closing that issue means screening
it.

The enumeration failed open on its own spawn. The screened file is
attacker-writable and git prints every matching value in full, so padding one
`smudge` past `spawnSync`'s default 1 MiB buffer killed git with ENOBUFS and
the screen skipped the one file that defines the filter — reporting the
repository clean while the checkout ran the last value. The spawn now carries
the 64 MiB buffer its neighbours use, and any outcome other than exit 0 or 1
refuses instead of skipping. Existence is not readability either: git reports
an unreadable `--file` with exit 1, byte-identical to "no key matched", so the
candidate is settled here by construction — a regular file this process can
open — rather than by asking git and reading the exit code.

`localFilterCommands` therefore returns the keys AND the first candidate it
could not read; both call sites refuse on either.

The docstring claimed more than the code does. It opened with "Every checkout
in this pipeline EXECUTES these" and enumerated only the screened sites, and
said hooks were disabled pipeline-wide when only some spawns pass
`core.hooksPath`. It now names the screened set as the screened set and says
the creation checkouts are not in it.

The test pinned the message but not the order: it planted a canary it never
asserted, and committed no attributes line selecting the filter, so the
restore executed no smudge with or without the screen. The fixture now commits
`*.ts filter=evil`, the canary is repo-local rather than under /tmp, and its
absence is asserted — that assertion is what goes red if the screen moves
below the checkout.
…d test

The canary lived inside the probe tree, where the restore's own `clean -ffdx`
deleted it before the assertion ran — so moving the screen below the checkout
left the suite green. It now sits in its own fixture directory outside the
tree, removed with the rest.

A checkout also runs the smudge only on files it REWRITES, and the fixture
never dirtied one, so the restore rewrote nothing. The tracked file is now
dirty before the run, which is the state a probe run actually leaves.

The unit test named for the REVERT checkout drove `runOneMutant`, which never
reaches the revert phase — it re-tested the restore under a misleading name.
The revert screen is covered end-to-end in the integration suite instead.
@wenshao

wenshao commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Round 1 addressed — 5 fixed, 1 tracked

Commits a426b4af (screen), 377b5a87 (tests), merged onto current main. Now 6 files, +424 −77.

finding verdict where
R1-2 revert checkout unscreened fixed — both call sites share one screen; #9558's triage named this site, so closing it means screening it a426b4af
R1-5 enumeration fails open (ENOBUFS) fixed — 64 MiB buffer, refuse on anything but exit 0/1, plus a constructive readability gate because git reports an unreadable --file as exit 1 a426b4af
R1-3 docstring reads as a completeness map fixed — names the screened set as the screened set, says the creation checkouts are not in it a426b4af
R1-8 "hooks disabled" claimed pipeline-wide fixed — sentence narrowed to the spawns that pass core.hooksPath a426b4af
R1-6 canary never asserted, no attributes line fixed — and two further reasons it could not fire, found by mutating 377b5a87
R1-1 include.path bypass tracked as #10441, not fixed here

On R1-6, because the first fix was not enough

Committing *.ts filter=evil and asserting the canary still left the ordering mutation green. Two more reasons, both found only by running the mutation the review specified:

  1. The restore's second spawn is clean -ffdx, which deleted an untracked canary written inside the probe tree before the assertion ran. It now lives in its own fixture directory outside the tree.
  2. A checkout runs the smudge only on files it actually rewrites, and the fixture never dirtied one. The tracked file is now dirty before the run — the state a probe run actually leaves.

With both in place, moving the screen below the checkout --force / clean spawns turns the canary assertion red.

I also deleted the unit test I first wrote for R1-2: it drove runOneMutant, which never reaches the revert phase, so it re-tested the restore under a REVERT name and passed with the revert screen removed. The revert is covered end-to-end in the integration suite instead, where the fake runner plants the filter mid-run exactly as the finding described.

On R1-1

The mechanism is not in dispute — I reproduced it. What I am declining is landing the fix here, on the grounds the finding itself grants: bare --includes re-imports filter.lfs.clean through a repo-local include naming the user's global config, and failing closed on include.path refuses legitimate per-repo configuration. #9741 shipped that second variant and answered for it twice (R12-3, R14-1). #10441 carries the --show-origin fix with both test arms the review specified. #9558's own "known limit" section scopes include.path out of this screen.

Verification

npx vitest run src/commands/review116 files, 5 584 passed, 3 skipped, 0 failed.

Seven mutations, each red on its own — full table in the PR description. The three new ones:

mutation result
remove the revert-phase screen red — planted smudge executes, canary exists
drop maxBuffer and the fail-closed branch (the original shape) red — padded filter executes and empties the file
remove the constructive readability gate red — unreadable candidate reports clean
中文说明

第 1 轮已处理——5 条修复、1 条跟踪

提交 a426b4af(屏蔽)、377b5a87(测试),已合入当前 main。现为 6 文件、+424 −77

关于 R1-6,因为第一次修得不够

提交 *.ts filter=evil 并断言金丝雀之后,顺序变异仍然是绿的。另有两个原因,都是跑评审指定的那条变异才发现的:恢复的第二个派发 clean -ffdx 会在断言之前删掉写在探针树的金丝雀(现已移到树外的独立夹具目录);以及 checkout 只对它真正重写的文件执行 smudge,而夹具从不弄脏文件(现在运行前就是脏的——那才是探针运行实际留下的状态)。两者到位后,把屏蔽挪到 checkout --force / clean 之下会让金丝雀断言变红。

我还删掉了最初为 R1-2 写的单元测试:它驱动 runOneMutant,而后者根本到不了回退相,等于用 REVERT 的名字重测恢复,且在移除回退屏蔽后仍然通过。回退改由集成套件端到端覆盖,fake runner 在运行中植入过滤器,与该发现描述的完全一致。

关于 R1-1

机制没有争议——我复现了。我拒绝的是在此落地修复,理由正是该发现自己给出的:裸 --includes 会经由指向用户全局配置的仓库本地 include 把 filter.lfs.clean 重新引入,而对 include.path 失败关闭会拒绝正当的按仓库配置。#9741 交付过后一种变体并为此两度作答(R12-3、R14-1)。#10441 承接 --show-origin 修法及评审指定的两条测试臂。#9558 自己的「已知边界」一节就把 include.path 划在本屏蔽之外。

验证

npx vitest run src/commands/review116 文件、5 584 通过、3 跳过、0 失败。七条变异各自单独变红,完整表格见 PR 描述。

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

The red Test (ubuntu-latest, Node 22.x) is not from this PR

Two runs of the same job failed with two disjoint sets of tests, none of them in a file this PR touches:

run failed file this PR touches it?
first 1 packages/web-shell/client/components/MessageList.dom.test.tsxexpected 1800 to be 600 (scrollTop after act + nextFrame) no — this PR has zero web-shell changes
rerun 3 packages/cli/src/agent-view/supervisor-process.test.tsTimed out waiting for condition in FakeAttachSocket.waitForOutput no

This PR's six files are all under packages/cli/src/commands/review/. Neither suite imports anything from them.

Both failure shapes are timing-dependent, and both pass locally on this branch — the three agent-view tests run green here (5 passed | 81 skipped). npx vitest run src/commands/review, the suite this PR is about, is 116 files, 5 584 passed, 3 skipped, 0 failed.

For context, #10443 is currently fixing exactly this class on the shared runners: "under the resource pressure of the shared self-hosted runners a stall longer than that surfaces as an unhandled error and exits an all-green run red with no failing test anywhere in the log." main itself is also red right now (run 33223243920, c13aa351a), in a different step.

I have not pushed anything to chase this, deliberately — a push would cancel the in-flight review-pr run. Happy to rerun again if a third disjoint set would help make the point.

中文说明

红掉的 Test (ubuntu-latest, Node 22.x) 不是本 PR 引起的

同一个 job 跑了两次,失败的是两组互不相交的测试,没有一个在本 PR 触碰的文件里:

  • 第一次:packages/web-shell/client/components/MessageList.dom.test.tsxexpected 1800 to be 600act + nextFrame 之后的 scrollTop)——本 PR 对 web-shell 零改动
  • 重跑:packages/cli/src/agent-view/supervisor-process.test.ts 三条(FakeAttachSocket.waitForOutputTimed out waiting for condition)。

本 PR 的六个文件全在 packages/cli/src/commands/review/ 下,上述两个套件都不引用其中任何东西。

两种失败形状都依赖时序,且在本分支本地都通过——那三条 agent-view 测试本地是绿的(5 passed | 81 skipped)。本 PR 真正关心的套件 npx vitest run src/commands/review116 文件、5 584 通过、3 跳过、0 失败

参考:#10443 正在修的就是共享 runner 上的这一类问题——「在共享自托管 runner 的资源压力下,超过 60 秒的停顿会表现为 unhandled error,把全绿的运行判成红色,而日志里没有任何失败的测试」。main 自己此刻也是红的(run 33223243920,c13aa351a),红在另一个步骤。

我没有为此推送任何东西——推送会取消正在进行的 review-pr。如果需要第三组互不相交的失败来佐证,我可以再重跑一次。

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

Correction to my previous comment on the red Test job

One thing I wrote above does not hold, and it is the part that carried the most weight, so I want to retract it explicitly rather than leave it standing.

I said #10443 is "currently fixing exactly this class". It is not. The exemption that PR extends is dangerouslyIgnoreUnhandledErrors: process.platform !== 'linux' — it applies to vitest unhandled errors, and only off Linux; packages/cli/vitest.config.ts already carries it, with the comment "only off Linux — the ubuntu lane and Linux local runs keep the unhandled-error signal."

The failures here are on the ubuntu (Linux) lane, and they are not unhandled errors — they are ordinary test failures thrown inside the tests (Timed out waiting for condition from FakeAttachSocket.waitForOutput; expected 1800 to be 600 on scrollTop). #10443 would not have suppressed either. The underlying cause may still be runner resource pressure, but the citation was wrong and I should not have leaned on it.

What does still hold

  • The tests this PR changes passed on CI, in both runs. From each job log: ✓ src/commands/review/scratch-tree.test.ts, ✓ src/commands/review/test-efficacy.test.ts, ✓ src/commands/review/test-efficacy.integration.test.ts.
  • The two failing sets are disjoint (web-shell/.../MessageList.dom.test.tsx first, cli/src/agent-view/supervisor-process.test.ts ×3 on the rerun), and this PR touches neither file — its six files are all under packages/cli/src/commands/review/.
  • Both failing suites pass locally on this branch (supervisor-process.test.ts: 86 passed).
  • The two runs landed on different runners (…-6 and …-23), so it is not one bad machine.
  • The four tests this PR adds cost ~1.1 s combined, so they are not crowding the budget.

What I have NOT explained

Other open PRs are green on this same job right now. So "the lane is broken for everyone" is not supported, and I do not have an explanation for why the red keeps landing on this PR specifically. A third rerun is in flight; I will report what it does either way, including if it fails a third time on yet another unrelated file.

中文说明

对上一条评论的更正

上面有一处不成立,而且恰恰是分量最重的一处,我明确撤回。

我说 #10443「正在修的就是这一类」——不对。 该 PR 推广的豁免是 dangerouslyIgnoreUnhandledErrors: process.platform !== 'linux',只针对 vitest 的 unhandled error,且只在 Linux 上;packages/cli/vitest.config.ts 本就带着它,注释写明「only off Linux —— ubuntu lane 与 Linux 本地运行保留 unhandled-error 信号」。

这里的失败发生在 ubuntu(Linux)lane,而且不是 unhandled error,是测试内部抛出的普通失败(FakeAttachSocket.waitForOutputTimed out waiting for conditionscrollTopexpected 1800 to be 600)。#10443 对这两者都不会起作用。底层成因也许仍是 runner 资源压力,但这条引用是错的,我不该拿它当论据。

仍然成立的部分

  • 本 PR 改动的测试在 CI 上两次都通过:两份 job 日志里都有 ✓ scratch-tree.test.ts✓ test-efficacy.test.ts✓ test-efficacy.integration.test.ts
  • 两次失败的集合互不相交(第一次 web-shell/.../MessageList.dom.test.tsx,重跑是 cli/src/agent-view/supervisor-process.test.ts 三条),本 PR 两个文件都没碰——它的六个文件全在 packages/cli/src/commands/review/ 下。
  • 两个失败套件在本分支本地都通过(supervisor-process.test.ts:86 通过)。
  • 两次跑在不同 runner…-6…-23),不是单台机器坏了。
  • 本 PR 新增的四条测试合计约 1.1 秒,谈不上挤占预算。

我尚未解释的部分

此刻其他开放 PR 在同一个 job 上是绿的。所以「这条 lane 对所有人都坏了」没有证据支持,我也解释不了为什么红偏偏反复落在这个 PR 上。第三次重跑正在进行,无论结果如何我都会如实汇报,包括它第三次又红在另一个无关文件上的情况。

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

Third run: the picture is now complete — filed as #10490

Same commit (0729301fb), three runs of the same job, three different runners, three disjoint failure sets. In all three, the tests this PR changes passed.

run runner failed shape
1 …s6t-6 web-shell/.../MessageList.dom.test.tsx expected 1800 to be 600
2 …s6t-23 cli/src/agent-view/supervisor-process.test.ts ×3 Timed out waiting for condition
3 …s6u-5 8 files: agent-view, commands/update, core/contentGenerator, memory/recall-scan-latency, serve/acp-http/transport, serve/process-env-guard, serve/workspace-registration-store, utils/shellAstParser latency assertions + timeouts

Run 3 named the cause outright:

AssertionError: expected 1762.0506539999997 to be less than 1000
AssertionError: expected 67.12685500000043 to be less than 50
Timed out waiting                              x4
Timeout calling "onTaskUpdate"                 x2
Unhandled Error                                x18

Those are wall-clock budget assertions and wait-for-condition polls under CPU contention — recall-scan-latency, shellAstParser … in bounded time, and friends. Run 3 was cancelled after ~60 min (normal: 25–28 min) leaving npm run test:ci and npm run typecheck:public-surface as orphan processes, so a step can hang rather than fail.

In every one of the three runs:

✓ src/commands/review/scratch-tree.test.ts
✓ src/commands/review/test-efficacy.test.ts
✓ src/commands/review/test-efficacy.integration.test.ts

Filed as #10490 with the full evidence and two concrete suggestions, since this can redden any PR at random and the natural reading of a red Test job is wrong here.

I have not pushed anything to chase it — a push would cancel the in-flight review run, and there is nothing in this PR to change: its six files are all under packages/cli/src/commands/review/, and npx vitest run src/commands/review is 116 files / 5 584 passed / 3 skipped locally.

中文说明

第三次运行:图景完整了——已归档为 #10490

同一提交(0729301fb)、同一 job 跑三次、三台不同 runner、三组互不相交的失败。三次之中,本 PR 改动的测试全部通过。

第三次直接点明了成因:expected 1762 to be less than 1000expected 67 to be less than 50、4 次 Timed out waiting、2 次 Timeout calling "onTaskUpdate"、18 次 Unhandled Error——都是 CPU 争用下的挂钟预算断言与轮询等待(recall-scan-latencyshellAstParser … in bounded time 等)。该次在约 60 分钟后被取消(正常 25–28 分钟),残留 npm run test:cinpm run typecheck:public-surface 孤儿进程,说明某个步骤会卡住而不是失败。

三次运行里都有:✓ scratch-tree.test.ts✓ test-efficacy.test.ts✓ test-efficacy.integration.test.ts

已带完整证据与两条具体建议归档为 #10490:它会随机让任何 PR 变红,而「红 = 这个 PR 弄坏了东西」在这里是错误读法。

我没有为此推送任何东西——推送会取消正在进行的评审运行,而且本 PR 也没有可改之处:它的六个文件全在 packages/cli/src/commands/review/ 下,本地 npx vitest run src/commands/review 是 116 文件 / 5 584 通过 / 3 跳过。

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is running in workflow run. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not explored to full depth (tool budget reached): "agent reverse-audit (round 10)": end-to-end pipeline-order confirmation (whether the scratch tree's admin entry already exists when probe code plants, or the plant lands on the reuse path of a ….

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

  • packages/cli/src/commands/review/lib/worktree.ts:692 — [review] No test plants filter.<name>.clean — dropping clean from the regex alternation is an uncatchable mutation (measured: (smudge|process) mutant misses a repo-local filter.evil.cle…
中文说明

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 10)"end-to-end pipeline-order confirmation (whether the scratch tree's admin entry already exists when probe code plants, or the plant lands on the reuse path of a …

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

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

Comment on lines +685 to +688
'config',
'--file',
file,
'--get-regexp',

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: Round 1, still stands. The screen reads each candidate config file with git config --file, which does not expand include.path / includeIf directives (git's documented default for --file), while the checkouts this screen authorises read merged config, which does. A filter planted behind an include directive is invisible to all three screens and executed by the checkout: append [include] path = evil.inc to <common>/config, write [filter "evil"] smudge = CMD into the included file, add one attributes line — the screen reports clean and the authorised checkout runs CMD on the host. The reply tracks this as #10441, and the tracking is right, but the mechanism is live at this commit and a reply does not retire a blocker: the finding stands until the fix lands (here or in #10441) — resolve each hit's origin file with --show-origin and refuse only when that origin is repo-local, or fail closed on any repo-local include.path / includeIf.*.path key. Bare --includes is not the fix: it follows a repo-local include naming the user's global config and re-imports filter.lfs.clean.

Witness:

Round-2 re-measurement (live, git 2.43.0, five independent agents):
git config --file .git/config --get-regexp '^filter\..*\.(smudge|clean|process)$' → exit 1, no output (SCREEN CLEAN)
git checkout --force HEAD -- . → executed the included smudge (canary created)
includeIf "gitdir:**" variant: screen exit 1, checkout created the canary; --local enumeration misses it identically

The fix must keep global-config filters exempt — expect(afterDetail).not.toContain('filter.lfs.clean') in test-efficacy.test.ts pins that a filter.lfs.clean in the user's GLOBAL config is not refused (the git-lfs contract). When the fix lands, a test that plants the filter via [include] path + an external file + an attributes line must assert refusal naming the key — please confirm it is red against the current code, and red again if the include handling is removed.

中文说明

第 1 轮发现,仍然成立。屏蔽用 git config --file 读取每个候选配置文件,而这不会展开 include.path / includeIf 指令(git 对 --file 的文档化默认);本屏蔽所授权的 checkout 读取的却是合并后配置,会展开 include。经由 include 指令植入的过滤器对全部三处屏蔽不可见,却会被 checkout 执行:向 <common>/config 追加 [include] path = evil.inc、在被包含文件中写入 [filter "evil"] smudge = CMD、再追加一行 attributes——屏蔽报干净,被授权的 checkout 在宿主机上执行 CMD。回复已将此跟踪为 #10441,跟踪是对的,但机制在本提交上仍然成立,回复本身不能撤销阻塞项:在修复落地之前(在本 PR 或 #10441 中)该发现持续有效——用 --show-origin 解析每个命中的来源文件、仅当来源是仓库本地时才拒绝,或对任何仓库本地的 include.path / includeIf.*.path 键失败关闭。裸 --includes 不是修法:它会跟随指向用户全局配置的仓库本地 include,把 filter.lfs.clean 拖回来。

修复必须豁免全局配置中的过滤器——test-efficacy.test.ts 钉住用户全局配置里的 filter.lfs.clean 不得被拒绝(git-lfs 契约)。修复落地时,通过 [include] path + 外部文件 + 一行 attributes 植入过滤器的测试必须断言点名该键的拒绝——请确认它对当前代码为红,移除 include 处理后再次为红。

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

Comment on lines +591 to +592
* the `worktree add` spawns that CREATE the probe and base trees run
* unscreened today, and hooks are neutralised only at the spawns that pass

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] R2-1: [certifies-falsely] [new-surface] The worktree add spawns that CREATE the probe and base trees (test-efficacy.ts:2657, base-tree.ts:268) run before any screen. Issue #9558's Stage-2 triage names exactly this: the screen belongs at run entry "so a single check covers the restore, the revert, and any other checkout the probe phase performs" — and scratch-tree screens its own rebuild worktree add. The issue's own persistence incident replays through this checkout: reviewing a malicious PR, its probe code plants filter.evil.smudge = CMD into the shared common config; every later screened checkout rightly refuses, but the plant persists (discard/cleanup never wipe the common dir). When a follow-up PR committing a .gitattributes that selects the filter is reviewed, the creation checkout rewrites every tracked file through the committed attributes + common config — CMD executes on the host before the first screen (the baseline restore's screen at test-efficacy.ts:2762) ever runs.

Witness:

git config filter.evil.smudge 'sh -c "echo SMUDGE-FIRED >> …; cat"' + committed *.ts filter=evil
git worktree add --detach … HEAD → canary after worktree add: SMUDGE-FIRED (creation checkout executed the planted command)
Contrast: scratch-tree screens BEFORE its own rebuild worktree add (scratch-tree.ts:424-446)

Screen the creation checkout with the same shared helper before the spawn (the review worktree and probeTree resolve the same <common>/config); the throw lands in the creation try/catch that already records every probe/mutant/hunk as inconclusive not-run. Alternatively track it as a named follow-up the way #10441 tracks include.path — the docstring disclosure alone leaves the triage-named coverage incomplete. The hooks/fsmonitor arms at this same site ride the class finding at lines 593-594. The screen must stay repo-local-only — issue #9558: "not the user's global or system config, which is their own contract"; a merged-config check at this new site would reproduce the git-lfs permanent refusal. An integration test in the shape of the new revert test — plant filter.evil.smudge = touch <canary> before runHandler, commit the selecting .gitattributes, assert the canary never exists — must go red when the pre-creation screen is removed.

中文说明

创建探针树与基础树的 worktree add 派发(test-efficacy.ts:2657、base-tree.ts:268)在任何屏蔽之前运行。issue #9558 的 Stage-2 分诊原话点名了此处:屏蔽应放在运行入口,「一次覆盖恢复、回退以及探针阶段执行的任何其他 checkout」——scratch-tree 也正是这样在自己的重建 worktree add 之前屏蔽的。issue 自己的持久化事故可以在这个 checkout 上完成重放:审查恶意 PR 时,其探针代码把 filter.evil.smudge = CMD 植入共享 common 配置;此后所有被屏蔽的 checkout 都会正确拒绝,但植入物持续存在(discard/cleanup 从不清扫 common 目录)。当一个提交了选中该过滤器的 .gitattributes 的后续 PR 被审查时,创建 checkout 会重写每个已跟踪文件、穿过已提交的 attributes 与 common 配置——CMD 在第一个屏蔽(test-efficacy.ts:2762 的基线恢复屏蔽)运行之前就已在宿主机上执行。

修复:在创建派发之前用同一共享 helper 屏蔽(审查 worktree 与 probeTree 解析同一个 <common>/config),throw 落入已把每个探针/突变体/分块记为 inconclusive not-run 的创建 try/catch;或者像 #10441 跟踪 include.path 那样作为命名后续跟踪——仅在 docstring 中披露会让分诊点名的覆盖不完整。本位置的 hooks/fsmonitor 入口由 593-594 行的类发现承接。屏蔽必须保持仅仓库本地——issue #9558:「不是用户的全局或系统配置,那是他们自己的契约」;在这个新位置做合并配置检查会复刻 git-lfs 永久拒绝。以新回退测试的形状写一个集成测试——在 runHandler 之前植入 filter.evil.smudge = touch <canary>、提交选中它的 .gitattributes、断言金丝雀从不出现——移除创建前屏蔽后该测试必须变红。

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

Comment on lines +593 to +594
* `core.hooksPath` explicitly, not pipeline-wide. Do not read this comment as
* a completeness map — widening the screen means adding a call site, and the

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] R2-2: [certifies-falsely] [new-surface] Class finding — the non-filter command-execution surface at the pipeline's git spawns is unbounded; close it structurally, not entrance by entrance. The screen enumerates exactly three filter.* keys at a subset of sites; the checkouts/spawns it authorises or coexists with also execute core.fsmonitor commands and core.hooksPath-planted hooks at spawns the screen never sees — and one planted core.fsmonitor fires at git worktree add, git checkout, git diff, git status, and git ls-files alike: every fsmonitor-capable spawn in the pipeline is an entrance, so per-site enumeration provably cannot converge (this review's audit loop found a new entrance on this surface in nearly every round).

Demonstrated entrances, all live-witnessed this round: (1) the revert checkout this diff newly screens (test-efficacy.ts:2997/3024) executes a planted core.fsmonitor AND a planted post-checkout hook while the screen passes — its paired restore spawns neutralise both via the pre-existing inert array (test-efficacy.ts:1665-1670), the revert spawn passes neither, and githooks(5) documents that pathspec checkouts fire post-checkout (measured flag=0); (2) scratch-tree's reset/rebuild checkouts (scratch-tree.ts:282/525) execute a planted fsmonitor — NO_HOOKS covers hooks only; (3) the creation spawns (test-efficacy.ts:2657, base-tree.ts:268) and fetch-pr's review-worktree add (fetch-pr.ts:976) pass no screen and no -c overrides — the planted post-checkout hook fires.

Witness:

Revert site: core.fsmonitor planted → git checkout <base> -- a.ts → canary FSMONITOR-FIRED + "warning: Empty last update token."
post-checkout planted in the common hooks dir → git checkout <sha> -- a.ts → hook fired flag=0 (also git checkout -- a.ts)
Contrast, restore shape (-c core.hooksPath=… -c core.fsmonitor=) → canary ABSENT
Sweep: one planted fsmonitor FIRED at worktree add, checkout, diff HEAD (clean and dirty), status --porcelain, ls-files -v/--others/-s

Structural options rather than entrance-by-entrance patches: (a) generalise the codebase's own established shape — the restore's inert pair (test-efficacy.ts:1665) and the residue probe's -c core.fsmonitor= (worktree.ts:1067, "the measurement must not itself become the execution") — to every spawn the pipeline controls; and/or (b) take the sandboxed guest-execution decision the #9556 class defers to. Per-site one-line fixes exist (the revert spawn, scratch-tree's spawns, the creation spawns), but the sweep shows the next entrance keeps opening. Neutralise at this layer, never refuse: legitimate user config must not wedge the pipeline — test-efficacy.test.ts pins not.toContain('filter.lfs.clean'); per-spawn -c overrides neutralise for that spawn only and never refuse the user. An integration test in the shape of the new revert test — plant core.fsmonitor = touch <canary> and separately a common-dir post-checkout hook, assert the canaries never exist — must go red when the neutralisation is removed.

中文说明

类发现——流水线 git 派发上的非过滤器命令执行面是无界的;请结构性关闭,而不是逐入口打补丁。屏蔽只在部分位置枚举恰好三个 filter.* 键;它所授权或共存的 checkout/派发还会执行 core.fsmonitor 命令与 core.hooksPath 植入的钩子——而这些派发屏蔽从不检查:一个植入的 core.fsmonitorgit worktree addgit checkoutgit diffgit statusgit ls-files 上都会触发——流水线里每个能触发 fsmonitor 的派发都是一个入口,因此逐站点枚举可证明地无法收敛(本轮审计循环几乎每一轮都在这个面上找到新入口)。

本轮全部实测的入口:(1) 本 diff 新加屏蔽的回退 checkout(test-efficacy.ts:2997/3024)在屏蔽放行时执行植入的 core.fsmonitor 与植入的 post-checkout 钩子——成对的恢复派发经由既有的 inert 数组(test-efficacy.ts:1665-1670)把两者都中和了,回退派发两个都没传;githooks(5) 明确 pathspec checkout 会触发 post-checkout(实测 flag=0);(2) scratch-tree 的重置/重建 checkout(scratch-tree.ts:282/525)执行植入的 fsmonitor——NO_HOOKS 只覆盖钩子;(3) 创建派发(test-efficacy.ts:2657、base-tree.ts:268)与 fetch-pr 的审查 worktree add(fetch-pr.ts:976)既无屏蔽也无 -c 覆盖——植入的 post-checkout 钩子触发。

结构性选项(而非逐入口补丁):(a) 把代码库自己既有的形状推广开——恢复的 inert 对(test-efficacy.ts:1665)与残留探针的 -c core.fsmonitor=(worktree.ts:1067,「测量本身不得成为执行」)——覆盖流水线控制的每个派发;和/或 (b) 做出 #9556 类别所推迟的沙箱化客户代码执行决策。逐位置的一行修复是存在的(回退派发、scratch-tree 派发、创建派发),但上述扫荡表明下一个入口还会不断出现。在这一层只做中和、绝不拒绝:正当的用户配置不得让流水线陷入永久拒绝——test-efficacy.test.ts 钉住 not.toContain('filter.lfs.clean');逐派发的 -c 覆盖只对该次派发中和、从不拒绝用户。以新回退测试的形状写集成测试——植入 core.fsmonitor = touch <canary> 以及 common 目录的 post-checkout 钩子、断言金丝雀从不出现——移除中和后必须变红。

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

Comment on lines +597 to +598
* The planting surface is two plain writes a probe can
* make into the COMMON dir this command's report calls shared:

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] R2-3: [certifies-falsely] [new-surface] Planting into the user's GLOBAL config bypasses every screen this diff installs, and the completeness claim this diff re-issues is false. A probe runs as the same user: git config --global filter.evil.smudge CMD + git config --global core.attributesFile <file> (one line * filter=evil) — no repo write at all — and every screened checkout reads MERGED config and executes the plant while all three screens exit 1/clean. The plant persists outside every screen AND outside the common dir (~/.gitconfig is never wiped), firing on every later review and on the user's own git usage; a committed .gitattributes variant (no core.attributesFile) executes too. Reachability is confirmed: the unsandboxed runner spawn sets no env override (test-efficacy.ts:1816-1828) and sanitizedGitEnv() keeps HOME, so git config --global from probe code writes the user's real config.

Witness:

Isolated HOME: screen's exact spawn against the common config → exit 1 (CLEAN)
restore-shape checkout (-c core.hooksPath=… -c core.fsmonitor=) → executed the GLOBAL smudge, canary created
committed-.gitattributes variant → canary created

Two fix paths, author's choice: (a) add the resolved global file(s) to the candidates with an explicit decision about the git-lfs refusal cost — a name allowlist is unsafe (the attacker picks filter.lfs.smudge); a delta check (snapshot the user's global filter keys before the run, refuse keys appearing mid-run) would catch the plant without false-positiving pre-existing user filters, untested; or (b) correct the completeness claim this diff adds ("The planting surface is two plain writes … into the COMMON dir"; "a probe's planting surface is the repo-local files") and track the bypass the way #10441 tracks include.path. The git-lfs contract bounds any fix: git lfs install writes filter.lfs.clean globally, and test-efficacy.test.ts pins expect(afterDetail).not.toContain('filter.lfs.clean') — the user's own global filter must not wedge the pipeline. For path (a), a test under isolateHostGitConfig() planting via git config --global + core.attributesFile must refuse with the canary absent, and go red when the global file is removed from the candidate set.

中文说明

向用户全局配置植入可以绕过本 diff 安装的所有屏蔽,且本 diff 重新发布的完备性声明是假的。探针以同一用户身份运行:git config --global filter.evil.smudge CMD + git config --global core.attributesFile <file>(一行 * filter=evil)——完全不写仓库——每个被屏蔽的 checkout 读取的却是合并后配置并执行植入物,而三处屏蔽全部 exit 1 报干净。植入物存在于所有屏蔽之外、也存在于 common 目录之外(~/.gitconfig 从不被清除),在此后的每次审查与用户自己的 git 使用中触发;提交 .gitattributes 的变体(不用 core.attributesFile)同样执行。可达性已确认:非沙箱的 runner 派发不设置 env 覆盖(test-efficacy.ts:1816-1828),sanitizedGitEnv() 保留 HOME,因此探针代码里的 git config --global 写的就是用户的真实配置。

两条修复路径,由作者选择:(a) 把解析出的全局文件加入候选,并明确承担 git-lfs 拒绝成本的决定——按名字白名单不安全(攻击者可以取名 filter.lfs.smudge);差量检查(运行前快照用户的全局过滤器键、拒绝运行中新出现的键)可以在不误伤既有用户过滤器的前提下捕获植入,未经测试;或 (b) 修正本 diff 新增的完备性声明(「植入面是两次写入 COMMON 目录的普通写入」「探针的植入面是仓库本地文件」),并像 #10441 跟踪 include.path 那样跟踪该绕过。任何修复都受 git-lfs 契约约束:git lfs installfilter.lfs.clean 写入全局,test-efficacy.test.ts 钉住 expect(afterDetail).not.toContain('filter.lfs.clean')——用户自己的全局过滤器不得让流水线陷入永久拒绝。路径 (a) 需要一个在 isolateHostGitConfig() 下经由 git config --global + core.attributesFile 植入、断言拒绝且金丝雀不出现的测试,且把全局文件移出候选集后变红。

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

Comment on lines +714 to +717
for (const line of r.stdout.split('\n')) {
const key = line.split(/\s+/)[0];
if (key && !found.includes(key)) found.push(key);
}

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] R2-4: [fails-closed] [new-surface] Pattern (2 locations — this is the key loop; the candidate walk at lines 654-656 is the second). The 64 MiB maxBuffer this diff raises from the 1 MiB default feeds a quadratic found.includes(key) dedup over up to ~2.9M unique keys and returns the list unbounded. One write into the config this diff itself documents as attacker-writable and never wiped — ~3M unique [filter "<n>"] keys stay under the cap at ~22 bytes/key — hangs the screen for tens of hours of synchronous CPU on a screen that runs once per mutant, and the unbounded list becomes a ~60 MB filters.keys.join(', ') refusal string in the revert records/out.json. Pre-diff, the 1 MiB ceiling ENOBUFS-killed git and skipped the loop entirely.

Witness:

PR arm (unmodified): 50k keys → 36,618 ms; 100k → 169,896 ms; 2,931,939 keys fit under the cap → extrapolated 40.6 h
BASE arm (base's exact spawn, no maxBuffer, same 100k fixture): ENOBUFS/SIGTERM at 1,052,672 bytes — loop skipped
Fix arm: Set dedup + slice(0, RESIDUE_PATH_CAP) → 100k keys in 334 ms, 12 keys returned

Bound both dimensions: collect keys into a Set (linear) and cap the reported list the way the residue cap does — first N keys plus the total, "…and M more" in the three refusal messages (RESIDUE_PATH_CAP = 12 is this file's own precedent); cap the candidate list and fail closed over the bound (see the comment at lines 654-656). Short-circuiting on first key does NOT close the candidate variant (a clean-file plant produces no keys). The refusal must keep naming the first planted key — test-efficacy.test.ts toContain('filter.evil.smudge') and scratch-tree.test.ts toContain('filter.evil.process') go red if the cap reports zero keys; the unreadable JSDoc's own rule "an unbounded join is its own denial-of-service" (worktree.ts:611-624) is the precedent the caps extend. A test planting cap+5 distinct filter keys through runOneMutant must assert the detail still names filter. keys with the "more" marker — removing the cap must make it red.

中文说明

模式(2 处——此处是键循环;654-656 行的候选遍历是第二处)。本 diff 把 maxBuffer 从默认 1 MiB 提高到 64 MiB,却把它喂给一个平方复杂度的 found.includes(key) 去重、且返回的列表无上界。向这个 diff 自己记为攻击者可写、永不清扫的配置写入一次——约 300 万个唯一 [filter "<n>"] 键(每键约 22 字节)恰好留在上限之下——就能让每次突变体都要运行一次的屏蔽同步阻塞数十小时,且无界列表会在回退记录/out.json 中变成约 60 MB 的 filters.keys.join(', ') 拒绝字符串。diff 之前,1 MiB 上限会以 ENOBUFS 杀掉 git、整个循环被跳过。

修复:给两个维度都加上界——用 Set 收集键(线性),并像残留上限那样给上报列表加上限:前 N 个键 + 总数,三处拒绝消息显示「…and M more」(RESIDUE_PATH_CAP = 12 是本文件自己的先例);给候选列表加上限并在超限时失败关闭(见 654-656 行的评论)。按首个键短路不能关闭候选变体(干净文件植入不产生任何键)。拒绝消息必须继续点名第一个植入的键——若上限报出零个键,test-efficacy.test.ts 的 toContain('filter.evil.smudge') 与 scratch-tree.test.ts 的 toContain('filter.evil.process') 会变红;unreadable JSDoc 自己的规则「无界 join 本身就是拒绝服务」(worktree.ts:611-624)正是这些上限所延伸的先例。通过 runOneMutant 植入上限+5 个不同过滤器键的测试必须断言 detail 仍然点名 filter. 键并带有「more」标记——移除上限必须变红。

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

Comment on lines +657 to +659
} catch {
// No linked worktrees registered: the two candidates above are all of it.
}

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] R2-5: [certifies-falsely] The bare catch around the <common>/worktrees readdir conflates every failure with "no linked worktrees registered". A probe that strips the directory's READ bit (chmod 0111, keeping search) makes the enumeration silently drop every per-worktree candidate while git still opens each config.worktree by direct path — the screen answers clean and the scratch-tree reset/rebuild checkout executes the planted smudge. The defect defeats the defense this PR itself documents: the file-level unreadability refusal was added beside it, while the directory-level hole stands. Plant extensions.worktreeConfig true (never matches the filter regex) + the filter into <common>/worktrees/<scratch-label>/config.worktree, then chmod — readdirSync throws EACCES, the catch swallows it, and the reset checkout resolves the per-worktree config by path traversal (search bit suffices). The chmod also removes the write bit, so git worktree prune exits 0 without removing the plant — the state persists into every later review. (The test-efficacy restore/revert sites are NOT bypassable this way — their screened tree's own config.worktree is candidate #2 via --git-dir — the fix belongs in the enumeration.)

Witness:

ROW-A pre-chmod control: screen catches the plant {"keys":["filter.evil.smudge"]}
ROW-B post-chmod: readdirSync EACCES → screen {"keys":[],"unreadable":null} (CLEAN) → checkout exit 0 → canary exists
ROW-B2 end-to-end: runScratchTree → available:true ("tracked files restored") and the reset checkout ran the plant
ROW-D: git worktree prune exit 0 — planted config.worktree still present
Fix arm (catch narrowed to ENOENT): screen unreadable=<common>/worktrees → runScratchTree refuses → canary absent; no-worktrees repo stays clean
Suggested change
} catch {
// No linked worktrees registered: the two candidates above are all of it.
}
} catch (e) {
// ENOENT is "no linked worktrees registered" — the two candidates above
// are all of it. Anything else is a directory this screen could not walk,
// and git opens each config.worktree below it by direct path.
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {
return { keys: [], unreadable: join(common, 'worktrees') };
}
}

The fix must populate the existing unreadable field — a new field fires none of the three callers' refusal branches (scratch-tree.ts:427, test-efficacy.ts:1656, :2998). A scratch-tree.test.ts case — enable extensions.worktreeConfig, plant the filter in a second linked worktree's config.worktree, chmod 0o111 the admin dir, assert available === false with 'could not be read to the end' — must go red when the catch reverts to the bare swallow.

中文说明

围绕 <common>/worktrees readdir 的裸 catch 把一切失败都当作「没有已注册的链接 worktree」。剥离该目录读位(chmod 0111,保留搜索位)的探针会让枚举静默丢弃所有按 worktree 的候选,而 git 仍按直接路径打开每个 config.worktree——屏蔽报干净,scratch-tree 的重置/重建 checkout 执行植入的 smudge。这个缺陷击败的正是本 PR 自己记下的防御:文件级不可读拒绝刚加在旁边,目录级的洞却仍在。植入 extensions.worktreeConfig true(永不匹配过滤器正则)+ 把过滤器写入 <common>/worktrees/<scratch-label>/config.worktree,然后 chmod——readdirSync 抛 EACCES,catch 吞掉,重置 checkout 按路径穿越解析该按 worktree 的配置(有搜索位即可)。chmod 同时移除了写位,git worktree prune 会 exit 0 却不移除植入物——状态持续到此后的每次审查。(test-efficacy 的恢复/回退位置不能被此手法绕过——被屏蔽树自己的 config.worktree 是候选 #2、经由 --git-dir 到达——修复属于枚举本身。)

修复必须填充既有的 unreadable 字段——新字段不会触发三个调用方的任何一个拒绝分支(scratch-tree.ts:427、test-efficacy.ts:1656、:2998)。scratch-tree.test.ts 用例——启用 extensions.worktreeConfig、把过滤器植入第二个链接 worktree 的 config.worktree、对管理目录 chmod 0o111、断言 available === false 且笔记包含 'could not be read to the end'——在 catch 回退为裸吞时必须变红。

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

Comment on lines +973 to +975
// Same positive-control semantics as the default fake: the injected
// always-failing test must actually fail, or the run re-classes to
// control-failed and never reaches the revert this test is about.

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 reworked dead-runner fake uses fs.readFileSync but never imports fs — every one of the ~10 sibling fakes in this file carries import fs from 'node:fs';. Every st() call throws a ReferenceError the bare catch swallows into 'passed', so the added logic is dead code and the fixture is byte-equivalent to the unconditional all-pass fake it replaced — while the comment above it claims the opposite ("the injected always-failing test must actually fail"). The accident is load-bearing: 're-classes every survivor and spends nothing when the positive control fails' REQUIRES a dead runner whose injected control stays green. A maintainer reconciling the fake with its comment or its siblings adds the import — the control then genuinely fails, harnessValidated flips true, mutants run, and the test's assertions (harnessValidated toBe(false), mutants.probed toEqual([]), skippedForControl toBe(1), the note containing 'positive control FAILED') all break — inviting an assertion "fix" that silently deletes the dead-runner coverage this test exists to pin.

Witness:

Committed script verbatim against a marker file → {"numPassedTests":1,"numFailedTests":0, … [{"status":"passed"}]}
Same script + import fs from 'node:fs'; → {"numPassedTests":0,"numFailedTests":1, … [{"status":"failed"}]}

Restore the honest dead-runner shape the test needs — assertionResults: [{ status: 'passed' }], numFailedTests: 0, no st — and drop the inverted comment (or rewrite it to say the dead runner reports even the injected control green by design). The fix must keep every file, control included, reported passed — the test's own contract comment (~line 964): "A runner that reports green unconditionally — it never reads the file, so the injected control is green too."

中文说明

重写后的死运行器 fake 使用了 fs.readFileSync 却从未导入 fs——本文件里约 10 个兄弟 fake 全都带着 import fs from 'node:fs';。每次 st() 调用都抛出 ReferenceError、被裸 catch 吞成 'passed',所以新增逻辑是死代码,该夹具与它所替换的无条件全过 fake 字节等价——而它上方的注释却声称相反(「注入的总是失败的测试必须真的失败」)。这个意外是承重结构:'re-classes every survivor and spends nothing when the positive control fails' 需要一个注入对照保持绿色的死运行器。维护者若按注释或兄弟 fake 对齐、补上导入——对照就真的失败了,harnessValidated 翻为 true,突变体开始运行,测试的断言(harnessValidated toBe(false)、mutants.probed toEqual([])、skippedForControl toBe(1)、包含 'positive control FAILED' 的笔记)全部破裂——诱发一次断言「修复」,悄悄删掉这个测试所要钉住的死运行器覆盖。

修复:恢复该测试真正需要的诚实死运行器形状——assertionResults: [{ status: 'passed' }]numFailedTests: 0、去掉 st——并删除这条反转的注释(或改写为「死运行器按设计对注入对照也报绿」)。修复必须让每个文件(含对照)继续报 passed——测试自己的契约注释(约 964 行):「无条件报绿的运行器——它从不读文件,因此注入的对照也是绿的。」

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

// failure as a tripwire that fires on every healthy run.
const filters = localFilterCommands(probeTree);
if (filters.unreadable) {
return `the repository's local config file ${filters.unreadable} could not be read to the end, so whether this tree's restore would EXECUTE a content filter is unknown`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The two new test-efficacy refusal messages interpolate the plant-controlled filters.unreadable path and filters.keys raw, while scratch-tree.ts:429-441 — in this same diff — flattens the identical LocalFilterScreen outputs through inertPath (lib/paths.ts:402), the helper that exists so control/invisible characters in attacker-named values never reach agent-facing sinks. A config subsection name legally carries control/format characters and --get-regexp prints them verbatim; the sink trace settles it: these detail strings reach exactly one sink — the agent-facing out.json via JSON.stringify, which escapes Cc but NOT Cf (U+202E bidi overrides), Zl/Zp, or backticks — so those classes ride the agent-facing report unflattened, exactly where inertPath's docstring says such values must be flattened.

Witness:

git config "filter.$(printf 'evil\x1bX').smudge" … → config line [filter "evil^[X"] — --get-regexp prints the raw control byte
Sink trace: mutantResults[].detail / revert 'probe could not run' record → only writeFileSync(out, JSON.stringify(…)) — no terminal/Markdown sink, but Cf/Zl/Zp/backtick survive JSON escaping
Suggested change
return `the repository's local config file ${filters.unreadable} could not be read to the end, so whether this tree's restore would EXECUTE a content filter is unknown`;
return `the repository's local config file ${inertPath(filters.unreadable)} could not be read to the end, so whether this tree's restore would EXECUTE a content filter is unknown`;

Apply the same wrap to filters.keys at line 1660 and to the revert twin at lines 2998-3008. Mirror scratch-tree.ts:429/437-441 exactly — reuse inertPath rather than a narrower local escape, or the two consumers drift. A variant of 'refuses to run through a repo-LOCAL content filter' planting a key with U+202E or ESC and asserting r.detail carries the flattened form must go red when the inertPath calls are removed.

中文说明

test-efficacy 的两条新拒绝消息把植入可控的 filters.unreadable 路径与 filters.keys 原样插值,而同一 diff 中的 scratch-tree.ts:429-441 却把同样的 LocalFilterScreen 输出经 inertPath(lib/paths.ts:402)压平——这个 helper 的存在就是为了不让攻击者命名值里的控制/隐形字符到达面向 agent 的汇聚点。配置子节名合法地携带控制/格式字符,--get-regexp 原样输出;汇聚点追踪给出了结论:这些 detail 字符串只到达一个汇聚点——经 JSON.stringify 写入面向 agent 的 out.json,JSON.stringify 会转义 Cc,但不会转义 Cf(U+202E 双向覆盖符)、Zl/Zp 与反引号——这些类别就这样未经压平进入面向 agent 的报告,恰是 inertPath 文档声明必须压平之处。

对 1660 行的 filters.keys 与 2998-3008 行的回退孪生消息做同样包裹。严格镜像 scratch-tree.ts:429/437-441——复用 inertPath 而不是更窄的本地转义,否则两个消费方会漂移。以「拒绝穿过仓库本地内容过滤器运行」的变体植入含 U+202E 或 ESC 的键、断言 r.detail 携带压平形式——移除 inertPath 调用后必须变红。

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

Comment on lines +709 to +712
if (r.error || (r.status !== 0 && r.status !== 1)) {
unreadable ??= file;
continue;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The fail-closed branch for a spawn failure or unexpected git config exit status — a named hardening of this round — has no test: every existing 'unreadable' case is caught earlier by the readability pre-check (statSync().isFile()), and the bigvalue test pads to ~1.2 MB, far under the 64 MiB cap, so git exits 0. Mutate the branch to a plain continue (the pre-hardening fail-open skip) and all three changed test files stay green; then a planted config the enumeration cannot complete — a smudge value padded past 64 MiB (ENOBUFS, r.error set) or a malformed config line git dies on with a non-0/1 exit (measured: exit 128) — reports the repository clean, and the checkout the screen authorises executes the filter. The PR's mutation-table row covers only the COMBINED maxBuffer+branch revert, which the maxBuffer half alone catches.

Witness:

Malformed config.worktree — git exits 128:
INTACT (PR code): {"keys":[],"unreadable":"…/config.worktree"} (refusal)
Branch relaxed to `continue`: {"keys":[],"unreadable":null} (clean); the relaxed mutant ran the changed suites green (181 + 31 passed)

Add a test that fails the enumeration itself: append a malformed line (e.g. a top-level not git config line) to a config.worktree fixture so git config --file dies non-0/1, then expect the inconclusive verdict with 'could not be read to the end' via runOneMutant. The witness must keep the candidate a readable regular file so it reaches the spawn branch, not the readability pre-check (worktree.ts:676-682). The new test must go red if the branch is relaxed to continue.

中文说明

针对派发失败或意外 git config 退出码的失败关闭分支——本轮点名的加固——没有测试:现有的每个「不可读」用例都被更早的可读性预检(statSync().isFile())捕获,而 bigvalue 测试只填充到约 1.2 MB、远低于 64 MiB 上限,因此 git exit 0。把该分支突变为裸 continue(加固前的失败开放跳过),三个改动的测试文件全部保持绿色;此时一个令枚举无法完成的植入配置——填充超过 64 MiB 的 smudge 值(ENOBUFS、r.error 置位)或让 git 以非 0/1 退出码死掉的畸形配置行(实测 exit 128)——会报仓库干净,而被屏蔽授权的 checkout 会执行该过滤器。PR 描述里的变异表一行只覆盖了 maxBuffer+分支的组合回退,而那一行单靠 maxBuffer 一半就能抓住。

修复:增加一个让枚举本身失败的测试——向 config.worktree 夹具追加一行畸形行(例如顶层的 not git config),使 git config --file 以非 0/1 死掉,然后经 runOneMutant 断言带有 'could not be read to the end' 的 inconclusive 判定。见证必须保持候选是一个可读的常规文件、从而到达派发分支而非可读性预检(worktree.ts:676-682)。分支被放宽为 continue 时,新测试必须变红。

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

Comment on lines +427 to +429
if (filters.unreadable) {
return unavailable(
`the repository's local config file ${inertPath(filters.unreadable)} ` +

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] unreadable is wired into three call sites but only one is tested: the restore side is pinned by test-efficacy.test.ts 'refuses a config candidate it cannot read — not "clean"'; the new runScratchTree branch here and the revert-phase throw (test-efficacy.ts:2998-3004) have no test at either site. Delete either untested if (filters.unreadable) block and the whole suite stays green — then a repository where a config candidate cannot be read (e.g. config.worktree replaced by a directory — the shape the restore test uses) is reported clean at those two sites, which proceed through a config the screen could not check: the fail-closed guarantee silently degrades to fail-open at two of the three screened sites.

Witness:

BOTH unreadable blocks deleted in the scratch tree → all three changed test files pass: Tests 212 passed (212)
Deleting the scratch-tree block alone: 181 passed (181), still green

Mirror the restore test at the scratch-tree site: after the first run(), mkdirSync a directory at <common>/worktrees/<label>/config.worktree (discovered the way the other-worktree test at scratch-tree.test.ts:135 discovers it) and assert available === false with the note containing 'could not be read to the end'; add the revert-phase unreadable twin beside the new revert integration test. The new tests must go red when either unreadable branch is deleted.

中文说明

unreadable 接入了三个调用点,但只有一个被测试:恢复侧由 test-efficacy.test.ts 的「拒绝穿过不可读的配置候选——而不是报干净」钉住;这里新的 runScratchTree 分支与回退阶段的 throw(test-efficacy.ts:2998-3004)在两处都没有测试。删除任一未被测试的 if (filters.unreadable) 块,整个套件保持绿色——于是配置候选不可读的仓库(例如 config.worktree 被换成目录——恢复测试所用的形状)在这两处会被报干净,照常穿过屏蔽未能检查的配置:失败关闭的保证在三处被屏蔽位置中的两处静默退化为失败开放。

修复:在 scratch-tree 位置镜像恢复测试——第一次 run() 之后,在 <common>/worktrees/<label>/config.worktree 处 mkdirSync 一个目录(按 scratch-tree.test.ts:135 的 other-worktree 测试发现它的方式),断言 available === false 且笔记包含 'could not be read to the end';在新回退集成测试旁加上回退阶段不可读的孪生用例。删除任一 unreadable 分支时,新测试必须变红。

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

@CanReader CanReader left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Flagging a merge-ordering dependency I ran into while reviewing this alongside #10474, because it is invisible from either PR on its own.

On origin/main today, localFilterCommands is defined in packages/cli/src/commands/review/scratch-tree.ts and does not exist in lib/worktree.ts:

git show origin/main:.../scratch-tree.ts | grep -c 'function localFilterCommands'  -> 1
git show origin/main:.../lib/worktree.ts | grep -c 'function localFilterCommands'  -> 0

This PR is the one that performs the move — deleting the definition from scratch-tree.ts and importing it from worktree.ts. Meanwhile #10474 ("resolve included filters by origin") edits that same function in lib/worktree.ts, i.e. it is written against a tree where this PR has already landed.

So the two are stacked whether or not they are labelled that way: #10474 cannot merge cleanly before this one, and if it somehow does, it is patching a file that does not contain the function. Worth making the dependency explicit in one of the two descriptions so whoever merges does not pick the wrong order. Since #10474 is a security fix (it closes an include.path bypass in the filter screen) there is some pressure to land it quickly, which makes getting the order right more than cosmetic.

On the move itself: putting the screen next to the other worktree-scoped git helpers makes sense, and the function's long comment travels with it rather than being left behind — the comment is doing real work here, since the --file-not-merged-config decision is the sort of thing someone would otherwise "simplify" later.

Two things I would check before merging, which I could not settle from the diff alone: that the moved function's sanitizedGitEnv() and any other helpers it closes over are available at the new location rather than newly imported from the old module (a cycle between scratch-tree.ts and lib/worktree.ts would be easy to introduce here), and that readdirSync — which the diff drops from scratch-tree.ts's imports — has no remaining use in that file.

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

Labels

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: extend the content-filter screen to test-efficacy, scoped to repo-local config (not global)

3 participants