@modusensus/dsh-mneme 0.8.16 → 0.9.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 (135) hide show
  1. package/README.md +91 -16
  2. package/assets/icon.svg +2 -2
  3. package/bin/cli.mjs +8 -2
  4. package/docs/CONFIGURATION.md +27 -1
  5. package/docs/CONTINUITY.md +18 -3
  6. package/docs/SEMANTIC.md +3 -3
  7. package/docs/WORKFLOWS.md +299 -0
  8. package/lib/api-standalone.js +78 -12
  9. package/lib/api.js +403 -62
  10. package/lib/candidates.js +454 -0
  11. package/lib/capabilities.js +819 -0
  12. package/lib/client.js +2294 -423
  13. package/lib/config.js +63 -6
  14. package/lib/continuity.js +35 -6
  15. package/lib/dream/decisions.js +17 -1
  16. package/lib/dream/sleep.js +10 -2
  17. package/lib/dream.js +5 -1
  18. package/lib/embedding.js +116 -15
  19. package/lib/entities/extractor.js +4 -0
  20. package/lib/index.js +81 -12
  21. package/lib/inject.js +40 -4
  22. package/lib/lang.js +124 -0
  23. package/lib/pipeline-activities.js +178 -0
  24. package/lib/pipeline-attribution.js +165 -0
  25. package/lib/pipeline-command.js +279 -0
  26. package/lib/pipeline-confirm.js +142 -0
  27. package/lib/pipeline-exec.js +174 -0
  28. package/lib/pipeline-nudge.js +1281 -0
  29. package/lib/pipeline-observations.js +181 -0
  30. package/lib/pipeline-observe.js +133 -0
  31. package/lib/pipeline-runner.js +346 -0
  32. package/lib/pipeline-runs.js +442 -0
  33. package/lib/pipeline.js +655 -0
  34. package/lib/scope.js +16 -4
  35. package/lib/semantic.js +66 -10
  36. package/lib/serve.js +1 -1
  37. package/lib/service.js +59 -18
  38. package/lib/session-usage.js +350 -0
  39. package/lib/settings.js +138 -1
  40. package/lib/store.js +132 -6
  41. package/lib/summarize.js +30 -0
  42. package/lib/tools.js +11 -2
  43. package/package.json +3 -3
  44. package/scripts/changelog-prep.mjs +62 -7
  45. package/scripts/check-sync.js +8 -6
  46. package/scripts/publish-gate.mjs +197 -0
  47. package/scripts/release-docs.mjs +204 -0
  48. package/scripts/release-notes.mjs +162 -0
  49. package/src/api-standalone.js +78 -12
  50. package/src/api.js +403 -62
  51. package/src/candidates.js +454 -0
  52. package/src/capabilities.js +819 -0
  53. package/src/config.js +63 -6
  54. package/src/continuity.js +35 -6
  55. package/src/dream/decisions.js +17 -1
  56. package/src/dream/sleep.js +10 -2
  57. package/src/dream.js +5 -1
  58. package/src/embedding.js +116 -15
  59. package/src/entities/extractor.js +4 -0
  60. package/src/index.js +81 -12
  61. package/src/inject.js +40 -4
  62. package/src/lang.js +124 -0
  63. package/src/pipeline-activities.js +178 -0
  64. package/src/pipeline-attribution.js +165 -0
  65. package/src/pipeline-command.js +279 -0
  66. package/src/pipeline-confirm.js +142 -0
  67. package/src/pipeline-exec.js +174 -0
  68. package/src/pipeline-nudge.js +1281 -0
  69. package/src/pipeline-observations.js +181 -0
  70. package/src/pipeline-observe.js +133 -0
  71. package/src/pipeline-runner.js +346 -0
  72. package/src/pipeline-runs.js +442 -0
  73. package/src/pipeline.js +655 -0
  74. package/src/scope.js +16 -4
  75. package/src/semantic.js +66 -10
  76. package/src/serve.js +1 -1
  77. package/src/service.js +59 -18
  78. package/src/session-usage.js +350 -0
  79. package/src/settings.js +138 -1
  80. package/src/store.js +132 -6
  81. package/src/summarize.js +30 -0
  82. package/src/tools.js +11 -2
  83. package/test/api-hardening.test.js +173 -0
  84. package/test/api.test.js +244 -7
  85. package/test/candidates.test.js +345 -0
  86. package/test/capabilities.test.js +577 -0
  87. package/test/changelog-prep.test.js +74 -8
  88. package/test/cli-enums.test.js +37 -0
  89. package/test/client.test.js +510 -2
  90. package/test/continuity.test.js +84 -0
  91. package/test/docs-config-keys.test.js +65 -0
  92. package/test/embed-ready-poll.test.js +94 -0
  93. package/test/entities.test.js +13 -2
  94. package/test/failure-query.test.js +147 -0
  95. package/test/failure-retention.test.js +105 -0
  96. package/test/graph-api.test.js +36 -2
  97. package/test/helpers/write-admission-samples.js +6 -2
  98. package/test/inject-recall.test.js +32 -4
  99. package/test/lib-smoke.test.js +20 -0
  100. package/test/llm-audit.test.js +92 -1
  101. package/test/mint-source.test.js +202 -0
  102. package/test/persona-api.test.js +172 -0
  103. package/test/persona-flag.test.js +36 -0
  104. package/test/persona-inject.test.js +145 -0
  105. package/test/persona-settings.test.js +145 -0
  106. package/test/pipeline-activities.test.js +155 -0
  107. package/test/pipeline-attribution.test.js +175 -0
  108. package/test/pipeline-command.test.js +243 -0
  109. package/test/pipeline-confirm.test.js +148 -0
  110. package/test/pipeline-exec.test.js +172 -0
  111. package/test/pipeline-nudge.test.js +1036 -0
  112. package/test/pipeline-observations.test.js +157 -0
  113. package/test/pipeline-observe.test.js +127 -0
  114. package/test/pipeline-r1r2.test.js +149 -0
  115. package/test/pipeline-runner.test.js +442 -0
  116. package/test/pipeline-runs.test.js +767 -0
  117. package/test/pipeline.test.js +636 -0
  118. package/test/placement.test.js +1 -1
  119. package/test/publish-gate.test.js +272 -0
  120. package/test/recall-retention.test.js +128 -0
  121. package/test/reindex-backfill.test.js +83 -4
  122. package/test/release-docs.test.js +130 -0
  123. package/test/release-notes.test.js +240 -0
  124. package/test/release-workflow.test.js +93 -0
  125. package/test/route-policy.test.js +113 -0
  126. package/test/scope-distill-stamp.test.js +135 -0
  127. package/test/security-doc.test.js +139 -0
  128. package/test/sensitive-scan.test.js +44 -0
  129. package/test/serve.test.js +1 -1
  130. package/test/session-usage.test.js +308 -0
  131. package/test/store-write-path-columns.test.js +191 -0
  132. package/test/summarize.test.js +176 -0
  133. package/test/supersede-state-tracking.test.js +4 -4
  134. package/test/tools.test.js +16 -1
  135. package/test/workflow-guards.test.js +156 -0
@@ -18,18 +18,84 @@ const hasBareLf = (text) => /(^|[^\r])\n/.test(text);
18
18
  test("LF 输入:插入占位节,行尾保持 LF,旧内容原样保留", () => {
19
19
  const r = insertReleaseSection(LF_HEAD, "0.8.13", "2026-10-03");
20
20
  assert.equal(r.ok, true);
21
- assert.ok(r.text.startsWith("# Changelog\n\n## [0.8.13] - 2026-10-03"), "插在标题之后");
21
+ assert.ok(r.text.startsWith("# Changelog\n\n## [0.8.13] - 2026-10-03"), "没有 [Unreleased] 时插在标题之后");
22
22
  assert.ok(r.text.includes("- (待填)"), "带 release.yml verify 要的占位节");
23
23
  assert.ok(r.text.includes("## [0.8.12] - 2026-10-01"), "旧内容不动");
24
24
  assert.equal(r.text.includes("\r\n"), false, "不该往 LF 文件里混 CRLF");
25
25
  });
26
26
 
27
- test("CRLF 输入:同样能插(原先这里静默 no-op —— 本模块存在的首要理由)", () => {
28
- const r = insertReleaseSection(CRLF_HEAD, "0.8.13", "2026-10-03");
29
- assert.equal(r.ok, true, "CRLF 检出上必须也能插入");
30
- assert.notEqual(r.text, CRLF_HEAD, "绝不返回原文——那正是当初的假成功");
31
- assert.ok(r.text.includes("## [0.8.13] - 2026-10-03"));
32
- assert.equal(hasBareLf(r.text), false, "插入的内容也要用 CRLF,不混行尾");
27
+ test("有 [Unreleased] 时插在它下面,不插在上面", () => {
28
+ // Keep a Changelog 的约定:未发布区在最上,新版本节排在它下面。
29
+ // 插反了会得到「[0.9.1] 在 [Unreleased] 之上」——v0.9.0 那次就是靠事后人工提交
30
+ // (77df0fb)掰回来的,机器算得对的事不该留给人补。
31
+ const src = "# Changelog\n\n## [Unreleased]\n\n## 🐛 修复\n\n- 攒着的条目\n\n## [0.9.0] - 2026-10-17\n\n- 旧版本\n";
32
+ const r = insertReleaseSection(src, "0.9.1", "2026-10-18");
33
+ assert.equal(r.ok, true);
34
+ assert.ok(
35
+ r.text.indexOf("## [Unreleased]") < r.text.indexOf("## [0.9.1] - 2026-10-18"),
36
+ "[Unreleased] 必须排在前面"
37
+ );
38
+ assert.ok(
39
+ r.text.indexOf("## [0.9.1] - 2026-10-18") < r.text.indexOf("## [0.9.0] - 2026-10-17"),
40
+ "新版本节又必须排在上一版之前"
41
+ );
42
+ assert.ok(r.text.includes("- 攒着的条目"), "[Unreleased] 里攒的条目不许丢");
43
+ assert.ok(r.text.includes("- 旧版本"), "旧版本节不许丢");
44
+ assert.equal(r.text.includes("\r\n"), false, "这组输入是 LF,不该混进 CRLF");
45
+ });
46
+
47
+ test("[Unreleased] 攒着条目时整段搬进新版本节,不留空占位节", () => {
48
+ // 原先只插一个「🐛 修复 / (待填)」占位,攒着的条目原地不动:发版后文件里同时存在
49
+ // 「新版本节是空的」和「旧条目还挂在 Unreleased 名下」,搬运这一步照样要人工补
50
+ // (v0.9.0 的 77df0fb 干的就是这件事)。搬位置不搬内容——原样移动、一个字不改。
51
+ const src =
52
+ "# Changelog\n\n## [Unreleased]\n\n## 🐛 修复\n\n- 条目甲\n- 条目乙\n\n## 🔐 安全\n\n- 条目丙\n\n## [0.9.0] - 2026-10-17\n\n- 旧版本\n";
53
+ const r = insertReleaseSection(src, "0.9.1", "2026-10-18");
54
+ assert.equal(r.ok, true);
55
+ assert.equal(r.moved, 3, "三条条目都要搬过去");
56
+ assert.equal(r.movedContent, true, "搬了正文与条目,两个字段都该为真");
57
+ assert.equal(r.text.includes("(待填)"), false, "搬了真条目就不该再留空占位");
58
+ // Unreleased 立刻接新版本节,中间不夹任何内容
59
+ assert.ok(r.text.startsWith("# Changelog\n\n## [Unreleased]\n\n## [0.9.1] - 2026-10-18\n\n## 🐛 修复\n\n- 条目甲"));
60
+ assert.ok(r.text.includes("## 🔐 安全\n\n- 条目丙"), "多个小节整体搬,小节结构不动");
61
+ assert.ok(r.text.indexOf("- 条目丙") < r.text.indexOf("## [0.9.0]"), "全都搬进新节里,不留一条挂在 Unreleased 名下");
62
+ });
63
+
64
+ test("[Unreleased] 正文非空但没有 `- ` 条目时,movedContent 仍为 true(moved 只是计数)", () => {
65
+ // 用 moved 当「有没有搬」的判据是错的:正文非空、但一条 `- ` 都没有时 moved 是 0,
66
+ // 入口会打印「占位节([Unreleased] 本来是空的)」——而它显然不空。两个字段必须分开用。
67
+ const src = "# Changelog\n\n## [Unreleased]\n\n一段没有条目的说明正文。\n\n## [0.9.0] - 2026-10-17\n\n- 旧版本\n";
68
+ const r = insertReleaseSection(src, "0.9.1", "2026-10-18");
69
+ assert.equal(r.ok, true);
70
+ assert.equal(r.moved, 0, "没有 `- ` 开头的行为,条目数是 0");
71
+ assert.equal(r.movedContent, true, "但正文确实搬了,这个字段才是「有没有搬」的判据");
72
+ assert.ok(r.text.includes("一段没有条目的说明正文。"), "正文不许丢");
73
+ assert.equal(r.text.includes("(待填)"), false, "不该在搬了正文之后还留空占位");
74
+ });
75
+
76
+ test("[Unreleased] 首行是缩进代码块时保留前导空格(原样搬,不 trim)", () => {
77
+ // body.trim() 会把首行的四空格吃掉,Markdown 代码块降级成普通段落——
78
+ // 那就不是「整段搬、一个字不改」了。只许剥掉节边界上的空行。
79
+ const src = "# Changelog\n\n## [Unreleased]\n\n npm run sync\n\n## [0.9.0] - 2026-10-17\n\n- 旧版本\n";
80
+ const r = insertReleaseSection(src, "0.9.1", "2026-10-18");
81
+ assert.equal(r.ok, true);
82
+ assert.ok(r.text.includes("\n npm run sync\n"), "四空格缩进必须原样保留:\n" + JSON.stringify(r.text));
83
+ });
84
+
85
+ test("[Unreleased] 为空时插占位节,且不产生多余空行", () => {
86
+ const src = "# Changelog\r\n\r\n## [Unreleased]\r\n## [0.9.0] - 2026-10-17\r\n\r\n- x\r\n";
87
+ const r = insertReleaseSection(src, "0.9.1", "2026-10-18");
88
+ assert.equal(r.ok, true, "CRLF 检出上也要能插");
89
+ assert.equal(r.moved, 0, "Unreleased 是空的,没有可搬的");
90
+ assert.equal(r.movedContent, false, "Unreleased 空时才该走占位节分支");
91
+ assert.ok(
92
+ r.text.startsWith(
93
+ "# Changelog\r\n\r\n## [Unreleased]\r\n\r\n## [0.9.1] - 2026-10-18\r\n\r\n## 🐛 修复\r\n\r\n- (待填)\r\n\r\n## [0.9.0] - 2026-10-17\r\n"
94
+ ),
95
+ "空的 Unreleased 后面接占位节,再接上一版"
96
+ );
97
+ assert.equal(/\n\n\n/.test(r.text.replace(/\r\n/g, "\n")), false, "不许出三连空行");
98
+ assert.equal(hasBareLf(r.text), false);
33
99
  });
34
100
 
35
101
  test("带 BOM 的输入也能插(`^#` 匹配不到属同一类形状漂移)", () => {
@@ -57,4 +123,4 @@ test("detectEol:认两种行尾,无换行时按 LF 处理", () => {
57
123
  assert.equal(detectEol("a\nb"), "\n");
58
124
  assert.equal(detectEol("a\r\nb"), "\r\n");
59
125
  assert.equal(detectEol("a"), "\n");
60
- });
126
+ });
@@ -0,0 +1,37 @@
1
+ // CLI 枚举是手写字面量(bin/cli.mjs 是零依赖 HTTP 客户端,不 import 服务端
2
+ // 模块),没有类型系统兜着,所以用一张文本锁把它钉在 store.TYPES 上。
3
+ // 防的回归:2026-10 审计发现 MEMORY_TYPES 只有 5 个类型、SEARCH_MODES 缺
4
+ // hybrid,结果是 add --type pitfall 与 search --mode hybrid 被客户端自己拒掉
5
+ // ——服务端明明支持,客户端先说不。
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { readFileSync } from "node:fs";
9
+ import { fileURLToPath } from "node:url";
10
+ import path from "node:path";
11
+ import { TYPES } from "../src/store.js";
12
+
13
+ const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
14
+ const cli = readFileSync(path.join(root, "bin", "cli.mjs"), "utf8");
15
+
16
+ function literalArray(name) {
17
+ const m = cli.match(new RegExp("const " + name + " = \\[([^\\]]*)\\]"));
18
+ assert.ok(m, name + " 应从 bin/cli.mjs 里解析出来(改名就同步改这把锁)");
19
+ return [...m[1].matchAll(/'([^']+)'/g)].map((x) => x[1]);
20
+ }
21
+
22
+ test("cli MEMORY_TYPES 与 store.TYPES 的差值恰好是 document", () => {
23
+ const cliTypes = literalArray("MEMORY_TYPES");
24
+ // document 只能经 memory_register_document 铸造;放进 --type 会让用户得到一条
25
+ // 服务端 400。锁「差值恰好是 document」而不是「完全相等」,就是为了让这条
26
+ // 例外显式化:未来再加一个只能经专用接口铸造的类型,这里会红。
27
+ const expected = [...TYPES].filter((t) => t !== "document").sort();
28
+ assert.deepEqual(cliTypes.slice().sort(), expected);
29
+ });
30
+
31
+ test("cli SEARCH_MODES 覆盖 API 支持的全部召回模式", () => {
32
+ const modes = literalArray("SEARCH_MODES");
33
+ // 四种模式来自 src/api.js 的 /search mode 注释(auto/hybrid/vector/keyword)。
34
+ for (const mode of ["keyword", "vector", "auto", "hybrid"]) {
35
+ assert.ok(modes.includes(mode), "SEARCH_MODES 缺少 " + mode);
36
+ }
37
+ });