@zvec/zvec-grep 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +34 -77
  2. package/README_CN.md +39 -76
  3. package/dist/cli/index.js +0 -0
  4. package/dist/daemon/backend.d.ts +1 -0
  5. package/dist/daemon/backend.js +72 -79
  6. package/dist/daemon/backend.js.map +1 -1
  7. package/dist/daemon/change-set.d.ts +5 -0
  8. package/dist/daemon/change-set.js +75 -13
  9. package/dist/daemon/change-set.js.map +1 -1
  10. package/dist/daemon/index-coordinator.d.ts +1 -3
  11. package/dist/daemon/index-coordinator.js +8 -21
  12. package/dist/daemon/index-coordinator.js.map +1 -1
  13. package/dist/daemon/root-runtime.js +2 -2
  14. package/dist/daemon/root-runtime.js.map +1 -1
  15. package/dist/daemon/server-controller.js +88 -15
  16. package/dist/daemon/server-controller.js.map +1 -1
  17. package/dist/daemon/watch-manager.d.ts +5 -0
  18. package/dist/daemon/watch-manager.js +68 -21
  19. package/dist/daemon/watch-manager.js.map +1 -1
  20. package/dist/engine/extraction/code/extractor.d.ts +5 -0
  21. package/dist/engine/extraction/code/extractor.js +41 -21
  22. package/dist/engine/extraction/code/extractor.js.map +1 -1
  23. package/dist/engine/extraction/index.d.ts +1 -1
  24. package/dist/engine/extraction/index.js +1 -1
  25. package/dist/engine/extraction/index.js.map +1 -1
  26. package/dist/engine/extraction/runtime.d.ts +6 -1
  27. package/dist/engine/extraction/runtime.js +11 -0
  28. package/dist/engine/extraction/runtime.js.map +1 -1
  29. package/dist/engine/extraction/vector-content.d.ts +1 -1
  30. package/dist/engine/extraction/vector-content.js +7 -19
  31. package/dist/engine/extraction/vector-content.js.map +1 -1
  32. package/dist/engine/pipeline/indexing/index.js +40 -21
  33. package/dist/engine/pipeline/indexing/index.js.map +1 -1
  34. package/dist/engine/pipeline/indexing/scanner/index.d.ts +2 -0
  35. package/dist/engine/pipeline/indexing/scanner/index.js +68 -11
  36. package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -1
  37. package/dist/engine/storage/index.d.ts +1 -0
  38. package/dist/engine/storage/index.js.map +1 -1
  39. package/dist/engine/storage/zvec.js +22 -4
  40. package/dist/engine/storage/zvec.js.map +1 -1
  41. package/dist/mcp/stdio-bridge.d.ts +2 -0
  42. package/dist/mcp/stdio-bridge.js +7 -4
  43. package/dist/mcp/stdio-bridge.js.map +1 -1
  44. package/docs/06-server.md +4 -0
  45. package/package.json +3 -1
package/README.md CHANGED
@@ -3,9 +3,6 @@
3
3
  </p>
4
4
 
5
5
  <div align="center">
6
- <p>
7
- <a href="./docs/08-roadmap.md"><img src="https://img.shields.io/badge/status-work%20in%20progress-F59E0B?style=for-the-badge" alt="Work in progress" /></a>
8
- </p>
9
6
  <picture>
10
7
  <source media="(prefers-color-scheme: dark)" srcset="./.github/assets/zg-logo-dark.svg">
11
8
  <img src="./.github/assets/zg-logo.svg" width="150" alt="zg logo" />
@@ -42,9 +39,6 @@ terminal, or let your agent use it for you.
42
39
  <img src="./.github/assets/zvec-grep-tour.gif" width="1000" alt="Install the agent integration, index a workspace, and let the agent search it with zvec-grep" />
43
40
  </div>
44
41
 
45
- Install the integration once, index the workspace, then search from the terminal
46
- or ask your agent naturally.
47
-
48
42
  <a id="features"></a>
49
43
 
50
44
  ## 💫 Why zg?
@@ -64,47 +58,36 @@ or ask your agent naturally.
64
58
 
65
59
  ## 🚀 Try it yourself
66
60
 
67
- Requires Node.js 22 or newer and a configured
68
- [OpenCode](https://opencode.ai/) installation. The agent example explicitly
69
- selects a free model, so it does not depend on your default provider.
70
-
71
61
  ### 1. Set up a sample bookshelf
72
62
 
73
63
  ```bash
64
+ # Requires Node.js 22 or newer.
74
65
  npm install -g @zvec/zvec-grep
75
- zg install --target opencode --yes
76
66
 
77
67
  mkdir zg-mystery && cd zg-mystery
78
- curl -L https://www.gutenberg.org/files/11/11-0.txt -o alice-in-wonderland.txt
79
- curl -L https://www.gutenberg.org/files/345/345-0.txt -o dracula.txt
80
- curl -L https://www.gutenberg.org/files/84/84-0.txt -o frankenstein.txt
81
- curl -L https://www.gutenberg.org/files/834/834-0.txt -o sherlock-holmes.txt
68
+ curl --retry 3 --retry-all-errors --progress-bar -fL \
69
+ -o alice-in-wonderland.txt https://raw.githubusercontent.com/GITenberg/Alice-s-Adventures-in-Wonderland_11/master/11.txt \
70
+ -o sherlock-holmes.txt https://raw.githubusercontent.com/GITenberg/The-Memoirs-of-Sherlock-Holmes_834/master/834.txt
82
71
 
83
72
  zg index --embedding local/potion-retrieval-32m
84
73
  ```
85
74
 
86
- This creates a small bookshelf of four public-domain novels and indexes it
87
- locally with Potion Retrieval.
88
-
89
- Installation starts the shared daemon immediately. The default MCP transport is
90
- stdio: the agent runs `zg server --stdio`, which safely starts or reuses it
91
- again after a reboot. Advanced users can keep direct HTTP configuration with
92
- `zg install --mcp-transport http` and `zg server on`.
93
-
94
- Use `zg install --mcp-toolset full` when the installed MCP integration should
95
- also expose managed rg, index, and status tools. The default is `agent`.
96
-
97
75
  ### 2. Choose how to search
98
76
 
99
77
  #### For agents: ask with OpenCode
100
78
 
79
+ With [OpenCode](https://opencode.ai/) configured:
80
+
101
81
  ```bash
82
+ zg install --target opencode --yes
102
83
  opencode run --model opencode/deepseek-v4-flash-free \
103
84
  "An unseen creature left a few marks. What did the detective infer? Cite local evidence."
104
85
  ```
105
86
 
106
- OpenCode chooses zg on its own—the prompt does not name a tool. Query rewrites
107
- may vary by model, but a typical run looks like this:
87
+ OpenCode chooses zg on its own—the prompt does not name a tool.
88
+
89
+ <details>
90
+ <summary><strong>See the full agent run and answer</strong></summary>
108
91
 
109
92
  ```text
110
93
  ⚙ zvec_grep_zvec_grep_search {"root":".../zg-mystery","query":"An unseen creature left a few marks. What did the detective infer?","fts":["marks","unseen creature","inferred","detective"],"fuse":true}
@@ -128,6 +111,8 @@ Holmes also inferred that a man entered through the window with it and later
128
111
  left with the key (sherlock-holmes.txt:5464-5470, 5527-5528).
129
112
  ```
130
113
 
114
+ </details>
115
+
131
116
  #### For humans: search directly
132
117
 
133
118
  Search the same bookshelf directly, without an agent:
@@ -136,60 +121,32 @@ Search the same bookshelf directly, without an agent:
136
121
  zg query --human "An unseen creature left a few marks. What did the detective infer?" --limit 3
137
122
  ```
138
123
 
139
- zg returns the relevant passages from `sherlock-holmes.txt`, ranked ahead of the
140
- other novels.
124
+ zg returns the relevant passages from `sherlock-holmes.txt`, ranked ahead of
125
+ `alice-in-wonderland.txt`.
141
126
 
142
127
  <a id="benchmarks"></a>
143
128
 
144
129
  ## 📊 Benchmarks
145
130
 
146
- zg is a general-purpose retrieval tool. Current benchmarks cover code and text
147
- retrieval.
148
-
149
- ### Repository code retrieval
150
-
151
- We use [SWE-QA-Bench](https://github.com/peng-weihan/SWE-QA-Bench), which
152
- requires cross-file and multi-hop reasoning over real-world codebases.
153
-
154
- - **Coverage:** 20 retrieval-intensive tasks covering the paper's four
155
- top-level categories—**What, Where, How, and Why**—across 8 intentions and
156
- 11 repositories.
157
- - **Agent:** Claude Code with **Claude Opus 5**.
158
- - **Embedding:** **Qwen3.7 Text Embedding** for the zg profile.
159
- - **Protocol:** 3 runs per task and profile.
160
-
161
- | Profile | Judge /100 ↑ | Input tokens ↓ | Tool calls ↓ | Time (s) ↓ | Cost ↓ |
162
- | --- | ---: | ---: | ---: | ---: | ---: |
163
- | Native tools | 80.42 | 558,651 | 23.42 | 127.5 | $0.905 |
164
- | Native tools + zg | 81.92 | 294,262 | 9.70 | 79.7 | $0.558 |
165
- | Change | **+1.50 pp** | **−47.3%** | **−58.6%** | **−37.5%** | **−38.3%** |
166
-
167
- Across 20 tasks × 3 runs, zg increased the Judge score by 1.50 points while
168
- reducing input tokens by 47.3%, tool calls by 58.6%, execution time by 37.5%,
169
- and cost by 38.3%.
170
-
171
- ### Large-scale text retrieval
172
-
173
- We use [BrowseComp-Plus](https://github.com/texttron/BrowseComp-Plus), which
174
- requires multi-document evidence retrieval over a fixed web corpus. The
175
- evaluation includes 30 queries covering 10 mapped topics and three
176
- evidence-breadth levels, with 3 runs per profile.
177
-
178
- | Profile | Accuracy ↑ | Evidence recall ↑ | Search calls ↓ | Input tokens ↓ | Time (s) ↓ | Cost ↓ |
179
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
180
- | Agent + BM25 | TBD | TBD | TBD | TBD | TBD | TBD |
181
- | Agent + zg | TBD | TBD | TBD | TBD | TBD | TBD |
182
- | Change | TBD | TBD | TBD | TBD | TBD | TBD |
183
-
184
- Across 30 queries × 3 runs, zg changed accuracy by TBD pp, evidence recall by
185
- TBD pp, search calls by TBD%, input tokens by TBD%, time by TBD%, and cost by
186
- TBD%.
187
-
188
- To reproduce the evaluation, follow the
189
- [benchmark guide](./benchmarks/README.md) for environment setup, pinned
190
- revisions, task manifests, baseline configuration, exact commands, and result
191
- aggregation. All profiles use the same agent, model, prompt, tool budget, and
192
- three-run protocol.
131
+ zg helps agents find relevant evidence faster by **narrowing the effective
132
+ search space** — using **fewer tokens, tool calls, and less time** while
133
+ preserving answer quality.
134
+
135
+ Each benchmark follows a **controlled, reproducible paired A/B protocol**: the
136
+ same agent runs the same pinned tasks with identical model, prompt, environment,
137
+ and limits; **only `zg` access and usage guidance differ**.
138
+
139
+ <img src="./.github/assets/benchmark-comparison-v2.svg" alt="Paired baseline and zvec-grep comparison for SWE-QA-Bench and BrowseComp-Plus" width="900" />
140
+
141
+ | Benchmark | Answer&nbsp;quality&nbsp;↑ | Avg.&nbsp;input&nbsp;tokens&nbsp;↓ | Avg.&nbsp;tool&nbsp;calls&nbsp;↓ | Avg.&nbsp;agent&nbsp;time&nbsp;↓ |
142
+ | --- | ---: | ---: | ---: | ---: |
143
+ | [**SWE-QA-Bench**](./benchmarks/swe-qa-bench/README.md)<br>20 tasks · Codebase QA | Judge Score<br>80.42&nbsp;→&nbsp;81.92 | 558,651&nbsp;→&nbsp;294,262 | 23.42&nbsp;→&nbsp;9.70 | 127.5s&nbsp;→&nbsp;79.7s |
144
+ | [**BrowseComp-Plus**](./benchmarks/browse-comp-plus/README.md)<br>80 cases · Deep-research QA | Accuracy<br>90.00%&nbsp;→&nbsp;90.00% | 2.04M&nbsp;→&nbsp;1.19M | 22.70&nbsp;→&nbsp;14.24 | 284.8s&nbsp;→&nbsp;199.3s |
145
+
146
+ Values show **Baseline → zg**.
147
+
148
+ See the [benchmark documentation](./benchmarks/README.md) for full results and
149
+ reproduction details.
193
150
 
194
151
  ## 📚 Documentation
195
152
 
package/README_CN.md CHANGED
@@ -3,9 +3,6 @@
3
3
  </p>
4
4
 
5
5
  <div align="center">
6
- <p>
7
- <a href="./docs/08-roadmap.md"><img src="https://img.shields.io/badge/status-work%20in%20progress-F59E0B?style=for-the-badge" alt="项目开发中" /></a>
8
- </p>
9
6
  <picture>
10
7
  <source media="(prefers-color-scheme: dark)" srcset="./.github/assets/zg-logo-dark.svg">
11
8
  <img src="./.github/assets/zg-logo.svg" width="150" alt="zg logo" />
@@ -42,9 +39,6 @@
42
39
  <img src="./.github/assets/zvec-grep-tour.gif" width="1000" alt="安装 Agent 集成、为工作区建索引并让 Agent 使用 zvec-grep 检索本地内容" />
43
40
  </div>
44
41
 
45
- 只需安装一次集成并为工作区建索引,之后既可以在终端中搜索,也可以直接向
46
- Agent 提问。
47
-
48
42
  <a id="features"></a>
49
43
 
50
44
  ## 💫 为什么选择 zg?
@@ -64,67 +58,61 @@ Agent 提问。
64
58
 
65
59
  ## 🚀 动手体验
66
60
 
67
- 需要 Node.js 22 或更新版本,以及已经完成配置的
68
- [OpenCode](https://opencode.ai/)。Agent 示例会显式选择免费模型,不依赖你配置的
69
- 默认模型提供方。
70
-
71
61
  ### 1. 准备示例书架
72
62
 
73
63
  ```bash
64
+ # 需要 Node.js 22 或更新版本。
74
65
  npm install -g @zvec/zvec-grep
75
- zg install --target opencode --yes
76
66
 
77
67
  mkdir zg-mystery && cd zg-mystery
78
- curl -L https://www.gutenberg.org/files/11/11-0.txt -o alice-in-wonderland.txt
79
- curl -L https://www.gutenberg.org/files/345/345-0.txt -o dracula.txt
80
- curl -L https://www.gutenberg.org/files/84/84-0.txt -o frankenstein.txt
81
- curl -L https://www.gutenberg.org/files/834/834-0.txt -o sherlock-holmes.txt
68
+ curl --retry 3 --retry-all-errors --progress-bar -fL \
69
+ -o alice-in-wonderland.txt https://raw.githubusercontent.com/GITenberg/Alice-s-Adventures-in-Wonderland_11/master/11.txt \
70
+ -o sherlock-holmes.txt https://raw.githubusercontent.com/GITenberg/The-Memoirs-of-Sherlock-Holmes_834/master/834.txt
82
71
 
83
72
  zg index --embedding local/potion-retrieval-32m
84
73
  ```
85
74
 
86
- 这会创建一个包含四本公版小说的小书架,并使用 Potion Retrieval 在本地建立索引。
87
-
88
- 安装时会立即启动共享 Daemon。默认 MCP 传输方式为 stdio:Agent 会运行
89
- `zg server --stdio`,电脑重启后该命令会再次安全启动或复用 Daemon。高级用户
90
- 可以通过 `zg install --mcp-transport http` 保留 HTTP 直连,并自行使用
91
- `zg server on` 管理后续启动。
92
-
93
- 如需让安装后的 MCP 集成额外暴露 managed rg、index 和 status 工具,可使用
94
- `zg install --mcp-toolset full`;默认值为 `agent`。
95
-
96
75
  ### 2. 选择检索方式
97
76
 
98
77
  #### Agent:通过 OpenCode 提问
99
78
 
79
+ 配置好 [OpenCode](https://opencode.ai/) 后:
80
+
100
81
  ```bash
82
+ zg install --target opencode --yes
101
83
  opencode run --model opencode/deepseek-v4-flash-free \
102
84
  "An unseen creature left a few marks. What did the detective infer? Cite local evidence."
103
85
  ```
104
86
 
105
- Prompt 中没有指定任何工具,OpenCode 会自主选择 zg。不同模型改写的
106
- Query 可能略有不同,一次典型的执行过程如下:
87
+ Prompt 中没有指定任何工具,OpenCode 会自主选择 zg。
88
+
89
+ <details>
90
+ <summary><strong>展开查看完整 Agent 调用与回答</strong></summary>
107
91
 
108
92
  ```text
109
93
  ⚙ zvec_grep_zvec_grep_search {"root":".../zg-mystery","query":"An unseen creature left a few marks. What did the detective infer?","fts":["marks","unseen creature","inferred","detective"],"fuse":true}
110
94
  → Read sherlock-holmes.txt [offset=5440, limit=120]
111
95
 
112
96
  Answer:
113
- 侦探是《驼背人》中的福尔摩斯。根据动物留下的脚印,他推断:
114
-
115
- - 它不是狗、猫、猴子或其他常见动物:脚印有五个肉垫和长爪,整个脚印
116
- 接近一把甜点勺大小(sherlock-holmes.txt:5479-5486)。
117
- - 它身体细长、四肢很短:身长接近两英尺,步幅却只有约三英寸
118
- (sherlock-holmes.txt:5497-5502)。
119
- - 它是食肉动物,因为它爬上窗帘,试图接近金丝雀笼
120
- (sherlock-holmes.txt:5509-5510)。
121
- - 它很可能属于鼬或白鼬一类,但体型异常大
122
- (sherlock-holmes.txt:5514-5516)。
123
-
124
- 福尔摩斯还推断,一名男子和它一起从窗户进入房间,离开时带走了钥匙
125
- (sherlock-holmes.txt:5464-5470, 5527-5528)。
97
+ The detective is Sherlock Holmes in *The Adventure of the Crooked Man*.
98
+ From the animal's foot-marks, he inferred:
99
+
100
+ - It was not a dog, cat, monkey, or other familiar creature: its print had
101
+ five foot-pads, long nails, and was nearly as large as a dessert-spoon
102
+ (sherlock-holmes.txt:5479-5486).
103
+ - It had a long body and very short legs: nearly two feet long, but with a
104
+ stride of only about three inches (sherlock-holmes.txt:5497-5502).
105
+ - It was carnivorous because it climbed the curtain toward a canary's cage
106
+ (sherlock-holmes.txt:5509-5510).
107
+ - It was probably from the weasel or stoat tribe, though unusually large
108
+ (sherlock-holmes.txt:5514-5516).
109
+
110
+ Holmes also inferred that a man entered through the window with it and later
111
+ left with the key (sherlock-holmes.txt:5464-5470, 5527-5528).
126
112
  ```
127
113
 
114
+ </details>
115
+
128
116
  #### 用户:直接检索
129
117
 
130
118
  不通过 Agent,直接搜索同一个书架:
@@ -133,52 +121,27 @@ Answer:
133
121
  zg query --human "An unseen creature left a few marks. What did the detective infer?" --limit 3
134
122
  ```
135
123
 
136
- zg 会从四本小说中对相关段落进行排序,并将 `sherlock-holmes.txt` 中的证据排在前面。
124
+ zg 会将 `sherlock-holmes.txt` 中的相关段落排在
125
+ `alice-in-wonderland.txt` 前面。
137
126
 
138
127
  <a id="benchmarks"></a>
139
128
 
140
129
  ## 📊 性能测试
141
130
 
142
- zg 是通用检索工具,目前的性能测试覆盖代码与文本检索。
143
-
144
- ### 代码仓库检索
145
-
146
- 我们使用 [SWE-QA-Bench](https://github.com/peng-weihan/SWE-QA-Bench),它要求
147
- Agent 在真实代码库中完成跨文件、多跳推理。
148
-
149
- - **覆盖范围:**20 个检索密集型任务,覆盖论文中的 **What、Where、How、Why**
150
- 四个一级类别、8 种意图和 11 个代码仓库。
151
- - **Agent:**使用 **Claude Opus 5** 的 Claude Code。
152
- - **Embedding:**zg 方案使用 **Qwen3.7 Text Embedding**。
153
- - **测试方式:**每个任务、每种方案运行 3 次。
154
-
155
- | 方案 | 评审得分 /100 ↑ | 输入 Token ↓ | 工具调用 ↓ | 耗时(秒)↓ | 成本 ↓ |
156
- | --- | ---: | ---: | ---: | ---: | ---: |
157
- | 原生工具 | 80.42 | 558,651 | 23.42 | 127.5 | $0.905 |
158
- | 原生工具 + zg | 81.92 | 294,262 | 9.70 | 79.7 | $0.558 |
159
- | 变化 | **+1.50 个百分点** | **−47.3%** | **−58.6%** | **−37.5%** | **−38.3%** |
160
-
161
- 在 20 个任务 × 3 次运行中,zg 将评审得分提升了 1.50 分,同时将输入 Token
162
- 减少 47.3%、工具调用减少 58.6%、执行时间减少 37.5%、成本减少 38.3%。
131
+ zg 通过**缩小有效搜索空间**,帮助 Agent 更快找到相关证据 —— 在保持回答质量的同时,使用**更少的 Token、工具调用和时间**。
163
132
 
164
- ### 大规模文本检索
133
+ 每项测试均采用**受控、可复现的配对 A/B 评测**:同一个 Agent 在完全相同的模型、Prompt、环境和资源限制下执行同一组预先固定的任务;**实验组仅额外提供 `zg` 工具及其使用说明**。
165
134
 
166
- 我们使用 [BrowseComp-Plus](https://github.com/texttron/BrowseComp-Plus),它要求
167
- Agent 从固定网页语料库中检索多文档证据。测试包含 30 个问题,覆盖 10 个映射主题
168
- 和三个证据广度级别,每种方案运行 3 次。
135
+ <img src="./.github/assets/benchmark-comparison-cn-v2.svg" alt="SWE-QA-Bench 和 BrowseComp-Plus 中 Baseline 与 zvec-grep 的配对对比" width="900" />
169
136
 
170
- | 方案 | 准确率 ↑ | 证据召回率 ↑ | 搜索调用 ↓ | 输入 Token ↓ | 耗时(秒)↓ | 成本 ↓ |
171
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
172
- | Agent + BM25 | TBD | TBD | TBD | TBD | TBD | TBD |
173
- | Agent + zg | TBD | TBD | TBD | TBD | TBD | TBD |
174
- | 变化 | TBD | TBD | TBD | TBD | TBD | TBD |
137
+ | 测试 | 回答质量&nbsp;↑ | 平均输入&nbsp;Token&nbsp;↓ | 平均工具调用&nbsp;↓ | 平均&nbsp;Agent&nbsp;耗时&nbsp;↓ |
138
+ | --- | ---: | ---: | ---: | ---: |
139
+ | [**SWE-QA-Bench**](./benchmarks/swe-qa-bench/README_CN.md)<br>20 个任务 · 代码库 QA | 评审得分<br>80.42&nbsp;→&nbsp;81.92 | 558,651&nbsp;→&nbsp;294,262 | 23.42&nbsp;→&nbsp;9.70 | 127.5s&nbsp;→&nbsp;79.7s |
140
+ | [**BrowseComp-Plus**](./benchmarks/browse-comp-plus/README_CN.md)<br>80 个样例 · 深度研究 QA | 准确率<br>90.00%&nbsp;→&nbsp;90.00% | 2.04M&nbsp;→&nbsp;1.19M | 22.70&nbsp;→&nbsp;14.24 | 284.8s&nbsp;→&nbsp;199.3s |
175
141
 
176
- 在 30 个问题 × 3 次运行中,zg 对准确率、证据召回率、搜索调用、输入 Token、
177
- 执行时间和成本的影响尚待测试结果补充。
142
+ 表中数值均按 **Baseline → zg** 展示。
178
143
 
179
- 如需复现测试,请按照[性能测试指南](./benchmarks/README.md)准备环境、固定版本、
180
- 任务清单、基线配置、运行命令和结果汇总。所有方案使用相同的 Agent、模型、Prompt、
181
- 工具预算和三次运行协议。
144
+ 完整结果和复现细节参见[性能测试文档](./benchmarks/README_CN.md)。
182
145
 
183
146
  ## 📚 文档
184
147
 
package/dist/cli/index.js CHANGED
File without changes
@@ -57,6 +57,7 @@ export declare class DaemonBackend implements ZvecGrepDaemonBackend {
57
57
  rg(input: ZvecGrepRgInput): Promise<ZvecGrepRgResult>;
58
58
  close(): Promise<void>;
59
59
  private runIndex;
60
+ private runIndexOperation;
60
61
  private submitIndex;
61
62
  private ensureWatcher;
62
63
  private settleKnownChanges;
@@ -203,40 +203,26 @@ export class DaemonBackend {
203
203
  const targetRevision = createsWork
204
204
  ? runtime.markDirty()
205
205
  : runtime.snapshot().dirtyRevision;
206
- runtime.setWriterPending(true);
207
- let submitted;
208
- try {
209
- submitted = this.scheduler.submit({
210
- canonicalRoot: runtime.canonicalRoot,
211
- reason: "manual",
212
- followupIfRunning: input.rebuild === true || followsNarrowJob,
213
- run: (report, signal) => runtime.withWrite(async () => {
214
- const proof = await this.runIndex(runtime, input, report, options.authorization, signal);
215
- this.lastScanDiagnostics.set(runtime.canonicalRoot, proof.scanDiagnostics);
216
- if (proof.reconciled) {
217
- runtime.markReconciled(targetRevision, proof.reconciliationEpoch);
218
- }
219
- else {
220
- runtime.markIndexed(targetRevision);
221
- }
222
- }),
223
- });
224
- }
225
- catch (error) {
226
- runtime.setWriterPending(false);
227
- throw error;
228
- }
229
- if (!submitted.reused) {
230
- void this.scheduler.waitForRootIdle(runtime.canonicalRoot).finally(() => {
231
- runtime.setWriterPending(false);
232
- });
233
- }
206
+ const submitted = this.scheduler.submit({
207
+ canonicalRoot: runtime.canonicalRoot,
208
+ reason: "manual",
209
+ followupIfRunning: input.rebuild === true || followsNarrowJob,
210
+ run: async (report, signal) => {
211
+ const proof = await this.runIndex(runtime, input, report, options.authorization, signal);
212
+ this.lastScanDiagnostics.set(runtime.canonicalRoot, proof.scanDiagnostics);
213
+ if (proof.reconciled) {
214
+ runtime.markReconciled(targetRevision, proof.reconciliationEpoch);
215
+ }
216
+ else {
217
+ runtime.markIndexed(targetRevision);
218
+ }
219
+ },
220
+ });
234
221
  const job = input.wait
235
222
  ? await this.scheduler.wait(submitted.job.id, options.onProgress)
236
223
  : submitted.job;
237
224
  if (input.wait) {
238
225
  await this.settleKnownChanges(runtime);
239
- runtime.setWriterPending(false);
240
226
  }
241
227
  this.options.logger?.event("job.submitted", {
242
228
  root_id: rootIdentity(runtime.canonicalRoot),
@@ -378,10 +364,6 @@ export class DaemonBackend {
378
364
  input.autoUpdate &&
379
365
  (runtime.requiresFullReconciliation() ||
380
366
  !this.scheduler.hasActiveRoot(runtime.canonicalRoot))) {
381
- if (!runtime.requiresFullReconciliation() ||
382
- runtime.canProbeFullReconciliation()) {
383
- await this.probeCurrentFreshness(runtime);
384
- }
385
367
  const currentJob = this.scheduler.getByRoot(runtime.canonicalRoot);
386
368
  const terminalKnownPathJob = !runtime.requiresFullReconciliation() &&
387
369
  (currentJob?.state === "failed" || currentJob?.state === "cancelled");
@@ -535,14 +517,25 @@ export class DaemonBackend {
535
517
  return this.closePromise;
536
518
  }
537
519
  async runIndex(runtime, input, report, authorization, signal) {
520
+ const releaseRuntimeActivity = runtime.beginActivity();
521
+ try {
522
+ return await this.runIndexOperation(runtime, input, report, authorization, signal);
523
+ }
524
+ finally {
525
+ releaseRuntimeActivity();
526
+ }
527
+ }
528
+ async runIndexOperation(runtime, input, report, authorization, signal) {
538
529
  const startedAt = Date.now();
539
- // Watcher path updates already identify their indexing scope, so avoid a
540
- // workspace-wide status scan here. Silent watcher misses are repaired by
541
- // the periodic full-reconciliation probes scheduled by WatchManager.
542
- const includeInitialStatus = input.rebuild !== true && input.changedPaths === undefined;
530
+ // Watcher path updates already identify their indexing scope. Uncertain
531
+ // watcher reconciliations are verified by the final status scan, so neither
532
+ // path needs another workspace-wide status scan before indexing starts.
533
+ const includeInitialStatus = input.skipInitialStatus !== true &&
534
+ input.rebuild !== true &&
535
+ input.changedPaths === undefined;
543
536
  const includeFinalStatus = input.changedPaths === undefined;
544
537
  const before = await this.inspectRoot(runtime.canonicalRoot, includeInitialStatus);
545
- if (input.changedPaths === undefined) {
538
+ if (includeInitialStatus) {
546
539
  this.statusCache.set(runtime.canonicalRoot, before);
547
540
  }
548
541
  const modelLoadRequest = this.indexModelLoadRequest(before, input);
@@ -573,7 +566,7 @@ export class DaemonBackend {
573
566
  device: input.runtimeOverridesAreEphemeral ? undefined : input.device,
574
567
  daemonInstanceToken: this.runtimeManager.instanceToken,
575
568
  });
576
- const result = await withRemoteEmbeddingOperationPermit(authorization, () => service.index({
569
+ const result = await runtime.withWrite(() => withRemoteEmbeddingOperationPermit(authorization, () => service.index({
577
570
  root: runtime.canonicalRoot,
578
571
  rebuild: input.rebuild,
579
572
  resetPaths: input.resetPaths,
@@ -592,7 +585,7 @@ export class DaemonBackend {
592
585
  signal,
593
586
  onProgress: report,
594
587
  onWriterContext: (context) => runtime.setWriterContext(context, lease.key),
595
- }));
588
+ })));
596
589
  scanDiagnostics = result.scanDiagnostics;
597
590
  }
598
591
  finally {
@@ -608,8 +601,10 @@ export class DaemonBackend {
608
601
  }
609
602
  const proofReconciliationEpoch = runtime.reconciliationEpoch();
610
603
  const after = await this.inspectRoot(runtime.canonicalRoot, includeFinalStatus);
611
- if (includeFinalStatus)
604
+ if (includeFinalStatus) {
612
605
  this.statusCache.set(runtime.canonicalRoot, after);
606
+ await this.watchers.get(runtime.canonicalRoot)?.refreshPaths?.();
607
+ }
613
608
  if (!after.workspaceIndex?.embedding) {
614
609
  throw new DaemonError("INDEX_MISSING", "Index completed without an embedding schema.");
615
610
  }
@@ -637,38 +632,29 @@ export class DaemonBackend {
637
632
  ? runtime.markDirty()
638
633
  : runtime.snapshot().dirtyRevision;
639
634
  const followsNarrowWatch = this.scheduler.getByRoot(runtime.canonicalRoot)?.reason === "watch";
640
- runtime.setWriterPending(true);
641
- let submitted;
642
- try {
643
- submitted = this.scheduler.submit({
644
- canonicalRoot: runtime.canonicalRoot,
645
- reason,
646
- followupIfRunning: followsNarrowWatch,
647
- run: (report, signal) => runtime.withWrite(async () => {
648
- const proof = await this.runIndex(runtime, input, report, authorization, signal);
649
- if (proof.reconciled) {
650
- runtime.markReconciled(targetRevision, proof.reconciliationEpoch);
651
- }
652
- else {
653
- runtime.markIndexed(targetRevision);
654
- }
655
- }),
656
- });
657
- }
658
- catch (error) {
659
- runtime.setWriterPending(false);
660
- throw error;
661
- }
662
- if (!submitted.reused) {
663
- void this.scheduler.waitForRootIdle(runtime.canonicalRoot).finally(() => {
664
- runtime.setWriterPending(false);
665
- });
666
- }
635
+ const submitted = this.scheduler.submit({
636
+ canonicalRoot: runtime.canonicalRoot,
637
+ reason,
638
+ followupIfRunning: followsNarrowWatch,
639
+ run: async (report, signal) => {
640
+ if (reason === "background_reconcile" &&
641
+ runtime.canProbeFullReconciliation() &&
642
+ (await this.probeCurrentFreshness(runtime)) === "fresh") {
643
+ return;
644
+ }
645
+ const proof = await this.runIndex(runtime, input, report, authorization, signal);
646
+ if (proof.reconciled) {
647
+ runtime.markReconciled(targetRevision, proof.reconciliationEpoch);
648
+ }
649
+ else {
650
+ runtime.markIndexed(targetRevision);
651
+ }
652
+ },
653
+ });
667
654
  if (!wait)
668
655
  return submitted.job;
669
656
  const job = await this.scheduler.wait(submitted.job.id);
670
657
  await this.scheduler.waitForRootIdle(runtime.canonicalRoot);
671
- runtime.setWriterPending(false);
672
658
  return job;
673
659
  }
674
660
  ensureWatcher(runtime) {
@@ -678,7 +664,6 @@ export class DaemonBackend {
678
664
  const coordinator = new IndexCoordinator({
679
665
  runtime,
680
666
  scheduler: this.scheduler,
681
- getIndexedFileCount: () => this.statusCache.get(runtime.canonicalRoot)?.status?.filesStored,
682
667
  run: async (changes, report, signal) => {
683
668
  const changedPaths = [
684
669
  ...changes.touchedFiles,
@@ -695,6 +680,7 @@ export class DaemonBackend {
695
680
  return await this.runIndex(runtime, {
696
681
  root: runtime.canonicalRoot,
697
682
  changedPaths: changes.forceFullReconcile ? undefined : changedPaths,
683
+ skipInitialStatus: changes.forceFullReconcile,
698
684
  }, report, automaticAuthorization.authorization, signal);
699
685
  },
700
686
  });
@@ -734,6 +720,7 @@ export class DaemonBackend {
734
720
  coordinator.enqueue(changes, reason);
735
721
  },
736
722
  onPendingChange: (pending) => runtime.setWatcherPending(pending),
723
+ getRootPaths: () => this.statusCache.get(runtime.canonicalRoot)?.workspaceIndex?.rootPaths,
737
724
  });
738
725
  watcher.start();
739
726
  this.indexCoordinators.set(runtime.canonicalRoot, coordinator);
@@ -821,15 +808,21 @@ export class DaemonBackend {
821
808
  }
822
809
  }
823
810
  async probeCurrentFreshness(runtime) {
824
- const freshness = await runtime.probeFreshness(async () => {
825
- const info = await inspectRoot(runtime.canonicalRoot, this.options.serviceOptions);
826
- this.statusCache.set(runtime.canonicalRoot, info);
827
- return indexStatusIsFresh(info);
828
- });
829
- this.options.logger?.event(`runtime.recovery_probe_${freshness}`, {
830
- root_id: rootIdentity(runtime.canonicalRoot),
831
- });
832
- return freshness;
811
+ const releaseRuntimeActivity = runtime.beginActivity();
812
+ try {
813
+ const freshness = await runtime.probeFreshness(async () => {
814
+ const info = await this.inspectRoot(runtime.canonicalRoot, true);
815
+ this.statusCache.set(runtime.canonicalRoot, info);
816
+ return indexStatusIsFresh(info);
817
+ });
818
+ this.options.logger?.event(`runtime.recovery_probe_${freshness}`, {
819
+ root_id: rootIdentity(runtime.canonicalRoot),
820
+ });
821
+ return freshness;
822
+ }
823
+ finally {
824
+ releaseRuntimeActivity();
825
+ }
833
826
  }
834
827
  async closeWatcher(canonicalRoot) {
835
828
  const watcher = this.watchers.get(canonicalRoot);