@max-null/dsh-memory 0.9.2 → 0.11.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 +25 -2
- package/dist/engine.d.ts +112 -0
- package/dist/engine.js +334 -96
- package/dist/index.js +48 -1
- package/dist/injection.d.ts +30 -0
- package/dist/injection.js +56 -1
- package/dist/self.js +4 -1
- package/package.json +5 -2
- package/scripts/ancestor-probe.mjs +415 -0
- package/scripts/scan-orphans.mjs +110 -0
- package/scripts/unzstd-frames.mjs +90 -0
package/README.md
CHANGED
|
@@ -39,8 +39,8 @@ npm install @max-null/dsh-memory
|
|
|
39
39
|
|
|
40
40
|
## 提供的服务与工具
|
|
41
41
|
|
|
42
|
-
- **服务** `ctx.memory`:`remember` / `list` / `search` / `forget` / `setStatus`
|
|
43
|
-
- **工具**:`memory_save`、`memory_list`、`memory_search`、`memory_confirm`、`memory_forget`、`memory_update`
|
|
42
|
+
- **服务** `ctx.memory`:`remember` / `list` / `search` / `forget` / `setStatus` / `move`
|
|
43
|
+
- **工具**:`memory_save`、`memory_list`、`memory_search`、`memory_confirm`、`memory_forget`、`memory_update`、`memory_move`
|
|
44
44
|
- **注入**:`tool:memory` 指引 section(工具用法 + 常驻注入判据)+ `memory:self` 机制自述 + `memory:recall` 召回 context(global 的 `approved + injected` + 当前会话工作区的 `approved + injected`,带 `[memory:<id>:<namespace>]` 来源标记;摘要化 + 预算截断)
|
|
45
45
|
- **检索**:BM25(CJK 单字 + 2-gram,content 与 keywords 字段分离加权;中文多字查询精度显著优于单字切分);可选语义融合(见「可选配置」)
|
|
46
46
|
|
|
@@ -55,6 +55,26 @@ npm install @max-null/dsh-memory
|
|
|
55
55
|
|
|
56
56
|
两个根都可用 config 覆盖(`globalRoot` / `projectRoot`)。`memory_list` / `memory_search` 不带 `namespace` 过滤时会同时查两层。旧版双重前缀文件名(`memory_project_memory_project_<hash>.json`)在打开时自动迁移为规范名。
|
|
57
57
|
|
|
58
|
+
## 跨工作区可见性(0.10.0 / 0.11.0)
|
|
59
|
+
|
|
60
|
+
project 记忆按**会话工作区**分文件存放,所以「工作区」就是可见性的边界。0.10.0 起补了三个方向——针对的是同一个盲区:**不知道存在**(检索是有意图的动作,搜不出自己不知道存在的东西)。
|
|
61
|
+
|
|
62
|
+
| 方向 | 机制 | 效果 |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| 向上 | **祖先链** | 在子目录里开会话能检索到父目录的记忆(反向不行,链是单向向上的),结果带 `..` / `../..` 来源标记 |
|
|
65
|
+
| 向下 | **子项目索引行** | 注入里多一行「本工作区下另有 N 个子项目带记忆:xxx(M 条)——用 memory_search 检索」 |
|
|
66
|
+
| 全局 | **记忆索引行** | 注入里多一行「索引:当前可见 N 条记忆,主题集中在 X(14)、Y(12)…——用 memory_search 检索」 |
|
|
67
|
+
|
|
68
|
+
三条边界:**写入永远只落当前工作区**(「我在这个项目里记的东西」不该被推理到别处);但**检索得到的记录就改得动**——`memory_update` / `memory_forget` 同样沿链定位;**注入路径只读当前工作区**(远处的记忆进检索、不进每轮成本)。
|
|
69
|
+
|
|
70
|
+
**0.11.0 起,「放错了层」有了显式的修正通道**:`memory_move(id, to)` 把一条记忆移到 `global`、`self`(当前工作区)或相对祖先(`..` / `../..`)。它是**例外动作**——默认写入仍然只落当前工作区,只有当你判定「这条其实属于上一层、或上一层的那一支」时才动它。移动**保持 id**、**从源层删除**(不是复制),并报告移动后源层是否仍看得见它:向上移动源层不失明(继承只向上,`global` 人人可见),向下移动则会让源层及其下层从此看不见它。典型用途是**把放高了的记忆降回它真正属于的工作区**——比如只属于某一个生态的规矩躺在 `global` 里、漏进了无关项目。判据与边界见设计文档 §3.2。
|
|
71
|
+
|
|
72
|
+
后两行**不占注入预算**、自消除(没有内容时整行不出现),且**长度常数级**——不随记忆增长。索引行里的主题取自各条的关键词词频,与检索用的是同一套词汇,所以**索引里出现的词就是能搜到的词**。
|
|
73
|
+
|
|
74
|
+
**孤儿文件清点**:`node scripts/scan-orphans.mjs [根目录]` 报出「哪些记忆文件当前代码已经打不开」(历史命名遗留:早期无哈希后缀、双重 `memory_project_` 前缀)。**只读,不做任何处置**——旧文件可能含未过隔离检查的内容,处置需人工拍板。
|
|
75
|
+
|
|
76
|
+
**祖先链诊断与探针**:`node scripts/ancestor-probe.mjs plan [--cwd <会话 cwd>]` 只读地复算该 cwd 的祖先链(哪些层级带记忆文件、引擎会纳入几级),并回答「这个 cwd 上祖先链是否**可观测**」——若以上祖先层都没有记忆文件,祖先链生效与否行为逐字相同,那是个零差异的观测点。要实测检索侧跨链,用 `seed` / `check` / `clean` 在祖先层造一条临时探针再撤销。**前提**:探针只对**没打开过该层**的会话有效——引擎的表打开即缓存,而写路径的新鲜度门(`refreshForWrite`)只比对 global 与当前 cwd,**不含祖先层**,被缓存过的层会用陈旧内存态覆盖掉外部写入。
|
|
77
|
+
|
|
58
78
|
## 多实例共存(0.7.1)
|
|
59
79
|
|
|
60
80
|
两个 DSH 实例(例如 DSH web 与 SSiD 桌面壳)可以同时运行、共用同一个 `DSH_HOME`,**记忆不再互相抹掉**。
|
|
@@ -127,6 +147,9 @@ npm run typecheck # tsc 严格类型检查
|
|
|
127
147
|
npm test # vitest 单测
|
|
128
148
|
npm run build # 产出 dist/
|
|
129
149
|
node scripts/verify-loader.mjs # 用 Loader 端到端验证插件可加载
|
|
150
|
+
node scripts/scan-orphans.mjs [根目录] # 清点「当前代码打不开」的记忆文件(只读)
|
|
151
|
+
node scripts/unzstd-frames.mjs <源> <目标> # 解多帧 zstd(读 DSH 会话日志用)
|
|
152
|
+
node scripts/ancestor-probe.mjs plan [--cwd <cwd>] # 复算祖先链(只读);seed/check/clean 验 ④-A
|
|
130
153
|
```
|
|
131
154
|
|
|
132
155
|
## 依赖(peerDependencies,由宿主提供)
|
package/dist/engine.d.ts
CHANGED
|
@@ -82,6 +82,11 @@ export interface MemoryRecord {
|
|
|
82
82
|
kind?: MemoryKind;
|
|
83
83
|
/** prompt 记录元数据(仅 kind==='prompt')。 */
|
|
84
84
|
meta?: PromptMetaIndex;
|
|
85
|
+
/**
|
|
86
|
+
* 来源工作区(④-A 2026-09-19):仅在跨工作区召回时出现——`undefined` = 当前会话
|
|
87
|
+
* 工作区,`..` / `../..` = 上级工作区。检索结果与注入行据此标出来源,避免「记忆串味」。
|
|
88
|
+
*/
|
|
89
|
+
scope?: string;
|
|
85
90
|
}
|
|
86
91
|
export interface MemoryWrite {
|
|
87
92
|
content: string;
|
|
@@ -173,6 +178,11 @@ export type MemoryChange = {
|
|
|
173
178
|
operation: 'quarantined';
|
|
174
179
|
id: MemoryId;
|
|
175
180
|
quarantined: boolean;
|
|
181
|
+
} | {
|
|
182
|
+
operation: 'moved';
|
|
183
|
+
id: MemoryId;
|
|
184
|
+
from: string;
|
|
185
|
+
to: string;
|
|
176
186
|
};
|
|
177
187
|
declare module '@deepseek-ai/cordis' {
|
|
178
188
|
interface Context {
|
|
@@ -234,6 +244,10 @@ export declare class MemoryEngine extends Service {
|
|
|
234
244
|
private globalTable?;
|
|
235
245
|
/** project 域按工作区 cwd 懒打开 + 缓存(多会话并发各工作区独立)。 */
|
|
236
246
|
private projectTables;
|
|
247
|
+
/** 子项目发现缓存(④-B 2026-09-19):key = 工作区路径;值 = 其直接子目录中带记忆的(按条数降序)。 */
|
|
248
|
+
private readonly neighborCache;
|
|
249
|
+
/** 进行中的子项目发现(key → promise):预热是 fire-and-forget,之后的显式调用与它合流而非重复扫描。 */
|
|
250
|
+
private readonly neighborOpenings;
|
|
237
251
|
/** 进行中的工作区打开(key → promise):并发调用复用同一次,见 {@link projectTableFor}。 */
|
|
238
252
|
private readonly projectOpenings;
|
|
239
253
|
private projectFacilities;
|
|
@@ -295,6 +309,75 @@ export declare class MemoryEngine extends Service {
|
|
|
295
309
|
private globalStoreFile;
|
|
296
310
|
/** 某工作区记忆的存储文件。 */
|
|
297
311
|
private projectStoreFile;
|
|
312
|
+
/**
|
|
313
|
+
* 工作区链(④-A 2026-09-19):当前 cwd 起向上,收集**记忆文件已存在**的层级。
|
|
314
|
+
*
|
|
315
|
+
* 两条约束:
|
|
316
|
+
* ① **自身始终纳入**(depth 0)——即使文件还不存在,也保持既有行为(`projectTableFor`
|
|
317
|
+
* 会创建它,会话预热路径同样如此)。
|
|
318
|
+
* ② **祖先只收文件已存在的**——对不存在的层级打开表会让 JsonStorageBackend **创建**空
|
|
319
|
+
* 文件,等于在用户每个祖先目录里留一个空记忆文件。
|
|
320
|
+
*
|
|
321
|
+
* `depth` 是真实的上级层数(每向上一步 +1,无论该级是否有文件),供来源标记渲染成
|
|
322
|
+
* `..` / `../..`。路径一律先 `join()` 规范化再算哈希——`projectBackendName` 对字符串
|
|
323
|
+
* 敏感,`H:\a\b` 与 `H:/a/b` 会得出**不同**的文件名(设计文档 §2.3)。
|
|
324
|
+
*/
|
|
325
|
+
private projectChain;
|
|
326
|
+
/**
|
|
327
|
+
* 打开工作区链上的表(由近及远)。复用 {@link projectTableFor} 的缓存与并发去重——
|
|
328
|
+
* 链上每级各自一张表,重复调用不会重复打开。
|
|
329
|
+
*/
|
|
330
|
+
private projectChainTables;
|
|
331
|
+
/** 来源标记:depth 0(当前工作区)不带标记;祖先渲染为 `..` / `../..`。 */
|
|
332
|
+
private static scopeLabel;
|
|
333
|
+
/**
|
|
334
|
+
* 子项目发现(④-B 2026-09-19):扫描 cwd 的**直接子目录**(不递归——实测一层即覆盖全部
|
|
335
|
+
* 实际情况:19 个子目录里 3 个带记忆),找出带记忆文件、且其中有非隔离记录的。
|
|
336
|
+
*
|
|
337
|
+
* 结果缓存进 {@link neighborCache}——注入 provider 是同步的,只能读缓存;扫描本身在这个
|
|
338
|
+
* 异步方法里做,由 `ensureProjectOpen` 预热路径触发。
|
|
339
|
+
*
|
|
340
|
+
* 跳过点开头的目录(`.dsh` / `.git` 等不是子项目);只报条数、不搬运内容——索引行的用途
|
|
341
|
+
* 是「告诉你去搜」,不是把子项目的记忆变成当前会话的持续成本。
|
|
342
|
+
*/
|
|
343
|
+
discoverNeighbors(projectCwd: string): Promise<Array<{
|
|
344
|
+
name: string;
|
|
345
|
+
count: number;
|
|
346
|
+
}>>;
|
|
347
|
+
/** {@link discoverNeighbors} 的实际扫描;并发去重由调用方负责。 */
|
|
348
|
+
private scanNeighbors;
|
|
349
|
+
/**
|
|
350
|
+
* 同步读取已发现的子项目(④-B):注入 provider 是同步回调,只能读缓存。
|
|
351
|
+
* 缓存由 {@link discoverNeighbors} 在预热路径填充;未跑过时返回空数组——该轮索引行
|
|
352
|
+
* 缺席、下一轮补上,与工作区表预热同一套取舍。
|
|
353
|
+
*/
|
|
354
|
+
neighborsOf(projectCwd?: string): Array<{
|
|
355
|
+
name: string;
|
|
356
|
+
count: number;
|
|
357
|
+
}>;
|
|
358
|
+
/**
|
|
359
|
+
* 同步读取索引候选(① 2026-09-19):global + 工作区链上**已打开**的表里的全部记录。
|
|
360
|
+
*
|
|
361
|
+
* 与 {@link recallRecords} 的分工:后者只取 `injected` 的(进注入行),前者取全部
|
|
362
|
+
* (供索引行统计规模与主题)。同样是同步只读已缓存的表——注入 provider 是同步回调,
|
|
363
|
+
* 未打开的层级本轮不计入,影响可忽略(链上祖先在检索路径打开后即缓存)。
|
|
364
|
+
*/
|
|
365
|
+
indexCandidates(projectCwd?: string): MemoryRecord[];
|
|
366
|
+
/**
|
|
367
|
+
* 按 id 定位记录所在表:global 优先,其次工作区链(自身 → 祖先,由近及远)。
|
|
368
|
+
* **异步版**——写路径必须用它:`writeScope` 可能在本次进入前刚 `reload()`,
|
|
369
|
+
* 那时同步缓存已被清空(同 {@link markUsedInner} 的注释)。
|
|
370
|
+
*/
|
|
371
|
+
private locateRecord;
|
|
372
|
+
/**
|
|
373
|
+
* 写入协调覆盖的存储文件:global + **全部已打开的工作区表** + 本次 cwd。
|
|
374
|
+
*
|
|
375
|
+
* 记录({@link noteStoreStamps})与比对({@link refreshForWrite})必须共用这一个定义——
|
|
376
|
+
* 两边各写一份正是漂移的来源:在此之前比对侧只列 global + cwd,而 ④-A 把**读**路径扩到了
|
|
377
|
+
* 祖先链(`projectChain`),于是被缓存过的祖先层「指纹记下了却从不被检查」——外部改动看不见,
|
|
378
|
+
* 下一次写回把它**整份覆盖**。**读扩到哪,门就得跟着扩到哪。**
|
|
379
|
+
*/
|
|
380
|
+
private storeScopeFiles;
|
|
298
381
|
/** 记录当前指纹,作为「这就是我们造成的状态」的基准(写入完成后调用)。 */
|
|
299
382
|
private noteStoreStamps;
|
|
300
383
|
/**
|
|
@@ -344,6 +427,7 @@ export declare class MemoryEngine extends Service {
|
|
|
344
427
|
* 「工具全没了」,让所有 `tool-list` 锚点误失效。
|
|
345
428
|
*/
|
|
346
429
|
private anchorProbes;
|
|
430
|
+
/** 同步版定位:只查**已打开**的表(global + 链上已缓存的工作区表)。 */
|
|
347
431
|
private tableOf;
|
|
348
432
|
/** 命中标记:写回 lastUsedAt 与命中次数,达标即自动升常驻(2026-09-15)。 */
|
|
349
433
|
private markUsed;
|
|
@@ -438,6 +522,34 @@ export declare class MemoryEngine extends Service {
|
|
|
438
522
|
forget(id: MemoryId, projectCwd?: string): Promise<boolean>;
|
|
439
523
|
/** {@link forget} 的实现体;必须经 `writeScope` 进入。 */
|
|
440
524
|
private forgetInner;
|
|
525
|
+
/**
|
|
526
|
+
* 把一条记忆移到另一层(迭代设计 §3.2)。**两个方向共用一个操作**:向上提到祖先层或
|
|
527
|
+
* `global`,向下把误放高层的降回它真正属于的工作区。
|
|
528
|
+
*
|
|
529
|
+
* 原设计(`memory_promote`)只允许向上,理由是「往下或横向会让原本能看见的人失明」。
|
|
530
|
+
* 但**移动的本意就是改变谁能看见**——那不是副作用,是目的。所以约束从「禁止方向」
|
|
531
|
+
* 换成「让后果可见」:返回值里的 `sourceStillSees` 就是那个后果。
|
|
532
|
+
*/
|
|
533
|
+
move(id: MemoryId, to: string, projectCwd?: string): Promise<{
|
|
534
|
+
id: MemoryId;
|
|
535
|
+
from: string;
|
|
536
|
+
to: string;
|
|
537
|
+
sourceStillSees: boolean;
|
|
538
|
+
}>;
|
|
539
|
+
/** {@link move} 的实现体;必须经 `writeScope` 进入。 */
|
|
540
|
+
private moveInner;
|
|
541
|
+
/** 按 id 定位记录**并带上它所在的层**(`locateRecord` 只返回表,而移动必须知道来源层)。 */
|
|
542
|
+
private locateWithKey;
|
|
543
|
+
/**
|
|
544
|
+
* 解析 `memory_move` 的目标:`global`、`self` / `.`(当前 cwd),或相对祖先(`..`、`../..`)。
|
|
545
|
+
*
|
|
546
|
+
* 用**相对深度**而不是绝对路径,是为了和来源标记 `scope`(`..` / `../..`)说同一套话。
|
|
547
|
+
* 允许落在**还没有记忆文件**的祖先层——移动是显式动作,在那儿创建文件正是目的,
|
|
548
|
+
* 不是 `projectChain` 要避免的那种副作用。
|
|
549
|
+
*/
|
|
550
|
+
private static resolveTarget;
|
|
551
|
+
/** `ancestor` 是否就是 `node` 本身或它的祖先——用来判断移动后源层是否仍看得见。 */
|
|
552
|
+
private static isAncestorOrSelf;
|
|
441
553
|
setStatus(id: MemoryId, status: MemoryStatus, projectCwd?: string): Promise<MemoryRecord>;
|
|
442
554
|
/** {@link setStatus} 的实现体;必须经 `writeScope` 进入。 */
|
|
443
555
|
private setStatusInner;
|