dsh-locator 0.7.6 → 0.8.0

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.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # 🔎 dsh-locator
4
4
 
5
- **给 DeepSeek Harness 的全盘文件搜索:先按名字找,再按内容找,找到就能打开。**
5
+ **记得名字就搜名字,记得内容就搜内容;找到之后直接打开,或者跳回那次对话。**
6
6
 
7
- 索引由插件自己维护,不装 Everything,也不依赖 `es.exe`。Windows、macOS、Linux 用同一套工具和同一个面板。
7
+ 给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的全盘文件搜索:自带索引,不装 Everything,不依赖 `es.exe`,Windows / macOS / Linux 用同一套工具和同一个面板。
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/dsh-locator)](https://www.npmjs.com/package/dsh-locator)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/dsh-locator)](https://www.npmjs.com/package/dsh-locator)
@@ -12,7 +12,8 @@
12
12
  [![version](https://img.shields.io/github/v/tag/PerryLink/dsh-locator?label=version)](https://github.com/PerryLink/dsh-locator/releases)
13
13
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
14
14
  [![DSH plugin](https://img.shields.io/badge/dsh--plugin-✅-green)](https://github.com/topics/dsh-plugin)
15
- [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
15
+ [![Awesome DSH](https://img.shields.io/badge/awesome--dsh--plugin-listed-2ea44f)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
16
+ [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#版本与兼容性)
16
17
  [![Gitee](https://img.shields.io/badge/Gitee-mirror-c71d23?logo=gitee)](https://gitee.com/perrylink/dsh-locator)
17
18
  [![GitCode](https://img.shields.io/badge/GitCode-mirror-1f6feb)](https://gitcode.com/PerryLink/dsh-locator)
18
19
 
@@ -24,21 +25,61 @@ dsh plugin --profile web add dsh-locator
24
25
 
25
26
  </div>
26
27
 
27
- > 官方仓库是 [GitHub `PerryLink/dsh-locator`](https://github.com/PerryLink/dsh-locator),Gitee 和 GitCode 是自动同步的镜像。这个插件属于 [DSH 插件家族](https://github.com/PerryLink),Apache-2.0;如果它帮你省了时间,点个 ⭐ 就行。
28
+ > 官方仓库是 [GitHub `PerryLink/dsh-locator`](https://github.com/PerryLink/dsh-locator),Gitee 和 GitCode 是自动同步的镜像,GitHub 为准。插件属于 [DSH 插件家族](https://github.com/PerryLink),Apache-2.0;已被 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) 收录。如果它帮你省了时间,点个 ⭐ 就行。
28
29
 
29
- ## 它解决什么问题
30
+ ## 目录
30
31
 
31
- 找文件通常分两种情况。
32
+ - [30 秒了解](#30-秒了解)
33
+ - [和 Everything / Spotlight / grep 有什么不同](#和-everything--spotlight--grep-有什么不同)
34
+ - [安装](#安装)
35
+ - [第一次运行](#第一次运行)
36
+ - [两种搜索](#两种搜索)
37
+ - [Web 面板](#web-面板)
38
+ - [工具一览](#工具一览)
39
+ - [查询语法](#查询语法)
40
+ - [配置](#配置)
41
+ - [性能与资源](#性能与资源)
42
+ - [平台支持](#平台支持)
43
+ - [故障排查](#故障排查)
44
+ - [从源码构建](#从源码构建)
45
+ - [设计](#设计)
46
+ - [限制](#限制)
47
+ - [安全与隐私](#安全与隐私)
48
+ - [版本与兼容性](#版本与兼容性)
49
+ - [路线图](#路线图)
50
+ - [许可证](#许可证)
32
51
 
33
- 一种是你记得名字,但不记得放在哪。`glob` 和 `grep` 每次都要重新遍历工作区,知道目录时很快,只记得半个文件名时就很慢。Everything 在 Windows 上很好用,可惜只支持 Windows;把它包一层,macOS、Linux、WSL 和远程机器仍然用不了。
52
+ ## 30 秒了解
34
53
 
35
- 另一种是你记得某句话、某个结论、某段代码,却想不起在哪个文件、哪次对话里。这时只搜文件名没有用。
54
+ 找文件通常分两种情况:
36
55
 
37
- `dsh-locator` 把这两件事放在一起:
56
+ - **记得名字,不记得位置。** `glob`/`grep` 每次都要重新遍历;文件在 `~/Downloads` 还好,只记得半个文件名时就很难受。
57
+ - **记得某句话、某个结论、某段代码,想不起在哪个文件、哪次对话里。** 这时只搜文件名没有用,而 DSH 的会话日志本身也是一等公民。
38
58
 
39
- - 一份常驻的文件名/路径索引,百万条记录也能在几十毫秒内返回;
40
- - 一份内容索引,覆盖文本文件和 DSH 会话消息,支持中文和子串;
41
- - 找到之后可以直接打开文件、定位到文件夹,或者跳到对应的会话。
59
+ `dsh-locator` 把两件事放进同一个插件:
60
+
61
+ | 能力 | 怎么用 |
62
+ |---|---|
63
+ | 文件名 / 路径搜索 | `locator_search`,或面板默认模式 |
64
+ | 文件正文搜索 | `locator_find`,或面板勾选“搜内容” |
65
+ | DSH 会话内容搜索 | 同上;命中带 `sessionId` / `messageId`,可直接跳回那次对话 |
66
+ | 打开 / 定位 | 工具返回路径;面板双击在文件管理器定位,`Ctrl+O` 打开,`Ctrl+Enter` 把 `@"路径"` 插进输入框 |
67
+ | 外部工具消费 | `locator_export` 导出只读 SQLite |
68
+
69
+ 索引完全在本地:不联网、不上传、不埋点;插件不会修改、移动或删除被索引的文件。
70
+
71
+ ## 和 Everything / Spotlight / grep 有什么不同
72
+
73
+ | | dsh-locator | Everything | Spotlight | `glob` / `grep` |
74
+ |---|---|---|---|---|
75
+ | 平台 | Windows / macOS / Linux / WSL | Windows | macOS | 跨平台 |
76
+ | 索引形态 | 常驻索引(自建) | 常驻索引(NTFS USN) | 系统元数据/内容索引 | 每次调用重新遍历 |
77
+ | 按正文搜索 | 文本文件 + DSH 会话消息 | 默认只搜文件名 | 支持,可用性因系统而异 | 支持,但无索引 |
78
+ | 会话跳转 | 命中带 `sessionId` / `messageId`,可跳回 | 无 | 无 | 无 |
79
+ | 给 agent 用 | 6 个工具,查询语法一致 | 需另装 CLI/服务 | 需另接脚本 | 原生工具,无持久索引 |
80
+ | Windows 速度 | 接近 Everything(USN 可选) | 最快最轻 | 不适用 | 慢于索引方案 |
81
+
82
+ 一句话:Everything 的**思路**(常驻索引 + 结构化查询)被独立实现成了跨平台版本,并补上了 DSH 会话这一层;不是 Everything 的封装。
42
83
 
43
84
  ## 安装
44
85
 
@@ -46,27 +87,32 @@ dsh plugin --profile web add dsh-locator
46
87
  # npm(推荐)
47
88
  dsh plugin --profile web add dsh-locator
48
89
 
49
- # 直接跟 GitHub 主仓
90
+ # 指定版本
91
+ dsh plugin --profile web add dsh-locator@0.7.6
92
+
93
+ # GitHub 主仓
50
94
  dsh plugin --profile web add github:PerryLink/dsh-locator
51
95
 
52
96
  # 从源码
53
97
  pnpm install
54
98
  pnpm build
55
- pnpm build:engine
99
+ pnpm build:engine # 当前平台的 Go 引擎
56
100
  dsh plugin --profile web add /绝对路径/dsh-locator
57
101
 
58
- # 从 tarball
102
+ # 从 tarball(npm pack 产物;替换成实际版本号)
59
103
  npm pack
60
- dsh plugin --profile web add /绝对路径/dsh-locator-0.7.0.tgz
104
+ dsh plugin --profile web add /绝对路径/dsh-locator-0.7.6.tgz
61
105
  ```
62
106
 
63
- npm 包里已经带了六个平台的预编译引擎:`linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwin-x64`、`darwin-arm64`。
64
-
65
- 如果装进一个**正在运行**的 Web profile,装完刷新一下页面(F5)。宿主工具可能会热挂载,但浏览器面板是页面加载时从 boot graph 里取的。headless/TUI profile 不会显示面板。
107
+ - 包里已经带了六个平台的预编译引擎:`linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwin-x64`、`darwin-arm64`,装完不需要本机 Go。
108
+ - 国内 npm 镜像偶尔滞后:如果 `latest` 不是新版本,加 `--registry=https://registry.npmjs.org` 再装。
109
+ - 装进**正在运行**的 Web profile 后,刷新一下页面(F5):宿主工具通常会热挂载,但浏览器面板是页面加载时从 boot graph 里取的。headless / TUI profile 只有工具,没有面板。
110
+ - 升级:`dsh plugin --profile web add dsh-locator@latest`,然后 F5;内容库结构变化时只重建一次。
111
+ - 卸载:`dsh plugin --profile web remove dsh-locator`;想临时关掉但保留配置,设 `enabled: false`。
66
112
 
67
113
  ## 第一次运行
68
114
 
69
- 默认索引你的主目录,快照放在系统缓存目录:
115
+ 默认索引你的主目录,快照和内容库放在系统缓存目录:
70
116
 
71
117
  | 平台 | 默认位置 |
72
118
  |---|---|
@@ -74,30 +120,44 @@ npm 包里已经带了六个平台的预编译引擎:`linux-x64`、`linux-arm6
74
120
  | macOS | `~/Library/Application Support/dsh-locator` |
75
121
  | Linux | `$XDG_CACHE_HOME/dsh-locator` 或 `~/.cache/dsh-locator` |
76
122
 
77
- 第一次扫描要读盘,之后启动直接加载快照。扫描期间搜索照常可用:返回已经索引的部分,并带 `indexing: true`,面板底栏也会提示“索引构建中”。
123
+ 第一次扫描要读盘;之后启动直接加载快照。扫描期间搜索照常可用:返回已索引的部分,并带 `indexing: true`,面板底栏也会提示“索引构建中”。
124
+
125
+ 默认跳过 `node_modules`、`.git`、`.hg`、`.svn`、`.cache`、`__pycache__`、`.venv`、`venv`、`.next`(按名称匹配,`excludes` 可改)。
78
126
 
79
- 默认跳过 `node_modules`、`.git`、`.svn`、`.hg`、`.cache`、`__pycache__`、`.venv`、`venv`、`.next`。
127
+ 可以这样试:
128
+
129
+ ```text
130
+ 帮我找上周改过的 PDF
131
+ 哪里讨论过 “contentRefreshMs”?
132
+ 看看哪个会话里定过这个方案
133
+ ```
80
134
 
81
135
  ## 两种搜索
82
136
 
83
137
  ### 按名字找:`locator_search`
84
138
 
85
- Everything 风格的查询语法,返回路径、大小、修改时间、扩展名、类型。适合“我记得文件名里有 report”“找上周改过的 PDF”这类问题。
139
+ Everything 风格的查询语法,返回路径、大小、修改时间、扩展名、类型。适合“我记得文件名里有 report”“找上周改过的 PDF”“找 50MB 以上的视频”这类问题。
140
+
141
+ 查询语法见[下方](#查询语法)。排序、分页、`limit`/`offset` 都由宿主引擎完成,151 万条记录的常见查询在几十毫秒内。
86
142
 
87
143
  ### 按内容找:`locator_find`
88
144
 
89
- 在文本文件和 DSH 会话消息里搜正文,返回命中片段,以及来源信息。
145
+ 在文本文件和 DSH 会话消息里搜正文,返回命中片段和来源:
90
146
 
91
- - 文件命中带路径,直接打开或定位;
92
- - 会话命中带 `sessionId` 和 `messageId`,可以跳回那次对话;
93
- - 中文和英文子串都能搜,不要求分词。
94
- - 内容索引默认**惰性启动**:插件启动时什么都不建,第一次搜内容(`locator_find` 或面板“搜内容”)才开始在后台构建,`contentLazy: false` 可恢复启动即建。重启后复用已有的内容库、先做一次增量刷新;只有库不存在、结构版本不兼容或损坏时才全量重建(先会话、后文件正文)。之后每 5 分钟再增量刷新一次,只重读 mtime 变化的文件/会话;也可以手动触发全量重建。
147
+ - 文件命中带路径,可以直接打开或定位;
148
+ - 会话命中带 `sessionId` 和 `messageId`,可以跳回那次对话并尽量滚动到命中的那句话;
149
+ - 中文和英文子串都能搜(trigram 分词),不要求分词正确。
95
150
 
96
- 内容索引在后台构建,刚启动时可能只覆盖了一部分,工具和面板都会显示构建状态。默认只索引 4MB 以内的文本文件,最多 20 万个文件、最近 200 个会话,内容总量上限 2GB;这些都能在配置里改。
151
+ 内容索引的行为:
97
152
 
98
- 依赖和工具链目录(`node_modules`、`.cargo`、`.bun`、`.pnpm`、`site-packages`、`toolchain` 等)默认不读正文,否则内容预算会先被库代码吃满、你自己的文档反而搜不到;要调整就改 `contentExcludes`。
153
+ - **惰性启动**:插件启动时什么都不建;第一次搜内容(`locator_find` 或面板“搜内容”)才开始后台构建,`contentLazy: false` 可恢复启动即建。第一次查询会立刻返回 `building: true`,不会让你干等或误以为没结果。
154
+ - **重启复用**:有内容库时只做一次增量刷新,不重读全部正文;只有库不存在、结构版本不兼容、上次构建没完成或损坏时才全量重建(先会话、后文件正文)。
155
+ - **增量刷新**:默认每 5 分钟检查一次,只重读 mtime 变化的文件/会话;`contentRefreshMs` 可调,0 关闭。
156
+ - **归档与活动会话**:已归档会话默认不索引(`contentIncludeArchived: true` 才索引);正在写入的会话等它安静 `contentActiveSessionDelayMs`(默认 2 分钟)后再索引,不会每轮刷新重解压整份日志;已删除的会话随文件消失自动退出索引。
157
+ - **预算与截断**:默认单文件最多 1MB、文件数最多 20 万、内容总量最多 1GB、最近 100 个会话。撞到总额上限时按扫描顺序保留先到的文件,`locator_find` 的输出会提示 `content budget reached`,HTTP 状态里是 `truncated: true`;调大 `contentMaxTotalBytes` 或收窄 `contentExcludes` 即可。
158
+ - **依赖目录不读正文**:`node_modules`、`.cargo`、`.bun`、`.pnpm`、`site-packages`、`toolchain` 等默认被 `contentExcludes` 跳过,否则预算会先被库代码吃满;文件名索引不受影响。
99
159
 
100
- 内容索引全程在宿主进程里跑,但读文件和 zstd 解压都是异步的,并且每约 8ms 就让出事件循环——实测 28.6MB 语料一次构建的最大事件循环阻塞从 968ms 降到 19ms,所以不会拖慢会话加载/切换。内容查询本身跑在 worker 线程里(只读连接),输入搜索时也不会卡住宿主。已删除的会话会随文件消失自动退出索引;已归档的会话默认不索引(`contentIncludeArchived: true` 才索引);正在写入的会话会等它安静 `contentActiveSessionDelayMs` 后再索引,不会每轮刷新把整份日志重新解压一遍。
160
+ 内容索引跑在宿主进程里,但文件读取和 zstd 解压都是异步的,构建循环每约 8ms 让出一次事件循环(28.6MB 语料实测最大事件循环阻塞从 968ms 降到约 20ms),所以不会拖慢会话加载/切换。内容**查询**在 worker 线程里用只读连接执行,常见词约 100ms 量级、生僻词几毫秒,不阻塞宿主。
101
161
 
102
162
  ## Web 面板
103
163
 
@@ -106,26 +166,30 @@ Everything 风格的查询语法,返回路径、大小、修改时间、扩展
106
166
  在 Web GUI 里按 `Ctrl+Shift+F`,或者点侧栏底部的放大镜。
107
167
 
108
168
  - 空查询按修改时间列出最近的条目;输入后 110ms 防抖,和工具用同一套语法。
109
- - 点列头可以按名称、大小、修改时间、路径排序。
110
- - 勾选“只看文件”“只看目录”过滤类型;勾选“搜内容”切换到正文/会话搜索。
111
- - `↑`/`↓` 选择,`Enter` 复制路径,`Ctrl+O` 打开,`Ctrl+Enter` 把 `@"路径"` 插进输入框。
112
- - 双击文件结果会在文件管理器里定位;双击会话结果会打开对应会话,并尝试滚动到命中的那句话。
169
+ - 点列头按名称、大小、修改时间、路径排序;可勾选“只看文件”“只看目录”。
170
+ - 勾选“搜内容”切换到正文/会话搜索;构建期间底栏显示状态并自动重查。
171
+ - `↑`/`↓` 选择,`Enter` 复制路径,`Ctrl+O` 打开,`Ctrl+Enter` 把 `@"路径"` 插进输入框,`Esc` 关闭。
172
+ - 双击文件结果在文件管理器里定位;双击会话结果打开对应会话并尝试滚动到命中处。
113
173
  - 面板文案跟随 `navigator.language`(中/英/西/葡/印地语)。
114
174
 
115
- 面板通过几个同源路由工作:`/dsh-locator/search`、`/dsh-locator/content`、`/dsh-locator/status`、`/dsh-locator/reveal`、`/dsh-locator/open`。不另起服务,也不占额外端口。
175
+ 面板通过同源路由工作:`/dsh-locator/search`、`/dsh-locator/content`、`/dsh-locator/content/rebuild`、`/dsh-locator/combined`、`/dsh-locator/status`、`/dsh-locator/reveal`、`/dsh-locator/open`。不另起服务,也不占额外端口。
176
+
177
+ - **默认同时搜索三个范围**:内容(正文 + 会话)、文件、目录,三个勾选默认全部打开;取消任一勾选即可缩小范围。结果按“内容 → 文件 → 目录”分组,组头显示数量;内容按相关度排序,文件/目录按排序菜单。
178
+ - **双击或 `Enter` 激活**:会话命中会打开对应会话,并把命中的词用半透明琥珀色高亮(随滚动跟随、自动居中、`Esc` 清除);文件/目录在文件管理器中定位。`Ctrl+Shift+C` 复制路径,`Ctrl+O` 打开,`Ctrl+Enter` 插入 `@file`,`Alt+1–3` 切换范围。
179
+ - 内容查询至少需要 2 个字符;空查询和 1 个字符的查询不会启动内容索引构建。
116
180
 
117
181
  ## 工具一览
118
182
 
119
183
  | 工具 | 作用 |
120
184
  |---|---|
121
- | `locator_search` | 按文件名/路径搜索,支持 `ext:`、`size:`、`dm:`、`path:` 等过滤。 |
122
- | `locator_find` | 按文件正文和会话内容搜索,返回片段与 `sessionId`/`messageId`。 |
123
- | `locator_status` | 查看根目录、条目数、监听状态、内容索引进度和引擎版本。 |
124
- | `locator_rebuild` | 后台重建文件名索引;新索引就绪前,搜索继续使用旧的。 |
185
+ | `locator_search` | 按文件名/路径搜索,支持 `ext:`、`size:`、`dm:`、`path:` 等过滤与排序分页。 |
186
+ | `locator_find` | 按文件正文和会话内容搜索,返回片段与 `sessionId`/`messageId`,并带内容索引状态。 |
187
+ | `locator_status` | 查看根目录、名称索引条目数、监听状态、状态目录和引擎版本(内容索引进度由 `locator_find` / 面板报告)。 |
188
+ | `locator_rebuild` | 后台重建名称索引;新索引就绪前搜索继续使用旧的。 |
125
189
  | `locator_reindex` | 重建/刷新内容索引:默认增量,`full: true` 全量。 |
126
- | `locator_export` | 把文件名索引导出成只读 SQLite,方便外部工具查询。 |
190
+ | `locator_export` | 把名称索引导出成只读 SQLite,方便外部工具查询。 |
127
191
 
128
- ### 查询语法
192
+ ## 查询语法
129
193
 
130
194
  ```text
131
195
  report 文件名里同时包含这些词
@@ -143,27 +207,73 @@ case:1 wholeword:1 区分大小写 / 全词匹配
143
207
 
144
208
  ## 配置
145
209
 
210
+ 在 profile 的 `cordis.patch.yml` 里覆盖,例如:
211
+
212
+ ```yaml
213
+ - id: dsh-locator
214
+ name: dsh-locator
215
+ config:
216
+ roots: ['/home/me/Documents', '/home/me/notes']
217
+ contentMaxTotalBytes: 2000000000
218
+ contentExcludes: ['node_modules', '.git', '.cargo', 'site-packages']
219
+ ```
220
+
146
221
  | 配置项 | 默认值 | 说明 |
147
222
  |---|---|---|
148
- | `roots` | 主目录 | 要索引的目录。 |
149
- | `excludes` | `node_modules` 等 | 按目录名或路径跳过的 glob。 |
223
+ | `enabled` | `true` | 总开关;`false` 时不挂工具、不启动引擎。 |
224
+ | `roots` | 主目录 | 要索引的目录,可多选。 |
225
+ | `excludes` | `node_modules` 等 | 名称索引跳过的目录名/路径 glob。 |
150
226
  | `watch` | `true` | 是否监听文件系统变化。 |
151
- | `refreshIntervalMs` | 30 分钟 | 没有原生监听时的重建周期。 |
227
+ | `refreshIntervalMs` | 1800000 | 没有原生监听时的重建周期(kqueue 回退或监听失败时用到)。 |
152
228
  | `scanWorkers` | 0 | 扫描并发;0 表示按 CPU 数。 |
153
229
  | `windowsUsn` | `false` | Windows 上尝试 USN/MFT 快路径,需要管理员权限。 |
154
- | `memoryLimitMB` | 0 | 引擎的 Go 软堆上限;0 表示交给 `GOMEMLIMIT`/`GOGC`。 |
155
- | `contentIndex` | `true` | 是否构建文件正文/会话内容索引。 |
156
- | `contentLazy` | `true` | 惰性构建:第一次搜内容时才建库;`false` 表示插件启动后就在后台建。 |
230
+ | `memoryLimitMB` | 0 | 引擎的 Go 软堆上限;0 交给 `GOMEMLIMIT`/`GOGC`。 |
231
+ | `contentIndex` | `true` | 是否构建正文/会话内容索引;`false` 时只保留名称搜索,开销接近 0。 |
232
+ | `contentLazy` | `true` | 惰性构建:第一次搜内容时才建库;`false` 表示启动后就在后台建。 |
157
233
  | `contentMaxFileBytes` | 1000000 | 单个文本文件最多读多少字节。 |
158
234
  | `contentMaxTotalBytes` | 1000000000 | 内容库总量上限;0 表示不限制。 |
159
235
  | `contentMaxFiles` | 200000 | 最多索引多少个文件;0 表示不限制。 |
160
236
  | `contentMaxSessions` | 100 | 最多索引最近多少个会话;0 表示全部。 |
161
237
  | `contentRefreshMs` | 300000 | 内容增量刷新周期;0 关闭自动刷新。 |
162
- | `contentIncludeArchived` | `false` | 是否索引已归档的会话;默认不索引。 |
163
- | `contentActiveSessionDelayMs` | 120000 | 会话日志安静多久后才索引;正在写的会话不重复解析。 |
238
+ | `contentIncludeArchived` | `false` | 是否索引已归档的会话。 |
239
+ | `contentActiveSessionDelayMs` | 120000 | 会话日志安静多久后才索引。 |
164
240
  | `contentSessionDirs` | `$DSH_HOME/sessions` | 额外的会话目录。 |
165
- | `contentExcludes` | 依赖/工具链目录 | 内容索引跳过的路径片段或子串;文件名索引不受影响。 |
166
- | `stateDir` | 系统缓存 | 快照和内容数据库的位置。 |
241
+ | `contentExcludes` | 依赖/工具链目录 | 内容索引跳过的路径片段/子串;名称索引不受影响。 |
242
+ | `stateDir` | 系统缓存 | 快照和内容库的位置;多个 DSH 进程请用不同目录。 |
243
+
244
+ > `binaryPath`、`maxResults`、`startupTimeoutMs`、`requestTimeoutMs` 也支持,分别用于自定义引擎、单页上限和超时;一般不需要改。
245
+
246
+ ## 性能与资源
247
+
248
+ 以下数字来自开发机(WSL2,Linux,热页缓存)和真实主目录;你的机器会因磁盘、文件数、缓存和 GC 状态而不同。
249
+
250
+ **名称索引**
251
+
252
+ | 项目 | 实测 |
253
+ |---|---|
254
+ | 冷启动扫描 | 约 151 万条目 / 11–12 秒 |
255
+ | 快照体积 | 约 171 MB(151 万条目) |
256
+ | 热查询(`locator_search`) | 常见查询 6–25ms;`path:` 前缀扫描约 70ms |
257
+ | 查询时索引重建 | 旧索引继续服务,新索引就绪后切换 |
258
+ | 扫描峰值内存 | 数百 MB 量级;引擎独立子进程,可用 `memoryLimitMB` / `GOMEMLIMIT` 限制 |
259
+
260
+ **内容索引**(真实主目录,1GB 预算上限)
261
+
262
+ | 项目 | 实测(0.7.x,真实主目录) |
263
+ |---|---|
264
+ | 覆盖 | 约 9.6 万个文本文件 / 95 个会话 / 1,050 条消息 |
265
+ | 全量重建 | 约 3.5 分钟(后台、不阻塞宿主;期间宿主 p95 14ms / max 115ms) |
266
+ | 无变化增量刷新 | 约 12 秒(主要花在引擎 151 万条目的 dump 上) |
267
+ | 空闲宿主延迟 | p50 2ms / p95 3ms / max 6ms |
268
+ | 内容库体积 | 约 3.1 GB(约为正文预算的 3 倍) |
269
+ | 内容查询 | 常见词约 100ms、生僻词几毫秒(worker 线程,不阻塞宿主) |
270
+
271
+ **怎么更省**
272
+
273
+ - 只要 Everything 式文件名搜索:`contentIndex: false`,内容开销归零。
274
+ - 想保留内容但更省磁盘:调小 `contentMaxTotalBytes`(如 256000000)、`contentMaxFileBytes`,或把 `contentRefreshMs` 调大。
275
+ - 想覆盖更多文件:调大 `contentMaxTotalBytes` / `contentMaxFiles`,并确认 `contentExcludes` 没把真实文档目录排除掉。
276
+ - 注意:内容库是缓存,删掉 `stateDir` 后会自动重建,不会影响源文件。
167
277
 
168
278
  ## 平台支持
169
279
 
@@ -171,39 +281,40 @@ case:1 wholeword:1 区分大小写 / 全词匹配
171
281
  |---|---|---|
172
282
  | Linux | 并行遍历 | inotify,递归监听 |
173
283
  | Windows | 并行遍历,或管理员下的 USN/MFT | `ReadDirectoryChangesW`;USN 模式下重启会补日志 |
174
- | macOS | 并行遍历 | 发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建回退到 kqueue |
284
+ | macOS | 并行遍历 | 发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建回退到 kqueue(目录上限 4096) |
175
285
 
176
- Windows 的 USN 快路径默认关闭(`windowsUsn: true`)。它需要管理员权限和启用中的卷变更日志;任何一步失败都会回退到普通遍历,不会让搜索不可用。USN 扫描成功后会记下日志游标,重启时先补上停机期间的变更,再开始服务。
286
+ ### Windows 与 USN 日志
177
287
 
178
- ## 性能
288
+ USN/MFT 快路径默认关闭(`windowsUsn: true`):需要管理员权限和启用中的卷变更日志,任何一步失败都会回退到普通遍历,不会让搜索不可用。成功扫描后会记下日志游标(`usn-state.json`),重启时先补上停机期间的变更。
179
289
 
180
- - 约 124 万条记录:热查询 25–35ms;扫描进行中查询约 2ms。
181
- - Windows CI 上 `C:\Windows` 16.4 万条用 USN 索引约 7.8 秒,查询 9.6ms。
182
- - 导出 124 万行 SQLite:约 477MB,6.4 秒。
183
- - 内容索引是后台任务,首次构建时间取决于文本文件数量和会话日志大小。
290
+ 它在 Windows CI runner 上做过端到端验证:读取 134 万条 MFT 记录、解析 134 万条路径;`C:\Windows` 下 16.4 万条目用 USN 索引约 7.8 秒、查询 9.6ms。也可以手动探测:
184
291
 
185
- 内容索引实测(同一台 WSL2 开发机,热缓存):
292
+ ```powershell
293
+ # 管理员 PowerShell
294
+ & ".\bin\win32-x64\indexd.exe" usn-probe --volume C: --root "C:\Windows" --limit 5 --journal 5
295
+ ```
186
296
 
187
- | 语料 | 全量构建 | `content.db` | 增量刷新(无变化) | 查询 p50 / p95 |
188
- |---|---|---|---|---|
189
- | 2 万文件 / 40MB 正文 | 15.1 s | 71 MB | 0.17 s | 5 / 17 ms |
190
- | 20 万文件 / 331MB 正文 | 31.5 s | 711 MB | 1.3 s | 5 / 156 ms |
191
- | 真实主目录(17.7 万文档 / 2GB 上限) | 7.4 min | 5.99 GB | 2.9 s | — |
297
+ USN 记录本身不带大小和修改时间:结果的名字/路径是准确的,`locator_status` 会标记这种索引为 `USN`,返回结果页时再 stat 补齐大小和日期;`size:`/`dm:` 过滤在整库上仍是近似值,直到跑一次元数据补全。日志被覆盖、删除或卷不支持时,自动回退可移植遍历。
192
298
 
193
- - 内容库体积大约是正文的 2–3 倍,磁盘要留余量;默认跳过依赖/工具链目录(`contentExcludes`),否则 2GB 预算会被库代码先吃光。
194
- - 重启会复用已有的内容库做增量刷新(20 万文件语料实测 63.4 s → 4.6 s);只有升级后首启、库损坏或结构版本不匹配时才全量重建。
195
- - 撞到 `contentMaxTotalBytes` 时工具会明确提示 “content budget reached”,可以调大上限或收窄 `contentExcludes`。
299
+ ## 故障排查
196
300
 
197
- ## 设计
301
+ **装完面板没出现?**
302
+ 刷新页面(F5);确认装的是 `web` profile,且面板从 boot graph 加载。headless/TUI profile 本来就没有面板。
198
303
 
199
- ![架构:文件系统 → 扫描/监听 → 紧凑快照 → 查询引擎 → agent 工具 / Web 面板](docs/assets/architecture.svg)
304
+ **刚创建的文件搜不到?**
305
+ 监听可能还没收到通知(WSL 写入 `/mnt/c`、网络盘、部分容器的 overlay 常见)。等几秒,或对相应根目录执行 `locator_rebuild`。USN 模式重启后会补日志。
200
306
 
201
- - **文件名索引**:Go 引擎维护一份紧凑快照。记录只存名字在 arena 里的偏移和父记录 id,完整路径在返回结果时重建。
202
- - **并行扫描**:目录读取协程消费有界队列,每个目录批量写入,保证父目录先于子目录。
203
- - **查询**:解析一次查询,分片并行匹配,用有界堆选出前 `offset+limit` 条,不排序全部命中。
204
- - **内容索引**:宿主侧 SQLite FTS5,trigram 分词,支持中文和子串;文件清单来自引擎已有的扫描结果,会话日志用 Node 自带的 zstd 解压。
205
- - **跳转**:文件走 `/reveal` 和 `/open`;会话通过 `uiWorkspace.openSession(sessionId)` 打开,再在页面里定位命中文字。
206
- - **协议**:引擎通过 stdio 上的 NDJSON 通信,不监听端口;Web 面板只走同源宿主路由。
307
+ **搜内容没结果?**
308
+ 先看 `locator_find` 返回里的 `building`:第一次搜索会触发惰性构建,构建期间结果可能不全,面板会自动重查。再确认文件扩展名在文本类型内、大小没超过 `contentMaxFileBytes`、没被 `contentExcludes` 排除、会话没归档(归档默认不索引)。
309
+
310
+ **内容库很大 / 磁盘涨得快?**
311
+ 见[性能与资源](#性能与资源):调小预算或关闭 `contentIndex`;删除 `stateDir` 会触发重建。
312
+
313
+ **`inotify` 相关警告?**
314
+ 目录太多时会碰到内核 watch 上限;索引仍然可用,只是变化依赖定时重建。可调大 `fs.inotify.max_user_watches`,或减少 `roots`。
315
+
316
+ **升级后行为变了?**
317
+ `dsh plugin --profile web add dsh-locator@latest` 后 F5;内容库结构变化时只重建一次。更新记录见 [CHANGELOG](CHANGELOG.md)。
207
318
 
208
319
  ## 从源码构建
209
320
 
@@ -212,28 +323,61 @@ Windows 的 USN 快路径默认关闭(`windowsUsn: true`)。它需要管理
212
323
  ```sh
213
324
  pnpm install
214
325
  pnpm build
215
- pnpm build:engine:all
216
- pnpm check
326
+ pnpm build:engine:all # 六个平台的引擎
327
+ pnpm check # typecheck + 测试 + 打包/安装校验
217
328
  ```
218
329
 
219
- Go 引擎在 `engine/` 下,测试用 `cd engine && go test ./...`。macOS 的 FSEvents 版本需要 cgo,由 macOS CI 构建。
330
+ Go 引擎在 `engine/` 下,测试用 `cd engine && go test ./...`。macOS 的 FSEvents 版本需要 cgo,由 macOS CI 构建。Node 测试在 Linux、Windows、macOS 三个平台的 CI 上运行。
331
+
332
+ ## 设计
333
+
334
+ ![架构:文件系统 → 扫描/监听 → 紧凑快照 → 查询引擎 → agent 工具 / Web 面板](docs/assets/architecture.svg)
335
+
336
+ - **名称索引**:Go 引擎维护紧凑快照;记录只存名字在 arena 里的偏移和父记录 id,完整路径在返回结果时重建;路径表用开放寻址(`id+1` 槽,约 12 字节/条)替代 `map[string]int32`。
337
+ - **并行扫描**:目录读取协程消费有界队列,每个目录批量写入,保证父目录先于子目录;队列满时退化为就地扫描,不阻塞。
338
+ - **查询**:解析一次查询,分片并行匹配,用有界最大堆选出前 `offset+limit` 条,不排序全部命中。
339
+ - **内容索引**:宿主侧 SQLite FTS5 + trigram 分词,支持中文和子串;文件清单来自引擎已有扫描结果,会话日志用 Node 自带 zstd 解压;查询在 worker 线程执行。
340
+ - **持久化**:快照先写临时文件再原子重命名;generation 计数让守护进程只在索引变化时自动保存;删除量超过阈值时压缩墓碑。
341
+ - **跳转**:文件走 `/reveal` 和 `/open`;会话通过 `uiWorkspace.openSession(sessionId)` 打开,再在页面里定位命中文字。
342
+ - **协议**:引擎通过 stdio 上的 NDJSON 通信,不监听端口;Web 面板只走同源宿主路由。
220
343
 
221
344
  ## 限制
222
345
 
223
- - 内容索引只处理文本类文件;PDF、Word、图片里的文字暂时不提取。
224
- - 文件名索引不是文件系统驱动,只覆盖配置的根目录。
346
+ - 内容索引只处理文本类文件;PDF、Word、图片里的文字暂不提取。
347
+ - 内容预算按扫描顺序消耗,不是按文件重要性排序;达到上限后后面的文件不可搜(`truncated` 为 true)。
348
+ - 名称索引不是文件系统驱动,只覆盖配置的根目录。
225
349
  - Linux inotify 每个目录占一个描述符,目录特别多时可能碰到内核上限;插件会报警,快照仍然可用。
226
350
  - 通过 WSL 写入 `/mnt/c` 的文件,Windows 端可能收不到通知,需要重建对应根目录。
351
+ - 网络盘只在能作为普通目录访问时才会被扫描,没有 SMB 专用的变更流。
227
352
  - 会话内容来自本地日志;如果会话还没落盘,搜不到最新几条消息。
228
- - macOS 的发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建只有 kqueue,目录上限 4096。
353
+ - 两个 DSH 进程共享同一个 `stateDir` 时,构建锁只能串行化写入;建议各用各的缓存目录。
354
+ - macOS 的发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建只有 kqueue,目录上限 4096,且内容变更依赖定时重建。
355
+
356
+ - 文件正文命中目前只在文件管理器里定位(在 DSH 内按行预览是下一步);assistant 消息的 DOM 行不带 messageId,跳转用短语回退定位。
357
+ - 会话内高亮依赖 CSS Custom Highlight API;不支持它的旧浏览器会退化为整行描边。
229
358
 
230
359
  ## 安全与隐私
231
360
 
232
- 如果同时运行两个 DSH 进程(例如 `dsh web` 与 `dsh tui`),请给它们配置不同的 `stateDir`:构建锁只能串行化写入,无法让多个进程共享同一个打开的数据库句柄。
361
+ - 索引完全在本地:不联网、不上传、不埋点;插件不会修改、移动或删除被索引的文件。
362
+ - 内容库与构建锁以 `0600` 创建、状态目录以 `0700` 创建,同机其他用户无法读取被索引的文档或会话正文。
363
+ - 快照和内容库里有文件名、路径和正文,请当作私有数据;删除 `stateDir` 即可清空。
364
+ - 建议给同时运行的多个 DSH 进程配置不同的 `stateDir`。
365
+
366
+ ## 版本与兼容性
367
+
368
+ - Node:`^22.19.0 || >=24.9.0`;DSH:`>=0.2.1-rc.2 <0.3.0`(见 `package.json` 的 `engines`)。
369
+ - 发布走 GitHub Actions provenance,npm 上的包可验证来源;Gitee / GitCode 由 mirror workflow 自动同步。
370
+ - 更新记录:[CHANGELOG.md](CHANGELOG.md);问题与建议:[Issues](https://github.com/PerryLink/dsh-locator/issues)。
371
+
372
+ ## 路线图
233
373
 
234
- 内容索引库与构建锁以 `0600` 创建、状态目录以 `0700` 创建,同机其他用户无法读取被索引的文档或会话正文。
374
+ 不是承诺,只是当前方向:
235
375
 
236
- 索引完全在本地,不联网、不上传、不埋点。快照和内容数据库里有文件名、路径和正文,请把它们当作私有数据。插件不会修改、移动或删除被索引的文件。
376
+ 1. Electron 桌面运行时在真实 Windows 安装上的验证。
377
+ 2. 面板里的预览和结果分组。
378
+ 3. Windows on ARM / Linux ARM 的运行时覆盖。
379
+ 4. 更大的索引:小写名字 arena 与 trigram 预筛选。
380
+ 5. 快照增量压缩与按根目录的 journal。
237
381
 
238
382
  ## 许可证
239
383