@yolk_vat-y/dsh-project-memory 0.5.8 → 0.5.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.
- package/CHANGELOG.md +81 -0
- package/README.md +104 -161
- package/README.zh-CN.md +103 -160
- package/client/client.js +73 -73
- package/cordis.patch.yml +7 -1
- package/package.json +3 -2
- package/scripts/bench-synthetic.mjs +3 -3
- package/scripts/bench.mjs +1 -1
- package/src/auto-inject.js +63 -16
- package/src/client/TaskPanel.tsx +1 -1
- package/src/client/css-modules.d.ts +12 -0
- package/src/commands/insight-actions.js +2 -1
- package/src/commands/task-actions.js +6 -12
- package/src/commands/tasks.js +4 -22
- package/src/index.js +27 -1
- package/src/lazy.js +22 -80
- package/src/reflection-pipeline.js +2 -1
- package/src/setup/taskbridge.js +19 -12
- package/src/tools/forget.js +2 -2
- package/src/tools/index-doc.js +20 -5
- package/src/tools/index-repo.js +50 -17
- package/src/tools/lesson-tools.js +3 -6
- package/src/tools/query-memory.js +3 -11
- package/src/tools/remember.js +2 -2
- package/src/tools/stats.js +2 -2
- package/src/tools/task-tools.js +6 -20
- package/src/tools/watch-repo.js +17 -10
- package/src/util/fs.js +351 -16
- package/src/util/task-view.js +66 -0
- package/src/util/text.js +14 -0
- package/src/watch.js +46 -7
package/README.zh-CN.md
CHANGED
|
@@ -9,15 +9,12 @@
|
|
|
9
9
|
为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)agent 提供持久化的 **项目开发记忆**。专门针对项目开发,原生融合 dsh 任务系统,会话内任务清单与读过的文件自动沉淀为跨会话任务记录,任务↔文件自动关联——开发工作流可切换、可续接,无需重复梳理整个项目,解决上下文失效;文档(PDF/Markdown/txt)与代码符号写入工作区独立存储,文档自动交叉链接至所提及的代码符号;经验笔记(问题 → 方案)自动去重,避免重复踩坑。所有数据按项目落盘,跨会话压缩与交接保留,召回附带 `路径:行号` 可回源核实。单依赖,无向量数据库,无原生构建。
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
> 插件在磁盘上维护一份精简的项目**记忆**,每条记录指向具体的文件与行号;agent
|
|
13
|
-

|
|
14
14
|
|
|
15
|
-
工作流卡片可收起,自动适应dsh及主题插件风格,提供四种卡片风格切换。
|
|
16
|
-
|
|
17
|
-

|
|
18
15
|
## 特性
|
|
19
16
|
|
|
20
|
-
- **TaskBridge:跨会话开发任务** — 会话内的任务清单与触碰过的文件持久化为跨会话的项目任务实体,工作流可以随时切走再续接,不必重新界定项目范围。关联文件按最近活跃排序(读取永远不越过写过的文件),续接时一眼看到该先看哪些。新会话通过 `list_tasks` → `select_task`
|
|
17
|
+
- **TaskBridge:跨会话开发任务** — 会话内的任务清单与触碰过的文件持久化为跨会话的项目任务实体,工作流可以随时切走再续接,不必重新界定项目范围。关联文件按最近活跃排序(读取永远不越过写过的文件),续接时一眼看到该先看哪些。新会话通过 `list_tasks` → `select_task` 续接。委派给子代理的工作不会建档(见「已知限制与边界」)。自动同步需要含会话事件的 dsh 构建;旧宿主下降级为纯记录。
|
|
21
18
|
- **dsh web 浮动任务面板(v0.4.2+)** — 卡片可拖拽,展示任务步骤与涉及文件,可折叠为迷你条或彻底隐藏。面板默认隐藏,仅在显式唤起后出现;会话切换只后台同步,刷新页面不会自行打开。渲染错误有边界兜底,面板崩溃不会拖垮宿主。
|
|
22
19
|
- **面板编辑与外观(v0.4.2+)** — 绑定卡片支持标题与步骤的行内编辑、步骤状态循环切换;非绑定卡片只读。四档外观风格只改材质、几何、字型与密度,颜色跟随宿主。
|
|
23
20
|
- **任务清单双向同步(v0.4.2+)** — 绑定任务时把步骤推给宿主任务清单,面板编辑与模型更新写回同一套逻辑。配置 `tasklist.syncHostOnAdopt` 可关闭。
|
|
@@ -30,71 +27,11 @@
|
|
|
30
27
|
- **经验笔记** — 记录问题 → 方案;相似问题覆盖而不重复,数量随项目规模有界,仅在检索命中时返回。
|
|
31
28
|
- **分层 insight 记忆(教训 / 决策 / 流程,v0.5)** — 同一实体贯穿任务、项目、全局三级。近重复合并或强化;提升是更换归属而非复制。被使用会记账,因此衰减与容量按活跃度排序,而不是只按时间。LLM 反思默认关闭,只写任务级草稿。面板提供分级的记忆视图用于审核与编辑。
|
|
32
29
|
- **触发式注入** — insight 可携带 authored trigger:只有 `when` 能触发,`guard` 只能收窄,`prevents` 记下没有它会坏在哪。语料正文永远不能触发注入;统计通道另有独立门槛。
|
|
33
|
-
- **流式 TF + IDF 缓存** —
|
|
30
|
+
- **流式 TF + IDF 缓存** — 查询路径按存储版本缓存词权重(实测数字见「性能」)。只有真实写入才清空缓存,watch 轮询不会把查询刚建好的缓存清掉。
|
|
34
31
|
- **无锁同步事务** — 所有写入走同步事务,`remember` / `forget` 不会排在重索引之后。锁在进程内:不要让两个 dsh 实例写同一个 store。
|
|
35
32
|
- **依赖极简** — 纯 JavaScript;运行时只有一个用于 PDF 文本提取的依赖,无需原生构建。
|
|
36
33
|
- **开销可忽略** — 记忆操作在进程内完成;瓶颈是文档提取与磁盘 I/O,而不是打分。
|
|
37
34
|
|
|
38
|
-
## 性能
|
|
39
|
-
|
|
40
|
-
### 合成基准测试(Node 24.19,WSL2 / 20 vCPU,Linux 文件系统)
|
|
41
|
-
|
|
42
|
-
| 场景 | 规模 | 实测 |
|
|
43
|
-
|------|------|------|
|
|
44
|
-
| 批量冷记忆构建 | 5,000 文件 / 20k 条目 | 269 ms 均值(p50 267)|
|
|
45
|
-
| 冷加载 | 5,000 文件 | 40 ms |
|
|
46
|
-
| 热路径懒记忆 | 单文件重记忆+落盘 | p50 2.4 ms / 最大 5.5 ms (5k) |
|
|
47
|
-
| query_memory (缓存命中) | 5k 文件 / 20k 条目 | p50 2.6 ms / p95 5.4 ms |
|
|
48
|
-
| query_memory (缓存命中) | 1k 文件 / 4k 条目 | p50 0.6 ms / p95 1.6 ms |
|
|
49
|
-
| 批量冷记忆构建 | 10,000 文件 / 40k 条目 | 551 ms 均值(p50 528)|
|
|
50
|
-
| 冷加载 | 10,000 文件 | 90 ms |
|
|
51
|
-
| 热路径懒记忆 | 单文件重记忆+落盘 | p50 5.4 ms / 最大 9.2 ms (10k) |
|
|
52
|
-
|
|
53
|
-
> 合成基准:生成代码(~4–5 符号/文件),Node 24.19 / WSL2 / 20 vCPU / Linux 文件系统,实测于 2026-09-14。复现命令 `npm run bench:synthetic -- 5000`(脚本 `scripts/bench-synthetic.mjs`)。测量纯索引开销,不含 LLM 调用。query_memory 使用 IDF 缓存 + 预计算 searchText;写入后的首次查询会重建 IDF(**40k 条目 106 ms**,20k 条目 57 ms,4k 条目 12 ms),后续查询命中缓存。
|
|
54
|
-
|
|
55
|
-
### 真实项目存储体积
|
|
56
|
-
|
|
57
|
-
| 项目 | 文件数 | 条目数 | 存储体积 | 单条目 |
|
|
58
|
-
|------|--------|--------|----------|--------|
|
|
59
|
-
| Java Spring Boot 后端 | 1,254 | 7,335 | 6.7 MB | ~0.9 KB |
|
|
60
|
-
| Vue 3 + Vite 前端 | 289 | 2,141 | 1.0 MB | ~0.5 KB |
|
|
61
|
-
|
|
62
|
-
> 真实项目(Java + Vue),测试于 Linux 文件系统(Node 24)。真实项目单条目体积小于合成基准,因符号密度更低、声明行更短。
|
|
63
|
-
|
|
64
|
-
### 自己复现这些数字
|
|
65
|
-
|
|
66
|
-
与其让你相信上面的表格,不如把测量本身一起发布——它随仓库发布,**也随 npm 包一起发布**(`scripts/` 已包含在 tarball 中)。脚本**不需要 dsh 实例、不需要网络、不调用任何模型**,也**不碰被测项目自己的 store**——结果写进临时目录,跑完删除:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm run bench -- /你的/项目路径
|
|
70
|
-
# 或带参数:
|
|
71
|
-
node scripts/bench.mjs /你的/项目路径 [--json] [--samples 100] [--no-pdf] [--keep]
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
输出包含:冷索引(拆成 read+hash / extract / commit 三段)、冷加载、IDF 重建、冷查询与热查询延迟(走线上同一套 scorer,100 条采样报 p50/p95/max)、单文件热重索引、存储体积与每条字节数。示例——我们内部的 Vue 项目(289 文件 / 2,141 条目,Node 24,20 CPU,Linux):
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
冷索引 253 ms (read+hash 9 ms · extract 229 ms · commit 13 ms)← 第二次、页缓存已热
|
|
78
|
-
存储 1.10 MB · 538 bytes/条目 · 冷加载 4.6 ms
|
|
79
|
-
热查询 p50 0.80 ms · p95 1.35 ms (2,141 条目)
|
|
80
|
-
单文件重索引 p50 0.33 ms
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
两个我们宁可自己说清楚的坑:`read+hash` 受操作系统页缓存影响——同一个语料第一遍花了 787 ms、第二遍 253 ms,报数时请说明是第几遍;**真实项目比上面的合成基准慢**——在一个大型 TypeScript 仓库的 3,000 文件切片上(15,594 条目)热查询 p50 为 7.5 ms,因为真实声明文本比生成出来的桩代码长得多。带 `--queries 你的查询集.json` 可以在你自己的项目上跑同一套标注集方法(hit@5 / hit@10 / MRR)。
|
|
84
|
-
|
|
85
|
-
## 工作原理
|
|
86
|
-
|
|
87
|
-
设计遵循四个原则:
|
|
88
|
-
|
|
89
|
-
- **易失性** — 上下文是临时的,会话压缩即丢失。
|
|
90
|
-
- **持久性** — **记忆**存于磁盘,跨压缩与会话保留。
|
|
91
|
-
- **紧凑性** — 代码层每个符号只存一行声明,所以代码为主的项目仍约 **0.5% 源码体积**(示例项目中 8.8 MB 源码 → 49 KB 索引),**召回**替代了通读整个文件。文档层按设计更重:每个 chunk 保留 ≤300 字符的注入 `summary`、覆盖整 chunk 的 `terms`,以及预计算的 `searchText`。纯文档语料实测(179 chunk / 225 KB Markdown):`terms` ≈ 源码 **27.5%**,整库落盘 ≈ 源码 **166%**——文档占比高的项目请按「约等于文档本身大小」估,而不是 0.5%。
|
|
92
|
-
- **可核验性** — **召回**在适用时携带 `路径:行号` 引用,agent 可对照源文件核实。
|
|
93
|
-
|
|
94
|
-
构建**记忆**无需预先全量扫描:文件在模型读取时被记忆,**记忆**恰好覆盖实际处理过的内容。未变更的文件重读是空操作(内容哈希),因此**记忆**的持续维护开销很低。
|
|
95
|
-
|
|
96
|
-
存储按项目独立存放,并跟随代码库变化:文件变更按内容哈希重新抽取,文件删除则同步移除。经验层仅检索,累积不影响上下文。
|
|
97
|
-
|
|
98
35
|
## 安装
|
|
99
36
|
|
|
100
37
|
插件仅依赖通过 peerDependencies 声明的稳定公共 API(`defineTool`、`llm.stream`、`Schema`),保证与后续 rc/alpha 版本无需改动即兼容。
|
|
@@ -126,8 +63,8 @@ dsh plugin --profile web add /path/to/dsh-project-memory.tgz
|
|
|
126
63
|
| 工具 | 用途 |
|
|
127
64
|
|---|---|
|
|
128
65
|
| `index_doc file_path` | 索引单个文档(PDF/MD/txt):分块 → 确定性 `summary` + 整 chunk `terms` → 带 `路径:行号` 入库。未变更文件自动跳过。 |
|
|
129
|
-
| `index_repo root` | 索引整个项目:文档生成确定性摘要 + 整 chunk 词项,代码文件生成零 token 符号表。增量更新、清理已删除文件、文档与符号交叉链接。根目录不存在(含在 Linux/macOS 上被解析成相对路径的 Windows
|
|
130
|
-
| `watch_repo root` | 启用自动刷新:后台轮询检测新增/变更文件(mtime +
|
|
66
|
+
| `index_repo root` | 索引整个项目:文档生成确定性摘要 + 整 chunk 词项,代码文件生成零 token 符号表。增量更新、清理已删除文件、文档与符号交叉链接。根目录不存在(含在 Linux/macOS 上被解析成相对路径的 Windows 风格路径)时会在写入任何内容前直接拒绝;**危险根**(家目录、文件系统根、系统/包管理器前缀)同样拒绝——扫它们等于走几十万个文件。 |
|
|
67
|
+
| `watch_repo root` | 启用自动刷新:后台轮询检测新增/变更文件(mtime + 内容哈希),仅重抽这些文件。监听的项目在插件重启后自动恢复;不存在的根目录与危险根(文件系统根、家目录、共享临时目录、系统/包管理器前缀)都会被拒绝,已消失的根目录会被丢弃而不是被重新创建,旧版本遗留的污染 watchlist 会在启动时自愈。 |
|
|
131
68
|
| `memory_stats root` | 查看记忆库内容:总量(文件 / 条目 / 经验笔记)、最近索引时间,以及按时间排序的逐文件清单。 |
|
|
132
69
|
| `query_memory query` | 对文档、符号、经验与 insight(教训/决策/流程)执行 BM25 检索,可选 LLM 查询扩展。`type` 选择层(`all` / `doc` / `symbol` / `experience` / `insight` / `task`)。返回带相对分数(0-100)、引用或 insight id、以及文档→符号链接的排序结果。 |
|
|
133
70
|
| `list_tasks` | 列出本项目任务记录(含归档,带标记)。新会话/续接前先调用。 |
|
|
@@ -145,6 +82,19 @@ dsh plugin --profile web add /path/to/dsh-project-memory.tgz
|
|
|
145
82
|
所以插件的宿主命令**只保留 `/tasks` 一条**,其余动作全部作为它的子动词由卡片按钮驱动:菜单里的重复因此只有一行。
|
|
146
83
|
手敲 `/tasks` 回车即执行(与合并前一致);手敲 `/tasks switch x` 这类带参数的行不再被识别为命令,请用卡片按钮。
|
|
147
84
|
|
|
85
|
+
## 工作原理
|
|
86
|
+
|
|
87
|
+
设计遵循四个原则:
|
|
88
|
+
|
|
89
|
+
- **易失性** — 上下文是临时的,会话压缩即丢失。
|
|
90
|
+
- **持久性** — **记忆**存于磁盘,跨压缩与会话保留。
|
|
91
|
+
- **紧凑性** — 代码层每个符号只存一行声明,所以代码为主的项目仍约 **0.5% 源码体积**(示例项目中 8.8 MB 源码 → 49 KB 索引),**召回**替代了通读整个文件。文档层按设计更重:每个 chunk 保留 ≤300 字符的注入 `summary`、覆盖整 chunk 的 `terms`,以及预计算的 `searchText`。纯文档语料实测(179 chunk / 225 KB Markdown):`terms` ≈ 源码 **27.5%**,整库落盘 ≈ 源码 **166%**——文档占比高的项目请按「约等于文档本身大小」估,而不是 0.5%。
|
|
92
|
+
- **可核验性** — **召回**在适用时携带 `路径:行号` 引用,agent 可对照源文件核实。
|
|
93
|
+
|
|
94
|
+
构建**记忆**无需预先全量扫描:文件在模型读取时被记忆,**记忆**恰好覆盖实际处理过的内容。未变更的文件重读是空操作(内容哈希),因此**记忆**的持续维护开销很低。
|
|
95
|
+
|
|
96
|
+
存储按项目独立存放,并跟随代码库变化:文件变更按内容哈希重新抽取,文件删除则同步移除。经验层仅检索,累积不影响上下文。
|
|
97
|
+
|
|
148
98
|
## 设计
|
|
149
99
|
|
|
150
100
|
```
|
|
@@ -178,97 +128,9 @@ TaskPanel (Container)
|
|
|
178
128
|
└── TaskComponents (MiniBar, TaskCard — 纯展示组件)
|
|
179
129
|
```
|
|
180
130
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
### 1. 同步无锁事务,而非异步锁
|
|
184
|
-
|
|
185
|
-
**我们做:** 所有写入走 `store.commit(fn)` 同步事务。回调 `fn` 内完成校验与变更,成功后才原子落盘。JS 事件循环天然串行,CAS (`applyFileUpdate`) 让并发写入幂等。
|
|
186
|
-
|
|
187
|
-
**不做:** 异步互斥锁、文件锁、多进程协调。
|
|
188
|
-
|
|
189
|
-
**为什么:** DSH 基于 Cordis,单进程是架构基石。为极少见的多进程场景加锁,会让热路径(每次 `remember`/`forget`/`index_doc`)增重。同步事务让热路径中位数 ~2 ms,零争用开销。
|
|
190
|
-
|
|
191
|
-
### 2. Watch:事务外计算,事务内提交
|
|
192
|
-
|
|
193
|
-
**我们做:** 重活(mtime/哈希/扫描/解析/PDF 抽取)在事务外跑,单次 `commit` 原子应用全部变更。失败回滚 snapshot,下轮自动重试。
|
|
194
|
-
|
|
195
|
-
**不做:** 持锁做解析,或用 `fs.watch` 事件。
|
|
196
|
-
|
|
197
|
-
**为什么:** PDF 抽取与大文件解析耗时明显,持锁会阻塞 `remember`/`forget`/`query_memory`。轮询 + mtime+内容哈希跨平台一致(网络盘、Docker 卷、WSL 皆可),避免 `fs.watch` 的「重复触发/漏事件」噩梦。
|
|
198
|
-
|
|
199
|
-
### 3. 损坏文件隔离,不自动修复
|
|
200
|
-
|
|
201
|
-
**我们做:** JSON 解析失败时,坏文件改名 `*.corrupt`、记错误、该文件存储重头开始,其余分片不受影响。
|
|
202
|
-
|
|
203
|
-
**不做:** 预写日志 (WAL)、嵌入式数据库、自动部分恢复。
|
|
204
|
-
|
|
205
|
-
**为什么:** 一个损坏分片 = 一个源文件索引丢失,隔离成本近零。WAL 或嵌入式 DB 增加 500 KB+ 原生依赖、锁竞争、新故障模式(WAL 自身损坏)。权衡:丢一个文件索引 vs. 引入重型原生栈。
|
|
206
|
-
|
|
207
|
-
### 4. 查询零向量、零语义搜索
|
|
208
|
-
|
|
209
|
-
**我们做:** BM25 + CJK 短语加分(3+ 字 ×1.5)、同义词表双向展开、字段加权(标题 ×5)、经验层短语加分。查询侧零 LLM 调用。
|
|
131
|
+
工作流卡片可收起,自动适应 dsh 及主题插件风格,提供四种卡片风格切换。
|
|
210
132
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
**为什么:** 向量需要嵌入模型(本地重、远程慢+贵+隐私)、向量索引(HNSW/IVF 占内存+建索引慢)、重排序(再调一次 LLM)。对本插件所针对的查询,词法 BM25 已经够用且可核实:基准(真实 Vue 项目 29 条查询)文件级 hit@5 为 **96.6%**,其中 28 条是精确符号名查询、词法检索基本必中;整 chunk `terms` 把文档词项覆盖从 **27.3% 提到 100%**,而原本可答的查询排序不变(MRR **0.958** vs **0.955**)。这些数字来自内部一个 Vue 项目 + 手工标注的 29 条查询集,出了那个语料无法复现——但**方法**已随代码发布:`scripts/bench.mjs --queries 你的查询集.json`,可以在你自己的项目上跑完全相同的测量。边际收益不抵 10x 复杂度/成本。
|
|
214
|
-
|
|
215
|
-
### 5. 索引确定且不调用模型
|
|
216
|
-
|
|
217
|
-
**我们做:** keywords 由规则推导(标题加权词项),并构建覆盖整 chunk 的 `terms`——两者都确定、可复现。doc↔symbol 链接从中文命中带出英文符号名,CJK 分词保证跨语种命中。`llmQueryExpansion: false` 时查询完全不碰 LLM。
|
|
218
|
-
|
|
219
|
-
**不做:** 索引时调用模型去翻译或改写文档,也不在查询时翻译。
|
|
220
|
-
|
|
221
|
-
**为什么:** 索引期调用模型会让索引变慢、不确定、不可复现——同一份文档两次索引可能得到不同结果。查询时翻译增延迟,且有一个硬失败模式(译错 = 零召回)。规则 + 符号链接覆盖常见情况,离线可用,并让索引期保持零模型调用。
|
|
222
|
-
|
|
223
|
-
### 6. 面向模型的记忆:agent 自己写,不把人放进回路
|
|
224
|
-
|
|
225
|
-
**我们做:** 把 agent 当作一等写入者。`remember` / `save_lesson` **随时可写任意作用域**(`task` / `project` / `global`),不需要人参与;提升是确定性的,就发生在普通写入路径里:跨任务的 token 重叠去重会累积 `sourceTaskIds`,随后 `promoteAllTasksToProject` / `promoteProjectToGlobal` 在佐证数达标时把条目上移(升 project 需 ≥2 个任务命中,升 global 需 ≥ `globalPromoteTasks`,默认 3)。整条链路不等任务面板:用户从不打开 UI,记忆照样会积累、去重、逐级提升。
|
|
226
|
-
|
|
227
|
-
**我们做(标注):** 让「推断出来的」和「记录下来的」可区分。v0.5 `reflection`(可选、**默认关闭**)是唯一做推断而非记录的写入者:它写的是 task 级草稿,带 `draft: true` / `source: 'reflect'`;只要还是草稿,`recall` 与静默注入就会跳过它。
|
|
228
|
-
|
|
229
|
-
**不做:** 不要求「人工批准」记忆才能生效,也不把 UI 变成写入路径上的一步。`draft` 是**来源标记 + 佐证门槛**,不是审批队列。
|
|
230
|
-
|
|
231
|
-
**为什么:** 记忆的消费方是 agent,而 agent 通常是无头的——只在有人点卡片时才升级的记忆,等于永远不会升级。保留标注就保住了这份谨慎里有用的那一半(推断 ≠ 记录,且未经佐证的单任务推断不进提示词),同时又不用给正常路径加税。草稿靠佐证毕业:第二个任务通过模型自己的写入命中了它,或者模型把同一知识直接写到 project 级(此时会挂到既有条目上,而不是复制一份)。
|
|
232
|
-
|
|
233
|
-
### 7. 直接返回完整条目
|
|
234
|
-
|
|
235
|
-
**我们做:** `query_memory` 直接返回含 `path:line` 引用的完整条目,每条可回源核实。
|
|
236
|
-
|
|
237
|
-
**不做:** 先返回极简索引(如 700 字符),再二次调工具取详情。
|
|
238
|
-
|
|
239
|
-
**为什么:** 完整返回保持 **可核验性**——Agent 能看到每条声明的出处行号。也避免了每次有效命中多一轮工具调用+上下文切换。条目本已紧凑(~300 字摘要+引用,外加一个从不进上下文的检索用 `terms`),完整返回的 token 成本低于二次调用。
|
|
240
|
-
|
|
241
|
-
### 8. 符号提取聚焦开发者实际搜索的内容
|
|
242
|
-
|
|
243
|
-
**我们做:** 正则符号提取(函数/类/方法/接口/类型别名),含字符串/注释掩码、多行签名、跨文件按名链接。对 TypeScript/JavaScript 项目,可选的 L2 增强层利用 TS Compiler API 推导返回类型、实例化泛型、提取接口 —— 全部按内容哈希缓存,毫秒级复用。
|
|
244
|
-
|
|
245
|
-
**不做:** tree-sitter AST、导入图、调用图、跨文件全程序类型推导。
|
|
246
|
-
|
|
247
|
-
**为什么:** 正则扫描器零依赖、8 语言、<1 ms/文件,覆盖开发者最常搜索的声明(名字、签名、泛型)。可选 TS 增强层为 TS/JS 提供语义深度,且无原生依赖。按名跨文件链接已覆盖最常见的「找相关代码」场景。全程序分析会引入原生二进制、安装体积增 10x、语言版本即破——边际收益仅在剩余 5% 的边缘情况。
|
|
248
|
-
|
|
249
|
-
### 9. `forget` 按关键词激进;精确请用 ID
|
|
250
|
-
|
|
251
|
-
**我们做:** `forget query` 删除所有 token 重叠 ≥0.5 的经验笔记。
|
|
252
|
-
|
|
253
|
-
**不做:** 交互确认、软删除/回收站、仅精确匹配。
|
|
254
|
-
|
|
255
|
-
**为什么:** 经验笔记低风险、高量、仅检索。激进删除防止陈旧噪音污染搜索。精确删用 ID(`query_memory` 输出里有)。
|
|
256
|
-
|
|
257
|
-
### 10. TS 增强可选、异步、缓存
|
|
258
|
-
|
|
259
|
-
**我们做:** L2 TS Compiler API 在优先级队列异步跑(P0 `fs/observed`、P1 `watch`、P2 `index_repo`),结果按内容哈希缓存 `type-cache/`。零配置——`npm i -D typescript@5` 或 `npm i -D typescript@6` 即用。无 TS 或禁用时优雅回退 L1 正则。
|
|
260
|
-
|
|
261
|
-
**不做:** 强制 TS、阻塞式增强、全程序类型检查。
|
|
262
|
-
|
|
263
|
-
**为什么:** 强制 TS 会让非 TS 项目装不上。阻塞增强会卡死大项目 `index_repo`。全程序检查慢 10x、内存重。设计:读到即增强、缓存复用、热路径永不阻塞。
|
|
264
|
-
|
|
265
|
-
### 11. 子代理会话暂不纳入(以后可能做)
|
|
266
|
-
|
|
267
|
-
**我们做:** 把以子代理身份启动的会话(`origin: 'subagent'` / `delegationDepth > 0`)排除在自动建档与绑定之外:它们的 `todo_write` 不建任务,也不继承任何任务绑定。
|
|
268
|
-
|
|
269
|
-
**不做:** 把委派出去的运行的 steps 与文件并回派发它的那条任务。这块**尚未设计**:目前没有委派工作的父子关联模型,而粗暴实现只会让每个子代理各铸一条项目任务。
|
|
270
|
-
|
|
271
|
-
**为什么:** 子代理只要写一次 todo 就会各自建档,一次 fan-out 就会往任务列表里灌进一批没人会续接的临时条目。排除掉它们,任务列表才等于「用户真正拥有的工作」。代价是委派进度在任务记录里不可见;正确的合并方式(子步骤折进父任务,或单列一个委派视图)属于后续工作。
|
|
133
|
+

|
|
272
134
|
|
|
273
135
|
## 配置
|
|
274
136
|
|
|
@@ -288,11 +150,19 @@ TaskPanel (Container)
|
|
|
288
150
|
| `autoIndexOnFirstUse` | false | 插件加载时对当前工作目录做全量扫描(可选) |
|
|
289
151
|
| `watch` | true | 启用后台刷新 |
|
|
290
152
|
| `watchInterval` | 15 | 轮询间隔(秒) |
|
|
153
|
+
| `maxScanFiles` | 20000 | 单次扫描的文件数硬上限;被截断时会在报告里说明,且**绝不**删除没扫到的旧条目。设 `0` 取消上限(自担风险) |
|
|
154
|
+
| `maxScanDepth` | 12 | 单次扫描的目录深度硬上限。设 `0` 取消 |
|
|
155
|
+
| `allowUnsafeRoots` | false | 允许**显式**工具调用(带 `root` 的 `index_repo`/`watch_repo`/`remember`)指向危险根。自动路径(懒索引、会话审计、TaskBridge、`autoIndexOnFirstUse`)无论此项如何都不会越权 |
|
|
291
156
|
| `tsPath` | (自动) | 可选:强制指定特定 `typescript` 安装路径;省略时按项目 cwd → 插件 node_modules 向上解析 |
|
|
292
157
|
| `enableTypeScript` | true | 设为 `false` 彻底禁用 L2 TS 增强(仅保留 L1 正则) |
|
|
158
|
+
|
|
159
|
+
### 记忆与注入旋钮
|
|
160
|
+
|
|
161
|
+
| 键 | 默认值 | 含义 |
|
|
162
|
+
|---|---|---|
|
|
293
163
|
| `insight.*` | dedupOverlap `0.7` · reinforceBand `0.65` · maxProject `100` · maxGlobalProcedures `200` · promoteConfidence `0.7` · globalPromoteTasks `3` · decayDays `90` · `globalFile`(自动) | v0.5 insight 去重/强化/提升/容量/归档设置 |
|
|
294
164
|
| `reflection.enabled` | false | v0.5 LLM 反思,**只写任务级草稿**(触发于任务切走/归档)。`cooldownMs` `1800000`、`maxLessonsPerReflect` `3`、`maxDecisionsPerReflect` `2` |
|
|
295
|
-
| `autoContext.enabled` | true | v0.5 静默注入包装(entry 常驻块 + relevance)。宿主无法解析会话 cwd 时完全透传(零副作用);`maxTokens` `400`、`editedMax` `3`(resident 任务卡显示最近"编辑中"文件数)、`signalMinRatio` `0.5`(提示至少要达到该层最高分的一半)、`skipEchoSelfTodo` `true`(模型自己写/维护任务清单后、无新人类消息时不回声任务卡,省 token;相关 insights 仍注入)、`budgetLog` `off`(预算丢弃审计写到 stderr:`off` 静默 / `once` 每会话最多一行 / `all` 丢弃组合每变化一次一行。注入按优先级排程,预算不够时丢掉低优先级条目属于**正常降级而非故障**,所以默认不占用用户终端)、`reinjectItemsAfter` `0`(同一条 insight 重复注入的冷却步数;`0` =
|
|
165
|
+
| `autoContext.enabled` | true | v0.5 静默注入包装(entry 常驻块 + relevance)。宿主无法解析会话 cwd 时完全透传(零副作用);`maxTokens` `400`、`editedMax` `3`(resident 任务卡显示最近"编辑中"文件数)、`signalMinRatio` `0.5`(提示至少要达到该层最高分的一半)、`skipEchoSelfTodo` `true`(模型自己写/维护任务清单后、无新人类消息时不回声任务卡,省 token;相关 insights 仍注入)、`budgetLog` `off`(预算丢弃审计写到 stderr:`off` 静默 / `once` 每会话最多一行 / `all` 丢弃组合每变化一次一行。注入按优先级排程,预算不够时丢掉低优先级条目属于**正常降级而非故障**,所以默认不占用用户终端)、`reinjectItemsAfter` `0`(同一条 insight 重复注入的冷却步数;`0` = 正文没变就不在本会话内再注入——注入消息留在会话历史里,重发只是重复占位)、`rootNotice` `true`(记忆根是从无标记的工作目录**推定**出来时,向模型通告一次根在哪、怎么改) |
|
|
296
166
|
| `autoContext.gateCooldownSteps` | 2 | **准入旋钮**:两次*条目*注入之间至少隔几步(常驻任务卡不受限——它是状态快照,内容变了就该更新)。这是"别频繁注入"的主旋钮 |
|
|
297
167
|
| `autoContext.maxItemsPerSession` | 12 | 每会话条目注入条数硬上限;预算是上限不是目标,用尽后条目通道持续沉默 |
|
|
298
168
|
| `autoContext.maxItemCharsPerSession` | 4000 | 同上,按字符计 |
|
|
@@ -319,6 +189,16 @@ TaskPanel (Container)
|
|
|
319
189
|
|
|
320
190
|
两个最常用的开关是 `lazyIndexing`(模型读取文件的瞬间即索引;默认开启)和 `autoIndexOnFirstUse`(插件加载时对当前工作目录做全量扫描;默认关闭)。懒加载建立的索引根会自动注册到 watcher,文件变更无需手动 `watch_repo` 也能保持新鲜。
|
|
321
191
|
|
|
192
|
+
**项目根是怎么定的。** 全插件同一套策略——懒索引、会话审计、TaskBridge、所有工具都走它:显式 `root` 参数优先;其次是显式登记的根(`watch_repo`);其次是最近的、含 VCS 标记(`.git`/`.hg`/`.svn`)或构建/清单标记(`package.json`、`go.mod`、`Cargo.toml`、`pyproject.toml` 等)的祖先目录;最后是**会话工作目录本身(只要它是安全目录)**。所以你在一个没有标记的临时目录里启动 dsh,项目记忆照样能用——插件只会说明一次:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
memory root: /Users/me/scratch (inferred from the session working directory; no project marker found).
|
|
196
|
+
If project memory should live elsewhere, pass `root: <dir>` to index_repo / watch_repo / remember / query_memory,
|
|
197
|
+
or restart dsh inside the project directory.
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
这条通告每个会话只发一次,可用 `autoContext.rootNotice: false` 关掉。插件**不会**做的是把任意目录升格成项目:在工作目录之外读到一个散文件不会记任何东西;危险根(文件系统根、家目录、共享临时目录、系统/包管理器前缀——POSIX 上如 `/opt/homebrew`,Windows 上是 `%SystemRoot%`/`%ProgramFiles%`/`%ProgramData%`)直接拒绝——旧版正是从这些目录一路扫下去把内存打满的。工作目录属于这些目录的会话,记忆功能整体停用(stderr 会有一行说明)。
|
|
201
|
+
|
|
322
202
|
配置存放在插件的 config 对象中。修改方式:在 profile 的 `cordis.patch.yml` 里加一条覆盖项——web profile 对应 `~/.dsh/profiles/web/cordis.patch.yml`:
|
|
323
203
|
|
|
324
204
|
```yaml
|
|
@@ -329,7 +209,10 @@ TaskPanel (Container)
|
|
|
329
209
|
llmQueryExpansion: false # 关闭:不用 LLM 扩展查询,节省 token(默认)
|
|
330
210
|
watch: true # 开启:被监听根目录后台保持新鲜(默认)
|
|
331
211
|
watchInterval: 15 # 轮询间隔(秒)
|
|
212
|
+
maxScanFiles: 20000 # 单次扫描文件上限(截断会报告,且不会误删旧条目)
|
|
213
|
+
maxScanDepth: 12 # 单次扫描目录深度上限
|
|
332
214
|
enableTypeScript: true # 开启:装了 TS 时启用 L2 语义增强(默认)
|
|
215
|
+
# allowUnsafeRoots: false # 保持 false,除非你确实要显式索引家目录/系统目录
|
|
333
216
|
# budgetLog: once # 调试用:注入被预算挤掉时在 stderr 留痕(默认 off 静默)
|
|
334
217
|
# reinjectItemsAfter: 20 # 调试用:同一条 insight 隔 N 步才允许重发(默认 0 = 本会话只发一次)
|
|
335
218
|
# tsPath: /custom/path/to/typescript # 可选:强制指定 TS 安装路径
|
|
@@ -345,6 +228,66 @@ dsh web --patch ./config.yml
|
|
|
345
228
|
|
|
346
229
|
其中 `config.yml` 内容就是上面的覆盖块。
|
|
347
230
|
|
|
231
|
+
## 性能
|
|
232
|
+
|
|
233
|
+
### 合成基准测试(Node 24.19,WSL2 / 20 vCPU,Linux 文件系统)
|
|
234
|
+
|
|
235
|
+
| 场景 | 规模 | 实测 |
|
|
236
|
+
|------|------|------|
|
|
237
|
+
| 批量冷记忆构建 | 5,000 文件 / 20k 条目 | 269 ms 均值(p50 267)|
|
|
238
|
+
| 冷加载 | 5,000 文件 | 40 ms |
|
|
239
|
+
| 热路径懒记忆 | 单文件重记忆+落盘 | p50 2.4 ms / 最大 5.5 ms (5k) |
|
|
240
|
+
| query_memory (缓存命中) | 5k 文件 / 20k 条目 | p50 2.6 ms / p95 5.4 ms |
|
|
241
|
+
| query_memory (缓存命中) | 1k 文件 / 4k 条目 | p50 0.6 ms / p95 1.6 ms |
|
|
242
|
+
| 批量冷记忆构建 | 10,000 文件 / 40k 条目 | 551 ms 均值(p50 528)|
|
|
243
|
+
| 冷加载 | 10,000 文件 | 90 ms |
|
|
244
|
+
| 热路径懒记忆 | 单文件重记忆+落盘 | p50 5.4 ms / 最大 9.2 ms (10k) |
|
|
245
|
+
|
|
246
|
+
> 合成基准:生成代码(~4–5 符号/文件),Node 24.19 / WSL2 / 20 vCPU / Linux 文件系统,实测于 2026-09-14。复现命令 `npm run bench:synthetic -- 5000`(脚本 `scripts/bench-synthetic.mjs`)。测量纯索引开销,不含 LLM 调用。query_memory 使用 IDF 缓存 + 预计算 searchText;写入后的首次查询会重建 IDF(**40k 条目 106 ms**,20k 条目 57 ms,4k 条目 12 ms),后续查询命中缓存。
|
|
247
|
+
|
|
248
|
+
### 真实项目存储体积
|
|
249
|
+
|
|
250
|
+
| 项目 | 文件数 | 条目数 | 存储体积 | 单条目 |
|
|
251
|
+
|------|--------|--------|----------|--------|
|
|
252
|
+
| Java Spring Boot 后端 | 1,254 | 7,335 | 6.7 MB | ~0.9 KB |
|
|
253
|
+
| Vue 3 + Vite 前端 | 289 | 2,141 | 1.0 MB | ~0.5 KB |
|
|
254
|
+
|
|
255
|
+
> 真实项目(Java + Vue),测试于 Linux 文件系统(Node 24)。真实项目单条目体积小于合成基准,因符号密度更低、声明行更短。
|
|
256
|
+
|
|
257
|
+
### 自己复现这些数字
|
|
258
|
+
|
|
259
|
+
与其让你相信上面的表格,不如把测量本身一起发布——它随仓库发布,**也随 npm 包一起发布**(`scripts/` 已包含在 tarball 中)。脚本**不需要 dsh 实例、不需要网络、不调用任何模型**,也**不碰被测项目自己的 store**——结果写进临时目录,跑完删除:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm run bench -- /你的/项目路径
|
|
263
|
+
# 或带参数:
|
|
264
|
+
node scripts/bench.mjs /你的/项目路径 [--json] [--samples 100] [--no-pdf] [--keep]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
输出包含:冷索引(拆成 read+hash / extract / commit 三段)、冷加载、IDF 重建、冷查询与热查询延迟(走线上同一套 scorer,100 条采样报 p50/p95/max)、单文件热重索引、存储体积与每条字节数。示例——我们内部的 Vue 项目(289 文件 / 2,141 条目,Node 24,20 CPU,Linux):
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
冷索引 253 ms (read+hash 9 ms · extract 229 ms · commit 13 ms)← 第二次、页缓存已热
|
|
271
|
+
存储 1.10 MB · 538 bytes/条目 · 冷加载 4.6 ms
|
|
272
|
+
热查询 p50 0.80 ms · p95 1.35 ms (2,141 条目)
|
|
273
|
+
单文件重索引 p50 0.33 ms
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
两个我们宁可自己说清楚的坑:`read+hash` 受操作系统页缓存影响——同一个语料第一遍花了 787 ms、第二遍 253 ms,报数时请说明是第几遍;**真实项目比上面的合成基准慢**——在一个大型 TypeScript 仓库的 3,000 文件切片上(15,594 条目)热查询 p50 为 7.5 ms,因为真实声明文本比生成出来的桩代码长得多。带 `--queries 你的查询集.json` 可以在你自己的项目上跑同一套标注集方法(hit@5 / hit@10 / MRR)。
|
|
277
|
+
|
|
278
|
+
## 设计取舍
|
|
279
|
+
|
|
280
|
+
- **同步无锁事务,而非异步锁** — 不加异步锁、文件锁或多进程协调:DSH 基于 Cordis,单进程是架构基石,为极少见的多进程场景加锁只会让热路径(每次 `remember`/`forget`/`index_doc`)增重;同步事务下热路径中位数 ~2 ms,零争用。
|
|
281
|
+
- **Watch:事务外计算,事务内提交** — 不持锁解析、也不用 `fs.watch`:解析与 PDF 抽取很慢,持锁会阻塞查询;轮询 + mtime/内容哈希在网络盘、Docker 卷、WSL 上行为一致,没有 `fs.watch` 的重复触发/漏事件问题。
|
|
282
|
+
- **损坏分片隔离,不自动修复** — 解析失败的 JSON 改名 `*.corrupt`、该文件重头开始,其余分片不受影响;不引入 WAL 或嵌入式数据库:那要多 500 KB+ 原生依赖、锁竞争,以及一个新故障模式(WAL 自身损坏),而代价只是丢一个文件的索引。
|
|
283
|
+
- **查询零向量、零语义搜索** — 不引入嵌入模型、向量索引(HNSW/IVF)或重排序:在本插件针对的查询上词法检索已经够用——真实 Vue 项目 29 条标注查询的文件级 hit@5 为 **96.6%**,整 chunk `terms` 把文档词项覆盖从 **27.3% 提到 100%** 且 MRR 不变(**0.958** vs **0.955**)。边际收益不抵 10x 复杂度/成本;方法随代码发布,`scripts/bench.mjs --queries 你的查询集.json` 可在你自己的项目上复现。
|
|
284
|
+
- **索引确定且不调用模型** — 索引期不调模型、查询期也不翻译:前者会让同一份文档两次索引结果不同,后者有一个硬失败模式(译错 = 零召回);规则 + 符号链接已经覆盖常见情况,且离线可用。
|
|
285
|
+
- **面向模型的记忆:agent 自己写,不把人放进回路** — 不要求人工批准:记忆的消费方是 agent,而 agent 通常是无头的,只在有人点卡片时才升级的记忆等于永远不会升级。`draft` 是「来源标记 + 佐证门槛」而不是审批队列——唯一的推断型写入者 `reflection`(默认关闭)只写任务级草稿,草稿不进召回与注入。
|
|
286
|
+
- **直接返回完整条目** — 条目本就紧凑,完整返回更可核验,也少一轮往返。
|
|
287
|
+
- **`forget` 按关键词激进;精确请用 ID** — 不做确认弹窗、回收站或仅精确匹配:经验笔记低风险、高量、仅用于检索,陈旧噪音比误删更伤。精确删用 `query_memory` 输出里的 ID。
|
|
288
|
+
- **TS 增强可选、异步、缓存** — L2 TS Compiler API 在优先级队列异步跑(P0 `fs/observed`、P1 `watch`、P2 `index_repo`),结果按内容哈希缓存;不强制 TS、也不阻塞索引:强制会让非 TS 项目装不上,阻塞会卡死大项目的 `index_repo`;`npm i -D typescript@5|6` 即自动启用,没有 TS 时回退 L1 正则。
|
|
289
|
+
- **子代理会话暂不纳入(以后可能做)**
|
|
290
|
+
|
|
348
291
|
## 开发(面向贡献者)
|
|
349
292
|
|
|
350
293
|
以下命令用于**维护插件源码**,普通用户无需执行。安装插件只需使用[安装](#安装)一节中的命令。
|
package/client/client.js
CHANGED
|
@@ -668,92 +668,92 @@ window.__ModuleLoader__.load({
|
|
|
668
668
|
document.head.appendChild(tag);
|
|
669
669
|
}
|
|
670
670
|
var TaskPanel_module_css_default = {
|
|
671
|
-
"
|
|
672
|
-
"
|
|
673
|
-
"dragGhostClose": "vZSZnG_dragGhostClose",
|
|
674
|
-
"counts": "vZSZnG_counts",
|
|
675
|
-
"dragGhostContent": "vZSZnG_dragGhostContent",
|
|
676
|
-
"dropTargetBefore": "vZSZnG_dropTargetBefore",
|
|
677
|
-
"stepContent": "vZSZnG_stepContent",
|
|
678
|
-
"memRowArchived": "vZSZnG_memRowArchived",
|
|
679
|
-
"memCount": "vZSZnG_memCount",
|
|
680
|
-
"stepDone": "vZSZnG_stepDone",
|
|
671
|
+
"memNew": "vZSZnG_memNew",
|
|
672
|
+
"dragHandle": "vZSZnG_dragHandle",
|
|
681
673
|
"headerIconBtn": "vZSZnG_headerIconBtn",
|
|
682
|
-
"
|
|
674
|
+
"memCount": "vZSZnG_memCount",
|
|
683
675
|
"memTitle": "vZSZnG_memTitle",
|
|
684
|
-
"
|
|
685
|
-
"
|
|
686
|
-
"
|
|
687
|
-
"
|
|
688
|
-
"
|
|
689
|
-
"
|
|
690
|
-
"
|
|
691
|
-
"
|
|
692
|
-
"
|
|
693
|
-
"
|
|
676
|
+
"headerTitle": "vZSZnG_headerTitle",
|
|
677
|
+
"memToolbarActs": "vZSZnG_memToolbarActs",
|
|
678
|
+
"stepContentRun": "vZSZnG_stepContentRun",
|
|
679
|
+
"miniChevron": "vZSZnG_miniChevron",
|
|
680
|
+
"panel": "vZSZnG_panel",
|
|
681
|
+
"memRowArchived": "vZSZnG_memRowArchived",
|
|
682
|
+
"filePath": "vZSZnG_filePath",
|
|
683
|
+
"notice": "vZSZnG_notice",
|
|
684
|
+
"fileDot": "vZSZnG_fileDot",
|
|
685
|
+
"cardTitle": "vZSZnG_cardTitle",
|
|
686
|
+
"cardTitleRow": "vZSZnG_cardTitleRow",
|
|
687
|
+
"miniText": "vZSZnG_miniText",
|
|
688
|
+
"stepContent": "vZSZnG_stepContent",
|
|
689
|
+
"fileLine": "vZSZnG_fileLine",
|
|
690
|
+
"boundBadge": "vZSZnG_boundBadge",
|
|
694
691
|
"card": "vZSZnG_card",
|
|
695
|
-
"
|
|
696
|
-
"
|
|
697
|
-
"stepRun": "vZSZnG_stepRun",
|
|
698
|
-
"dragGhostCard": "vZSZnG_dragGhostCard",
|
|
699
|
-
"hintsOff": "vZSZnG_hintsOff",
|
|
700
|
-
"stepToggle": "vZSZnG_stepToggle",
|
|
701
|
-
"taskList": "vZSZnG_taskList",
|
|
692
|
+
"updated": "vZSZnG_updated",
|
|
693
|
+
"muted": "vZSZnG_muted",
|
|
702
694
|
"memActs": "vZSZnG_memActs",
|
|
703
|
-
"
|
|
704
|
-
"cardFooter": "vZSZnG_cardFooter",
|
|
695
|
+
"memLabel": "vZSZnG_memLabel",
|
|
705
696
|
"memToolbar": "vZSZnG_memToolbar",
|
|
706
|
-
"
|
|
707
|
-
"
|
|
697
|
+
"syncHint": "vZSZnG_syncHint",
|
|
698
|
+
"miniIcon": "vZSZnG_miniIcon",
|
|
699
|
+
"dropTargetAfter": "vZSZnG_dropTargetAfter",
|
|
700
|
+
"stepPending": "vZSZnG_stepPending",
|
|
701
|
+
"dragGhostCard": "vZSZnG_dragGhostCard",
|
|
702
|
+
"stepEditable": "vZSZnG_stepEditable",
|
|
703
|
+
"sectionCount": "vZSZnG_sectionCount",
|
|
704
|
+
"stepToggle": "vZSZnG_stepToggle",
|
|
705
|
+
"hintsOff": "vZSZnG_hintsOff",
|
|
706
|
+
"boundaryFallback": "vZSZnG_boundaryFallback",
|
|
707
|
+
"emptyTitle": "vZSZnG_emptyTitle",
|
|
708
|
+
"memRowMain": "vZSZnG_memRowMain",
|
|
709
|
+
"cardHead": "vZSZnG_cardHead",
|
|
708
710
|
"header": "vZSZnG_header",
|
|
709
|
-
"filePath": "vZSZnG_filePath",
|
|
710
|
-
"cardTitleRow": "vZSZnG_cardTitleRow",
|
|
711
|
-
"cardTitleEditable": "vZSZnG_cardTitleEditable",
|
|
712
|
-
"boundBadge": "vZSZnG_boundBadge",
|
|
713
|
-
"cardMeta": "vZSZnG_cardMeta",
|
|
714
|
-
"progressText": "vZSZnG_progressText",
|
|
715
|
-
"stepContentRun": "vZSZnG_stepContentRun",
|
|
716
|
-
"headerTitle": "vZSZnG_headerTitle",
|
|
717
711
|
"editorArea": "vZSZnG_editorArea",
|
|
718
|
-
"
|
|
719
|
-
"
|
|
720
|
-
"
|
|
712
|
+
"badgeArchived": "vZSZnG_badgeArchived",
|
|
713
|
+
"stepRun": "vZSZnG_stepRun",
|
|
714
|
+
"miniBar": "vZSZnG_miniBar",
|
|
715
|
+
"stepDragging": "vZSZnG_stepDragging",
|
|
716
|
+
"memoryPane": "vZSZnG_memoryPane",
|
|
717
|
+
"dragGhostClose": "vZSZnG_dragGhostClose",
|
|
718
|
+
"cardMeta": "vZSZnG_cardMeta",
|
|
719
|
+
"stepRow": "vZSZnG_stepRow",
|
|
720
|
+
"filesList": "vZSZnG_filesList",
|
|
721
|
+
"memNewRow": "vZSZnG_memNewRow",
|
|
721
722
|
"cardBody": "vZSZnG_cardBody",
|
|
722
|
-
"
|
|
723
|
-
"
|
|
724
|
-
"track": "vZSZnG_track",
|
|
723
|
+
"stepContentDone": "vZSZnG_stepContentDone",
|
|
724
|
+
"cardTitleEditable": "vZSZnG_cardTitleEditable",
|
|
725
725
|
"dropPulse": "vZSZnG_dropPulse",
|
|
726
726
|
"kindBadge": "vZSZnG_kindBadge",
|
|
727
|
-
"
|
|
727
|
+
"headerRight": "vZSZnG_headerRight",
|
|
728
728
|
"trackFill": "vZSZnG_trackFill",
|
|
729
|
-
"
|
|
730
|
-
"headerIcon": "vZSZnG_headerIcon",
|
|
731
|
-
"headerLeft": "vZSZnG_headerLeft",
|
|
732
|
-
"notice": "vZSZnG_notice",
|
|
733
|
-
"stepDragging": "vZSZnG_stepDragging",
|
|
734
|
-
"miniText": "vZSZnG_miniText",
|
|
735
|
-
"stepPending": "vZSZnG_stepPending",
|
|
736
|
-
"miniIcon": "vZSZnG_miniIcon",
|
|
737
|
-
"fileDot": "vZSZnG_fileDot",
|
|
738
|
-
"stepEditable": "vZSZnG_stepEditable",
|
|
739
|
-
"miniBar": "vZSZnG_miniBar",
|
|
740
|
-
"memLabel": "vZSZnG_memLabel",
|
|
741
|
-
"memMeta": "vZSZnG_memMeta",
|
|
729
|
+
"progressText": "vZSZnG_progressText",
|
|
742
730
|
"panelIn": "vZSZnG_panelIn",
|
|
731
|
+
"dragGhostContent": "vZSZnG_dragGhostContent",
|
|
732
|
+
"emptyDesc": "vZSZnG_emptyDesc",
|
|
733
|
+
"chevron": "vZSZnG_chevron",
|
|
734
|
+
"track": "vZSZnG_track",
|
|
735
|
+
"sectionLabel": "vZSZnG_sectionLabel",
|
|
736
|
+
"cardFooter": "vZSZnG_cardFooter",
|
|
737
|
+
"stepDone": "vZSZnG_stepDone",
|
|
738
|
+
"section": "vZSZnG_section",
|
|
739
|
+
"dropTargetBefore": "vZSZnG_dropTargetBefore",
|
|
740
|
+
"memList": "vZSZnG_memList",
|
|
741
|
+
"cardBound": "vZSZnG_cardBound",
|
|
742
|
+
"fileRow": "vZSZnG_fileRow",
|
|
743
|
+
"handleGrip": "vZSZnG_handleGrip",
|
|
744
|
+
"counts": "vZSZnG_counts",
|
|
745
|
+
"stepsList": "vZSZnG_stepsList",
|
|
746
|
+
"dragGhost": "vZSZnG_dragGhost",
|
|
743
747
|
"emptyState": "vZSZnG_emptyState",
|
|
744
|
-
"
|
|
745
|
-
"
|
|
746
|
-
"panel": "vZSZnG_panel",
|
|
747
|
-
"stepContentDone": "vZSZnG_stepContentDone",
|
|
748
|
+
"memBody": "vZSZnG_memBody",
|
|
749
|
+
"headerLeft": "vZSZnG_headerLeft",
|
|
748
750
|
"memRow": "vZSZnG_memRow",
|
|
749
|
-
"cardBound": "vZSZnG_cardBound",
|
|
750
751
|
"badgeDraft": "vZSZnG_badgeDraft",
|
|
751
|
-
"
|
|
752
|
-
"
|
|
753
|
-
"
|
|
754
|
-
"
|
|
755
|
-
"
|
|
756
|
-
"memToolbarActs": "vZSZnG_memToolbarActs"
|
|
752
|
+
"badge": "vZSZnG_badge",
|
|
753
|
+
"headerIcon": "vZSZnG_headerIcon",
|
|
754
|
+
"taskList": "vZSZnG_taskList",
|
|
755
|
+
"memMeta": "vZSZnG_memMeta",
|
|
756
|
+
"stepInput": "vZSZnG_stepInput"
|
|
757
757
|
};
|
|
758
758
|
//#endregion
|
|
759
759
|
//#region src/client/TaskComponents.tsx
|
|
@@ -2201,8 +2201,8 @@ window.__ModuleLoader__.load({
|
|
|
2201
2201
|
document.head.appendChild(tag);
|
|
2202
2202
|
}
|
|
2203
2203
|
var TaskCommandNode_module_css_default = {
|
|
2204
|
-
"
|
|
2205
|
-
"
|
|
2204
|
+
"icon": "TPe-xa_icon",
|
|
2205
|
+
"row": "TPe-xa_row"
|
|
2206
2206
|
};
|
|
2207
2207
|
//#endregion
|
|
2208
2208
|
//#region src/client/TaskCommandNode.tsx
|
package/cordis.patch.yml
CHANGED
|
@@ -10,4 +10,10 @@
|
|
|
10
10
|
lazyIndexing: true
|
|
11
11
|
autoIndexOnFirstUse: false
|
|
12
12
|
watch: true
|
|
13
|
-
watchInterval: 15
|
|
13
|
+
watchInterval: 15
|
|
14
|
+
# 危险根护栏(家目录 / 文件系统根 / 系统与包管理器前缀整体扫描会吃满内存)。
|
|
15
|
+
# 自动路径永远不越权;true 只放开**显式**带 root 的工具调用。
|
|
16
|
+
allowUnsafeRoots: false
|
|
17
|
+
# 单次扫描上限;截断会报告,且不会删除未扫到的条目。
|
|
18
|
+
maxScanFiles: 20000
|
|
19
|
+
maxScanDepth: 12
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolk_vat-y/dsh-project-memory",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Persistent project memory for dsh agents: index docs (PDF/Markdown/text) and code symbols into a searchable per-workspace store, recall them with cited sources, and keep experience entries (problems -> solutions) searchable on demand.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
"url": "https://github.com/00080000/dsh-project-memory.git"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "node test/run-test.mjs && node test/taskbridge.test.mjs && node test/insight-store.test.mjs && node test/reflection-pipeline.test.mjs && node test/auto-inject.test.mjs && node test/insight-actions.test.mjs && node test/host-contract.test.mjs && node test/llm-route.test.mjs && node test/doc-index.test.mjs && node test/client-hints.test.mjs && node test/recall.test.mjs && node test/readiness.test.mjs && node test/insight-derive.test.mjs && node test/readiness-eval.test.mjs && node test/ops.test.mjs && node test/injection-audit.test.mjs && node test/injection-budget.test.mjs && node test/injection-scenarios.test.mjs && node test/bugfix-0.5.7.test.mjs && node test/client-icons.test.mjs && node test/client-slash.test.mjs && node test/workflow-command.test.mjs && node test/client-session-id.test.mjs",
|
|
21
|
+
"test": "node test/run-test.mjs && node test/taskbridge.test.mjs && node test/insight-store.test.mjs && node test/reflection-pipeline.test.mjs && node test/auto-inject.test.mjs && node test/insight-actions.test.mjs && node test/host-contract.test.mjs && node test/llm-route.test.mjs && node test/doc-index.test.mjs && node test/client-hints.test.mjs && node test/recall.test.mjs && node test/readiness.test.mjs && node test/insight-derive.test.mjs && node test/readiness-eval.test.mjs && node test/ops.test.mjs && node test/injection-audit.test.mjs && node test/injection-budget.test.mjs && node test/injection-scenarios.test.mjs && node test/bugfix-0.5.7.test.mjs && node test/client-icons.test.mjs && node test/client-slash.test.mjs && node test/workflow-command.test.mjs && node test/client-session-id.test.mjs && node test/task-view.test.mjs && node test/root-guards.test.mjs",
|
|
22
22
|
"eval:injection": "node test/injection-scenarios.test.mjs",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
|
|
23
24
|
"selfcheck:triggers": "node test/injection-scenarios.test.mjs --selfcheck",
|
|
24
25
|
"bench": "node scripts/bench.mjs",
|
|
25
26
|
"bench:synthetic": "node scripts/bench-synthetic.mjs",
|
|
@@ -90,7 +90,7 @@ async function coldIndex(root, config) {
|
|
|
90
90
|
const store = new ProjectMemoryStore(storeDir)
|
|
91
91
|
// 首次 load 会读盘(若已存在),这里传入一个全新实例以贴近真实冷启动
|
|
92
92
|
store.load()
|
|
93
|
-
const files = walkDir(root)
|
|
93
|
+
const { files } = walkDir(root)
|
|
94
94
|
const seen = new Set()
|
|
95
95
|
let updated = 0
|
|
96
96
|
const fileUpdates = []
|
|
@@ -234,7 +234,7 @@ async function main() {
|
|
|
234
234
|
{
|
|
235
235
|
const root = tmpd('pm-sym-')
|
|
236
236
|
generateCorpus(root, FILES)
|
|
237
|
-
const files = walkDir(root).filter((f) => f.endsWith(
|
|
237
|
+
const files = walkDir(root).files.filter((f) => f.endsWith(".js"))
|
|
238
238
|
const times = []
|
|
239
239
|
const n = Math.min(files.length, 500)
|
|
240
240
|
for (let i = 0; i < n; i++) {
|
|
@@ -256,7 +256,7 @@ async function main() {
|
|
|
256
256
|
generateCorpus(root, FILES)
|
|
257
257
|
const { store, report } = await coldIndex(root, config)
|
|
258
258
|
void report
|
|
259
|
-
const files = walkDir(root).filter((f) => f.endsWith(
|
|
259
|
+
const files = walkDir(root).files.filter((f) => f.endsWith(".js"))
|
|
260
260
|
const times = []
|
|
261
261
|
const n = Math.min(files.length, 50)
|
|
262
262
|
for (let i = 0; i < n; i++) {
|
package/scripts/bench.mjs
CHANGED
|
@@ -123,7 +123,7 @@ const kb = (n) => `${(n / 1024).toFixed(1)} KB`
|
|
|
123
123
|
const mb = (n) => `${(n / 1024 / 1024).toFixed(2)} MB`
|
|
124
124
|
|
|
125
125
|
// ---------- 1. 扫描 ----------
|
|
126
|
-
const all = walkDir(root)
|
|
126
|
+
const { files: all } = walkDir(root)
|
|
127
127
|
const targets = []
|
|
128
128
|
let skippedPdf = 0
|
|
129
129
|
for (const abs of all) {
|