@modusensus/dsh-mneme 0.8.4 → 0.8.5

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 (69) hide show
  1. package/README.md +16 -7
  2. package/bin/dsh-mneme-mcp.mjs +11 -3
  3. package/lib/api-standalone.js +11 -0
  4. package/lib/api.js +21 -0
  5. package/lib/client.js +213 -31
  6. package/lib/config.js +82 -0
  7. package/lib/document.js +221 -0
  8. package/lib/dream/sleep.js +6 -4
  9. package/lib/dream.js +1347 -1319
  10. package/lib/guide.js +44 -0
  11. package/lib/heat.js +2 -0
  12. package/lib/inject.js +91 -15
  13. package/lib/lang.js +6 -0
  14. package/lib/organize.js +450 -0
  15. package/lib/recall-stats.js +23 -1
  16. package/lib/runtime/layout.js +30 -0
  17. package/lib/runtime/loader.js +4 -1
  18. package/lib/runtime/verify.js +19 -6
  19. package/lib/search/adaptive.js +38 -0
  20. package/lib/service.js +165 -12
  21. package/lib/settings.js +31 -2
  22. package/lib/store.js +109 -8
  23. package/lib/summarize.js +253 -8
  24. package/lib/tools.js +136 -8
  25. package/package.json +6 -6
  26. package/runtime-manifest.json +104 -174
  27. package/scripts/build-runtime-manifest.mjs +8 -1
  28. package/scripts/sync-test-badge.mjs +18 -12
  29. package/src/api-standalone.js +11 -0
  30. package/src/api.js +21 -0
  31. package/src/config.js +82 -0
  32. package/src/document.js +221 -0
  33. package/src/dream/sleep.js +6 -4
  34. package/src/dream.js +1347 -1319
  35. package/src/guide.js +44 -0
  36. package/src/heat.js +2 -0
  37. package/src/inject.js +91 -15
  38. package/src/lang.js +6 -0
  39. package/src/organize.js +450 -0
  40. package/src/recall-stats.js +23 -1
  41. package/src/runtime/layout.js +30 -0
  42. package/src/runtime/loader.js +4 -1
  43. package/src/runtime/verify.js +19 -6
  44. package/src/search/adaptive.js +38 -0
  45. package/src/service.js +165 -12
  46. package/src/settings.js +31 -2
  47. package/src/store.js +109 -8
  48. package/src/summarize.js +253 -8
  49. package/src/tools.js +136 -8
  50. package/test/adaptive-inject-budget.test.js +85 -0
  51. package/test/api.test.js +65 -3
  52. package/test/client.test.js +63 -2
  53. package/test/document.test.js +523 -0
  54. package/test/dream.test.js +51 -0
  55. package/test/inject-content-cap.test.js +5 -3
  56. package/test/inject-pools.test.js +170 -0
  57. package/test/inject-recall.test.js +105 -0
  58. package/test/inject.test.js +35 -1
  59. package/test/llm-audit.test.js +153 -0
  60. package/test/organize.test.js +418 -0
  61. package/test/recall-stats.test.js +7 -1
  62. package/test/runtime-loader.test.js +18 -0
  63. package/test/runtime-manifest.test.js +2 -1
  64. package/test/runtime-verify.test.js +65 -0
  65. package/test/sleep.test.js +37 -0
  66. package/test/store.test.js +13 -0
  67. package/test/summarize.test.js +407 -3
  68. package/test/tools-dto-schema-matrix.test.js +325 -0
  69. package/test/tools.test.js +36 -3
package/test/api.test.js CHANGED
@@ -5,6 +5,7 @@ import { readFileSync } from "node:fs";
5
5
  import { createStore } from "../src/store.js";
6
6
  import { createService } from "../src/service.js";
7
7
  import { createApi } from "../src/api.js";
8
+ import { createInjector } from "../src/inject.js";
8
9
  import { createSettings } from "../src/settings.js";
9
10
  import { createVectorIndex } from "../src/vector-index.js";
10
11
  import { Config } from "../src/config.js";
@@ -654,9 +655,16 @@ test("GET /api/dsh-mneme/features returns empty overrides and effective config d
654
655
  // scopeEnabled/strictScope、issue #194 新增的 resilientModelDownload),
655
656
  // 未覆盖时取 bundle 配置的解析默认值;
656
657
  // dreamProvider/dreamModel 无 schema 默认值(Config({}) 解析为 undefined),
657
- // 不编造给前端 → 58 - 2 = 56(issue #164① 新增 injectContentMaxChars,
658
- // issue #164 叙述条批次新增 dreamNarrativeEnabled/dreamNarrativeMinCluster)
659
- assert.equal(Object.keys(data.effective).length, 56);
658
+ // 不编造给前端 → 62 - 2 = 60(issue #164① 新增 injectContentMaxChars,
659
+ // issue #164 叙述条批次新增 dreamNarrativeEnabled/dreamNarrativeMinCluster,
660
+ // issue #239 新增 summarizeMinWindowChars/summarizeMaxRunsPerSession、
661
+ // 第 4 项的 summarizePeakHours/summarizePeakMaxDeferMinutes 与
662
+ // 第 5 项的 injectUncertaintyAdaptive、
663
+ // issue #257 新增 sleepMaxTokens、issue #258 新增总览路由两键与
664
+ // dreamSummaryMaxInputs、issue #230 新增 documentMemoryEnabled/
665
+ // documentInjectBudget、issue #249 第一批新增 injectGuidanceEnabled/
666
+ // pinnedInjectBudget,v0.8.5 新增 disableMemorySearch/disableMemoryArchive)
667
+ assert.equal(Object.keys(data.effective).length, 59 + 3 + 2 + 1 + 2 + 2 + 2);
660
668
  assert.equal(data.effective.dreamSkipInvalid, true);
661
669
  assert.equal(data.effective.allowCrossTypeMerge, false);
662
670
  assert.equal(data.effective.dreamMinIntervalMinutes, 0);
@@ -1576,3 +1584,57 @@ test("inject-status: autoInject off is never suppressed (user's own choice, not
1576
1584
  await route.handler(req("/api/dsh-mneme/inject-status"), res);
1577
1585
  assert.deepEqual(JSON.parse(res.body), { autoInject: false, agentPreset: "minimal", suppressed: false });
1578
1586
  });
1587
+
1588
+ test("GET /api/dsh-mneme/inject-preview: returns last assembly snapshot (issue #179)", async () => {
1589
+ const store = createStore(":memory:");
1590
+ const service = createService({ store, mirror: null, config: {} });
1591
+ const settings = createSettings(store.db);
1592
+ const routes = [];
1593
+ const ctx = {
1594
+ webServer: {
1595
+ register(route) {
1596
+ routes.push(route);
1597
+ return () => {};
1598
+ }
1599
+ }
1600
+ };
1601
+ const api = createApi(
1602
+ ctx,
1603
+ service,
1604
+ settings,
1605
+ { add() {}, remove() {}, list() { return []; } },
1606
+ undefined,
1607
+ undefined,
1608
+ "",
1609
+ Config({})
1610
+ );
1611
+ const route = routes.find((r) => r.path === "/api/dsh-mneme/inject-preview");
1612
+ assert.ok(route, "preview endpoint must be registered");
1613
+
1614
+ // 无渲染发生:snapshot=null(新宿主/新会话/autoInject 关闭同形)。
1615
+ let res = new FakeRes();
1616
+ await route.handler(req("/api/dsh-mneme/inject-preview"), res);
1617
+ assert.equal(res.statusCode, 200);
1618
+ assert.deepEqual(JSON.parse(res.body), { snapshot: null });
1619
+
1620
+ // 真实渲染一帧后:快照透传(api 层不做任何加工)。
1621
+ service.saveWithDedupe({ type: "preference", title: "语言", content: "用户用中文交流", importance: 5 });
1622
+ const contexts = [];
1623
+ const promptCtx = {
1624
+ systemPrompt: { context(def) { contexts.push(def); return () => {}; } }
1625
+ };
1626
+ const injector = createInjector(promptCtx, service, settings, Config({ maxInjectedItems: 3, importanceThreshold: 3 }));
1627
+ contexts[0].text({});
1628
+ res = new FakeRes();
1629
+ await route.handler(req("/api/dsh-mneme/inject-preview"), res);
1630
+ const body = JSON.parse(res.body);
1631
+ assert.ok(body.snapshot, "snapshot present after a real render");
1632
+ assert.equal(body.snapshot.entries[0].title, "语言");
1633
+ assert.ok(body.snapshot.totalChars > 0);
1634
+
1635
+ // 非 GET → 404(与 inject-status 同款)。
1636
+ res = new FakeRes();
1637
+ await route.handler(req("/api/dsh-mneme/inject-preview", "POST"), res);
1638
+ assert.equal(res.statusCode, 404);
1639
+ injector(); // 快照是模块全局:用完即清,不污染后续用例
1640
+ });
@@ -665,7 +665,7 @@ test("settings feedback card: prefilled issue + mailto + browse, version from /i
665
665
  );
666
666
  // 2. GitHub 新建 issue:issues/new?title=&body= 预填环境信息(当前仓库无模板)
667
667
  assert.ok(
668
- clientSource.includes("https://github.com/modusensus/dsh-mneme/issues/new?title="),
668
+ clientSource.includes("https://github.com/slow-stack/dsh-mneme/issues/new?title="),
669
669
  "the issue link must prefill title+body on issues/new"
670
670
  );
671
671
  assert.ok(
@@ -679,7 +679,7 @@ test("settings feedback card: prefilled issue + mailto + browse, version from /i
679
679
  );
680
680
  // 4. 浏览已知问题:跳仓库 issues 列表页(去重前置步骤)
681
681
  assert.ok(
682
- clientSource.includes('href: "https://github.com/modusensus/dsh-mneme/issues"'),
682
+ clientSource.includes('href: "https://github.com/slow-stack/dsh-mneme/issues"'),
683
683
  "the browse link must open the repo issues list"
684
684
  );
685
685
  // 5. 双语 i18n
@@ -797,4 +797,65 @@ test("conflict queue ships a central review surface on the status tab", () => {
797
797
  assert.ok(clientSource.includes('"/api/dsh-mneme/conflicts"'), "queue must fetch the conflicts endpoint");
798
798
  assert.ok(clientSource.includes('"/api/dsh-mneme/conflicts/resolve"'), "queue must call the resolve endpoint");
799
799
  assert.ok(clientSource.includes("h(ConflictsQueue, { t })"), "status tab must render the queue");
800
+ });
801
+
802
+ // --- issue #178 批次一:无障碍(aria-live 网络 / 语义标题 / 焦点管理 / 图摘要) ---
803
+
804
+ // 面板瞬时反馈(保存/裁决/刷新/复制)不能只靠纯视觉 span:announce() 单例
805
+ // polite live region 是唯一的播报通道,所有接线点都必须走它。
806
+ test("a11y: announce() live region exists and every transient feedback routes through it", () => {
807
+ assert.ok(clientSource.includes('function announce(text)'), "module-level announce() must exist");
808
+ assert.ok(clientSource.includes('"aria-live"'), "live region must set aria-live");
809
+ assert.ok(clientSource.includes('"role", "status"'), "live region must carry role=status");
810
+ // 接线点:功能开关保存、画像、向量、token、模式、extapi 保存+复制、
811
+ // 记忆编辑保存、队列刷新、裁决完成 —— 至少 9 处。
812
+ const wired = (clientSource.match(/\bannounce\(t\(/g) || []).length;
813
+ assert.ok(wired >= 9, `announce() wiring points expected >= 9, got ${wired}`);
814
+ });
815
+
816
+ // 状态卡标题原来是 div,读屏无法按标题导航;统一 h3(CSS margin 归零防回归)。
817
+ test("a11y: status card titles are real headings", () => {
818
+ assert.ok(clientSource.includes('h("h3", { className: "mneme-xcolhead" }'), "StatusCard title must be an h3");
819
+ const css = clientSource.match(/\.mneme-xcolhead\{[^}]*\}/);
820
+ assert.ok(css && css[0].includes("margin:0"), "h3 default margin must be neutralized in CSS");
821
+ });
822
+
823
+ // 弹层焦点管理:Tab 圈在面板内,关闭后焦点还给触发元素(两个 opener 都要标记)。
824
+ test("a11y: overlay focus trap and focus restore are wired", () => {
825
+ const openers = (clientSource.match(/"data-mneme-overlay-opener"/g) || []).length;
826
+ assert.equal(openers, 2, "both overlay entry buttons must carry the opener marker");
827
+ assert.ok(clientSource.includes('querySelectorAll(\'button, input, select, textarea, a[href], [tabindex]:not([tabindex="-1"])\')'),
828
+ "overlay must enumerate focusables for the Tab cycle");
829
+ assert.ok(/querySelector\('\[data-mneme-overlay-opener\]'\)/.test(clientSource), "closing must restore focus to the opener");
830
+ });
831
+
832
+ // 冲突裁决按钮「保留 A/B」在同文案多卡片下不可区分,必须带条目标题的 aria-label。
833
+ test("a11y: conflict keep buttons carry item-title aria-labels", () => {
834
+ const labels = (clientSource.match(/"aria-label": `\$\{t\("memory\.status\.conflictQueue\.keep/g) || []).length;
835
+ assert.equal(labels, 2, "keepA/keepB buttons must both set aria-label");
836
+ });
837
+
838
+ // ego 关系图对读屏是一块不可达 SVG,必须 role=img + 计数摘要(键中英各一)。
839
+ test("a11y: entity graph svg exposes a count summary", () => {
840
+ assert.ok(clientSource.includes('role: "img"'), "graph svg must be role=img");
841
+ for (const key of ["memory.graph.summary"]) {
842
+ const occurrences = clientSource.split(`"${key}"`).length - 1;
843
+ assert.ok(occurrences >= 2, `i18n key ${key} must exist in both zh and en (got ${occurrences})`);
844
+ }
845
+ });
846
+
847
+ // --- issue #179:注入预览(状态页卡片 + /inject-preview 端点透传的旁路快照) ---
848
+
849
+ test("a11y+preview: inject preview card is wired on the status tab", () => {
850
+ assert.ok(clientSource.includes('"/api/dsh-mneme/inject-preview"'), "card must fetch the preview endpoint");
851
+ assert.ok(clientSource.includes("h(InjectPreviewCard, { t })"), "status grid must render the preview card");
852
+ for (const key of [
853
+ "memory.status.injectPreview",
854
+ "memory.status.injectPreviewNone",
855
+ "memory.status.injectPreview.chars",
856
+ "memory.status.injectPreview.empty"
857
+ ]) {
858
+ const occurrences = clientSource.split(`"${key}"`).length - 1;
859
+ assert.ok(occurrences >= 2, `i18n key ${key} must exist in both zh and en (got ${occurrences})`);
860
+ }
800
861
  });