dsh-prime-memory 0.22.2 → 0.24.1

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 (104) hide show
  1. package/CHANGELOG.en.md +421 -0
  2. package/CHANGELOG.ja.md +416 -0
  3. package/CHANGELOG.ko.md +416 -0
  4. package/CHANGELOG.md +120 -0
  5. package/dist/client.js +1266 -845
  6. package/dist/config.d.ts +131 -1341
  7. package/dist/config.js +89 -4
  8. package/dist/conflict-service.d.ts +7 -1
  9. package/dist/conflict-service.js +7 -1
  10. package/dist/contract.d.ts +211 -0
  11. package/dist/domain-gate.d.ts +11 -0
  12. package/dist/domain-gate.js +15 -0
  13. package/dist/hooks/obsolete-notice.d.ts +26 -0
  14. package/dist/hooks/obsolete-notice.js +64 -0
  15. package/dist/hooks/recall.d.ts +39 -0
  16. package/dist/hooks/recall.js +110 -30
  17. package/dist/index.d.ts +1 -1127
  18. package/dist/index.js +52 -2
  19. package/dist/memory-types.d.ts +69 -0
  20. package/dist/memory-types.js +165 -0
  21. package/dist/memport/format.d.ts +6 -1
  22. package/dist/memport/format.js +42 -2
  23. package/dist/pipeline/l1.d.ts +44 -2
  24. package/dist/pipeline/l1.js +163 -9
  25. package/dist/pipeline/md-sync.d.ts +65 -0
  26. package/dist/pipeline/md-sync.js +456 -0
  27. package/dist/pipeline/ruminate.d.ts +46 -1
  28. package/dist/pipeline/ruminate.js +109 -0
  29. package/dist/pipeline/runner.d.ts +10 -0
  30. package/dist/pipeline/runner.js +13 -2
  31. package/dist/pipeline/workspace-relink.d.ts +60 -0
  32. package/dist/pipeline/workspace-relink.js +73 -0
  33. package/dist/projection/recall.d.ts +51 -13
  34. package/dist/projection/recall.js +47 -12
  35. package/dist/prompts/examples.d.ts +73 -0
  36. package/dist/prompts/examples.js +178 -0
  37. package/dist/prompts/l1-dedup.d.ts +9 -3
  38. package/dist/prompts/l1-dedup.js +34 -13
  39. package/dist/prompts/l1-extraction.d.ts +5 -3
  40. package/dist/prompts/l1-extraction.js +49 -4
  41. package/dist/settings.d.ts +8 -0
  42. package/dist/settings.js +16 -1
  43. package/dist/stats.d.ts +12 -0
  44. package/dist/stats.js +352 -17
  45. package/dist/store/embedding-source.d.ts +8 -0
  46. package/dist/store/embedding-source.js +39 -2
  47. package/dist/store/file-versions.d.ts +2 -0
  48. package/dist/store/file-versions.js +2 -0
  49. package/dist/store/graph-store.d.ts +14 -0
  50. package/dist/store/graph-store.js +35 -0
  51. package/dist/store/l1-snapshot.d.ts +26 -1
  52. package/dist/store/l1-snapshot.js +53 -8
  53. package/dist/store/l1.d.ts +94 -3
  54. package/dist/store/l1.js +169 -4
  55. package/dist/store/memory-md-archive.d.ts +84 -0
  56. package/dist/store/memory-md-archive.js +155 -0
  57. package/dist/store/memory-md-disable.d.ts +74 -0
  58. package/dist/store/memory-md-disable.js +302 -0
  59. package/dist/store/memory-md-export.d.ts +50 -0
  60. package/dist/store/memory-md-export.js +227 -0
  61. package/dist/store/memory-md-ledger.d.ts +94 -0
  62. package/dist/store/memory-md-ledger.js +243 -0
  63. package/dist/store/memory-md-orphan.d.ts +27 -0
  64. package/dist/store/memory-md-orphan.js +153 -0
  65. package/dist/store/memory-md-parse.d.ts +35 -0
  66. package/dist/store/memory-md-parse.js +85 -0
  67. package/dist/store/memory-md-run.d.ts +48 -0
  68. package/dist/store/memory-md-run.js +151 -0
  69. package/dist/store/memory-md-skeleton.d.ts +18 -0
  70. package/dist/store/memory-md-skeleton.js +61 -0
  71. package/dist/store/memory-md-sync.d.ts +59 -0
  72. package/dist/store/memory-md-sync.js +152 -0
  73. package/dist/store/memory-md-types.d.ts +243 -0
  74. package/dist/store/memory-md-types.js +10 -0
  75. package/dist/store/memory-md.d.ts +117 -0
  76. package/dist/store/memory-md.js +230 -0
  77. package/dist/store/obsolete-notices.d.ts +39 -0
  78. package/dist/store/obsolete-notices.js +153 -0
  79. package/dist/store/recall-feedback.d.ts +75 -0
  80. package/dist/store/recall-feedback.js +213 -0
  81. package/dist/store/recall-usage.d.ts +11 -0
  82. package/dist/store/recall-usage.js +29 -0
  83. package/dist/store/session-recalled.d.ts +38 -3
  84. package/dist/store/session-recalled.js +156 -9
  85. package/dist/store/sqlite.d.ts +66 -0
  86. package/dist/store/sqlite.js +233 -15
  87. package/dist/store/supersede.d.ts +1 -1
  88. package/dist/store/supersede.js +1 -1
  89. package/dist/tools/index.d.ts +4 -0
  90. package/dist/tools/index.js +26 -7
  91. package/dist/tools/slots.d.ts +2 -1
  92. package/dist/tools/slots.js +2 -1
  93. package/dist/types.d.ts +24 -7
  94. package/dist/types.js +10 -2
  95. package/dist/util/maturity.d.ts +20 -3
  96. package/dist/util/maturity.js +18 -1
  97. package/dist/util/md-name.d.ts +129 -0
  98. package/dist/util/md-name.js +366 -0
  99. package/dist/util/md-normalize.d.ts +23 -0
  100. package/dist/util/md-normalize.js +131 -0
  101. package/dist/workspace-route.d.ts +22 -0
  102. package/dist/workspace-route.js +66 -0
  103. package/dsh.plugin.json +1 -1
  104. package/package.json +6 -5
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 记忆正文的 markdown 版式归一(P4.2「格式需要标准化」)。
3
+ *
4
+ * 只做**版式**,不做**语义**:不 bullet 化散文、不插 H1、不往正文里塞元数据(元数据一律在
5
+ * frontmatter)。三条理由:
6
+ * - 散文转列表会把"因为/所以/但是"这类连接关系拆散,且拆完不可逆;
7
+ * - 正文一旦带骨架,`parseMdFile` 的往返一致性(P2)就得反向剪枝,两套逻辑必然漂移;
8
+ * - 归一结果要进 `renderRecord`(导出)与回灌(入库)两头,任何有损改写都会被写进库。
9
+ *
10
+ * **幂等是硬要求**:`f(f(x)) === f(x)`。不幂等的话账本 `fileDigest` 每轮都变,反向同步
11
+ * 永远判"文件被改过",写回环打不完——那正是 P2 用例钉死的那条性质。
12
+ *
13
+ * 刻意不用正则与转义写法:同仓产物里出现过 `\n` 被展开成真实换行、把正则字面量截断的事,
14
+ * 所以这里全部走字符码与字符串切片,形状只依赖文件真实结构。
15
+ */
16
+ const NL = String.fromCharCode(10);
17
+ const TAB = String.fromCharCode(9);
18
+ const FENCE_BACKTICK = String.fromCharCode(96, 96, 96);
19
+ const FENCE_TILDE = String.fromCharCode(126, 126, 126);
20
+ /** 无序列表的等价符号:统一成 `- `(有序列表**不动**,`1. 2. 3.` 自带语义顺序)。 */
21
+ const BULLETS = ['*', '+', '·', '•'];
22
+ /** 行首缩进宽度(只数空格;tab 缩进按 1 计,不做宽度换算——换算就不是幂等的了)。 */
23
+ function indentWidth(line) {
24
+ let i = 0;
25
+ while (i < line.length && (line.charAt(i) === ' ' || line.charAt(i) === TAB))
26
+ i += 1;
27
+ return i;
28
+ }
29
+ /** 行尾收口:尾部空白里空格 ≥2 视为 markdown 硬换行,压成恰好两个;单个尾随空格与所有尾随 tab 去掉。 */
30
+ function settleEol(line) {
31
+ let i = line.length;
32
+ while (i > 0 && (line.charAt(i - 1) === ' ' || line.charAt(i - 1) === TAB))
33
+ i -= 1;
34
+ const core = line.slice(0, i);
35
+ const trail = line.slice(i);
36
+ let spaces = 0;
37
+ for (const c of trail)
38
+ if (c === ' ')
39
+ spaces += 1;
40
+ return spaces >= 2 ? `${core} ` : core;
41
+ }
42
+ /** 无序列表符号归一(只认"符号 + 一个空格"的规范写法,避免把 `*` 强调语法当列表改了)。 */
43
+ function settleBullet(line) {
44
+ const w = indentWidth(line);
45
+ const head = line.slice(0, w);
46
+ const rest = line.slice(w);
47
+ for (const b of BULLETS) {
48
+ if (rest.startsWith(`${b} `) || rest.startsWith(b + TAB))
49
+ return `${head}-${rest.slice(1)}`;
50
+ }
51
+ return line;
52
+ }
53
+ /** 标题行的前导空白去掉(` # 标题` → `# 标题`);列表项内的 `#` 不算标题(那多半是内容)。 */
54
+ function settleHeading(line) {
55
+ if (indentWidth(line) === 0)
56
+ return line;
57
+ const rest = line.slice(indentWidth(line));
58
+ return rest.startsWith('#') ? rest : line;
59
+ }
60
+ /**
61
+ * 归一一段记忆正文。
62
+ *
63
+ * @param text - 原始正文(可含 CRLF、BOM、任意尾随空白)。
64
+ * @returns 版式归一后的正文:**无**前导/尾随空行、段落间恰好一个空行、围栏内(含 delimiter)
65
+ * 逐字保留、无末尾换行(渲染器自己控制)。
66
+ */
67
+ export function normalizeContentMd(text) {
68
+ // ① 换行统一:BOM 去掉,CRLF/CR → LF。这一步之后再按 LF 切。
69
+ let src = text;
70
+ // BOM
71
+ if (src.charCodeAt(0) === 0xfeff)
72
+ src = src.slice(1);
73
+ // CRLF → LF(逐字符扫,避开转义写法)
74
+ let flat = '';
75
+ for (let i = 0; i < src.length; i += 1) {
76
+ const c = src.charAt(i);
77
+ if (c === String.fromCharCode(13)) {
78
+ // CR 无论后面是不是 LF 都必须落一个 LF:只进 `i += 1` 不写 `flat` 会把换行直接吞掉
79
+ // (自家幂等用例抓到的第一个真 bug:`甲\r\n乙` 变成 `甲乙`,段落边界就这么没了)。
80
+ if (src.charAt(i + 1) === NL)
81
+ i += 1; // CR LF → 吃掉 CR,只留下面那个 LF
82
+ flat += NL;
83
+ continue;
84
+ }
85
+ flat += c;
86
+ }
87
+ const lines = flat.split(NL);
88
+ const out = [];
89
+ let inFence = false;
90
+ let fenceMark = '';
91
+ let blanks = 0;
92
+ for (const raw of lines) {
93
+ const pre = raw.slice(indentWidth(raw));
94
+ const isFence = pre.startsWith(FENCE_BACKTICK) || pre.startsWith(FENCE_TILDE);
95
+ if (isFence) {
96
+ const mark = pre.startsWith(FENCE_BACKTICK) ? FENCE_BACKTICK : FENCE_TILDE;
97
+ if (!inFence) {
98
+ inFence = true;
99
+ fenceMark = mark;
100
+ }
101
+ else if (mark === fenceMark) {
102
+ inFence = false;
103
+ fenceMark = '';
104
+ }
105
+ // 围栏 delimiter 行就是版式的一部分:原样落盘,连行尾收口也不做
106
+ blanks = 0;
107
+ out.push(raw);
108
+ continue;
109
+ }
110
+ if (inFence) {
111
+ // 围栏内的行逐字保留:代码里的缩进、`*` 号、行尾双空格都可能是有意为之
112
+ blanks = 0;
113
+ out.push(raw);
114
+ continue;
115
+ }
116
+ let line = settleBullet(settleHeading(settleEol(raw)));
117
+ if (line.trim().length === 0) {
118
+ blanks += 1;
119
+ // 空行只在已有内容时才可能保留,且最多一个
120
+ if (out.length > 0 && blanks === 1)
121
+ out.push('');
122
+ continue;
123
+ }
124
+ blanks = 0;
125
+ out.push(line);
126
+ }
127
+ // ③ 末尾无换行:渲染器负责补,正文本体不带(否则 digest 会随写法抖动)。
128
+ while (out.length > 0 && out[out.length - 1] === '')
129
+ out.pop();
130
+ return out.join(NL);
131
+ }
@@ -0,0 +1,22 @@
1
+ /** 一条路由规则。 */
2
+ export interface WorkspaceRoute {
3
+ /** 命中后归到的 workspace(绝对路径;返回值已归一)。 */
4
+ path: string;
5
+ /** 命中任一关键词即归此路(大小写不敏感、已 NFKC 归一的子串匹配)。 */
6
+ anyOf: readonly string[];
7
+ /** 出现任一排除词则**此路**不命中(继续往下看下一条规则)。 */
8
+ noneOf?: readonly string[];
9
+ }
10
+ /**
11
+ * 按序取首个命中;全不命中返回 `undefined`(**宁缺勿猜**——归错 workspace 会让记忆在
12
+ * 错误的工作区可见/不可见,比不归更难查)。
13
+ *
14
+ * 排序即优先级:调用方给的表里 dsh 侧在前,故"同一段内容既谈 Hanako 又谈 dsh"会归 dsh
15
+ * 一侧(用户口径);非 dsh 那条同时带 `noneOf: ['dsh']` 做二次保险。
16
+ */
17
+ export declare function routeWorkspace(text: string, routes: readonly WorkspaceRoute[]): string | undefined;
18
+ /**
19
+ * 路由结论与来源链结论**矛盾**时返回 true(来源链仅作交叉验证)。
20
+ * 单独抽出是为了把"冲突要可观测"这条判据变成可测函数,而不是散在调用点的 if 里。
21
+ */
22
+ export declare function routeConflictsWithProvenance(routed: string | undefined, provenance: string | undefined): boolean;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 记忆的 **内容路由**(workspace 归属):按「这条记忆谈到哪个项目」给出归属路径。
3
+ *
4
+ * ## 为什么不是「来源会话反查」
5
+ * 直觉做法是顺着 L1 → 来源会话 → 会话的工作目录往回推。实测三处都断:
6
+ * - `l1_records.session_id` 恒为 `'default'`(蒸馏写入时用的是后台自己的会话标识,
7
+ * 与来源对话无关);
8
+ * - `metadata.dsh_source_anchors` 里确实带 `sessionId`,但锚点覆盖率仅 69%,且其中
9
+ * 能在磁盘会话树(`~/.dsh/sessions/<slug>/<uuid>`)找到目录的只有 **7.3%**——
10
+ * 会话树会被清理/隔离,不能当持久事实源;
11
+ * - L1 的 `timestamp_str` 与 L0 的 `recorded_at` / `timestamp` 两列**零命中**
12
+ * (L1 时间戳来自抽取回显,不是消息元数据)。
13
+ *
14
+ * 内容路由不受这些缺失影响:**内容自己说它属于哪个项目**。这也与人的归类习惯一致
15
+ * (谈 dsh 生态的收到 dsh 项目下,谈 Hanako/inferglow 且与 dsh 无关的收到另一个项目)。
16
+ *
17
+ * ## 只写归属列,不动可见性
18
+ * 返回值只用于填 `l1_records.workspace_id`;**`scope` 一律不改**。
19
+ * 理由:`resolveRecordScope` 规则③ 刻意在 `cfg.scope='global'` 时不写归属,以保证既有
20
+ * 部署零漂移;而检索可见性判据是 `scope` + `workspace_id` 的组合(`isScopeVisible`),
21
+ * 单改 `workspace_id` 在 `scope='global'` 下不产生任何过滤差异。
22
+ * 所以本路由是「**先把事实存下来**」:将来真启用 workspace 围栏时它是现成的正确依据,
23
+ * 今天则一行行为都不改变。
24
+ */
25
+ import { normalizeWorkspacePath } from './workspace.js';
26
+ /** 归一:NFKC + 小写 + 空白折叠(与 `memport/format.normText` 同口径,不另造一套)。 */
27
+ function norm(s) {
28
+ return s.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
29
+ }
30
+ /**
31
+ * 按序取首个命中;全不命中返回 `undefined`(**宁缺勿猜**——归错 workspace 会让记忆在
32
+ * 错误的工作区可见/不可见,比不归更难查)。
33
+ *
34
+ * 排序即优先级:调用方给的表里 dsh 侧在前,故"同一段内容既谈 Hanako 又谈 dsh"会归 dsh
35
+ * 一侧(用户口径);非 dsh 那条同时带 `noneOf: ['dsh']` 做二次保险。
36
+ */
37
+ export function routeWorkspace(text, routes) {
38
+ const hay = norm(text);
39
+ if (hay.length === 0)
40
+ return undefined;
41
+ for (const route of routes) {
42
+ const target = typeof route.path === 'string' ? route.path.trim() : '';
43
+ if (target.length === 0)
44
+ continue;
45
+ const excluded = (route.noneOf ?? []).some((word) => word.length > 0 && hay.includes(norm(word)));
46
+ if (excluded)
47
+ continue;
48
+ const hit = (route.anyOf ?? []).some((word) => word.length > 0 && hay.includes(norm(word)));
49
+ if (!hit)
50
+ continue;
51
+ // 归一失败(空串/非法路径)时**不写**:路由表写错不该污染归属列。
52
+ const normalized = normalizeWorkspacePath(target);
53
+ if (normalized)
54
+ return normalized;
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * 路由结论与来源链结论**矛盾**时返回 true(来源链仅作交叉验证)。
60
+ * 单独抽出是为了把"冲突要可观测"这条判据变成可测函数,而不是散在调用点的 if 里。
61
+ */
62
+ export function routeConflictsWithProvenance(routed, provenance) {
63
+ if (!routed || !provenance)
64
+ return false;
65
+ return normalizeWorkspacePath(routed) !== normalizeWorkspacePath(provenance);
66
+ }
package/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dsh-prime-memory",
3
3
  "name": "dsh-prime-memory",
4
- "version": "0.22.2",
4
+ "version": "0.24.1",
5
5
  "description": {
6
6
  "en": "Layered distilled memory plugin (L0 capture → L1 atomic memory → L2 scenes → L3 persona) with automatic recall injection before each model step.",
7
7
  "zh": "L0~L3 分层蒸馏记忆插件:自动捕获对话、抽取原子记忆、整合场景块、蒸馏核心画像,并在模型步骤前自动召回注入。"
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "dsh-prime-memory",
3
- "version": "0.22.2",
3
+ "version": "0.24.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",
7
- "tag": "dsh-0.2.0"
7
+ "tag": "latest"
8
8
  },
9
9
  "description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
10
10
  "type": "module",
@@ -77,6 +77,7 @@
77
77
  "author": "drscrewdriver <597093681@qq.com>",
78
78
  "license": "MIT",
79
79
  "dependencies": {
80
+ "@deepseek-ai/schemastery": "^3.18.4",
80
81
  "@node-rs/jieba": "^2.0.2",
81
82
  "sqlite-vec": "^0.1.7-alpha.2",
82
83
  "undici": "^7.29.0"
@@ -125,16 +126,17 @@
125
126
  "devDependencies": {
126
127
  "@deepseek-ai/cordis": "^4.0.4",
127
128
  "@deepseek-ai/cordis-plugin-loader": "^1.0.5",
129
+ "@deepseek-ai/cosmokit": "1.8.5",
128
130
  "@deepseek-ai/dsh-agent": "0.2.0-rc.1",
129
131
  "@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.1",
130
132
  "@deepseek-ai/dsh-client-connection": "0.2.0-rc.1",
133
+ "@deepseek-ai/dsh-compaction": "0.2.0-rc.1",
131
134
  "@deepseek-ai/dsh-home-paths": "0.2.0-rc.1",
132
135
  "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
133
136
  "@deepseek-ai/dsh-session": "0.2.0-rc.1",
134
137
  "@deepseek-ai/dsh-settings": "0.2.0-rc.1",
135
138
  "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
136
139
  "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
137
- "@deepseek-ai/schemastery": "^3.18.4",
138
140
  "@eslint/js": "^10.0.1",
139
141
  "@types/node": "^22.0.0",
140
142
  "@types/react": "^19.2.18",
@@ -143,7 +145,6 @@
143
145
  "globals": "^17.12.0",
144
146
  "typescript": "^5.6.0",
145
147
  "typescript-eslint": "^8.69.0",
146
- "vitest": "^4.1.11",
147
- "@deepseek-ai/dsh-compaction": "0.2.0-rc.1"
148
+ "vitest": "^4.1.11"
148
149
  }
149
150
  }