@modusensus/dsh-mneme 0.6.7 → 0.6.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/.github/workflows/test.yml +32 -0
- package/.release-notes-v0.6.9.md +13 -0
- package/CHANGELOG.md +89 -0
- package/LICENSE +21 -21
- package/README.md +219 -463
- package/SECURITY.md +544 -0
- package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +247 -0
- package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +145 -0
- package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +56 -0
- package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +77 -0
- package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +64 -0
- package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +90 -0
- package/dsh-mneme/CHANGELOG.md +248 -0
- package/dsh-mneme/LICENSE +21 -0
- package/dsh-mneme/README.md +465 -0
- package/{cordis.patch.yml → dsh-mneme/cordis.patch.yml} +15 -15
- package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +183 -0
- package/dsh-mneme/docs/ENTITIES.md +245 -0
- package/dsh-mneme/docs/LOCAL_MODEL.md +141 -0
- package/dsh-mneme/docs/MIGRATION.md +127 -0
- package/dsh-mneme/docs/SEMANTIC.md +256 -0
- package/dsh-mneme/docs/SLEEP.md +163 -0
- package/{lib → dsh-mneme/lib}/api.js +783 -783
- package/{lib → dsh-mneme/lib}/client.js +1757 -1757
- package/{src → dsh-mneme/lib}/commands.js +64 -64
- package/{lib → dsh-mneme/lib}/config.js +298 -288
- package/{lib → dsh-mneme/lib}/dream/clustering.js +118 -118
- package/{src → dsh-mneme/lib}/dream/decisions.js +488 -439
- package/{lib → dsh-mneme/lib}/dream/sleep.js +561 -554
- package/{src → dsh-mneme/lib}/dream/tag-extractor.js +156 -156
- package/{lib → dsh-mneme/lib}/dream.js +958 -929
- package/{src → dsh-mneme/lib}/embedding.js +154 -154
- package/{src → dsh-mneme/lib}/entities/extractor.js +242 -242
- package/{src → dsh-mneme/lib}/hot-memory.js +53 -53
- package/{lib → dsh-mneme/lib}/index.js +361 -361
- package/{src → dsh-mneme/lib}/inject.js +208 -208
- package/{lib → dsh-mneme/lib}/local-embedder.js +282 -282
- package/{lib → dsh-mneme/lib}/mirror.js +170 -170
- package/{lib → dsh-mneme/lib}/parser/tag.js +59 -59
- package/{lib → dsh-mneme/lib}/parser/wiki-link.js +38 -38
- package/{src → dsh-mneme/lib}/quality-filter.js +123 -123
- package/{lib → dsh-mneme/lib}/reranker.js +218 -218
- package/{lib → dsh-mneme/lib}/search/adaptive.js +22 -22
- package/{lib → dsh-mneme/lib}/search/bm25.js +96 -96
- package/{src → dsh-mneme/lib}/search/tag-boost.js +61 -61
- package/{src → dsh-mneme/lib}/service.js +1726 -1726
- package/{lib → dsh-mneme/lib}/settings.js +172 -172
- package/{lib → dsh-mneme/lib}/store.js +2238 -2238
- package/{lib → dsh-mneme/lib}/summarize.js +236 -236
- package/{lib → dsh-mneme/lib}/tools.js +290 -290
- package/{lib → dsh-mneme/lib}/vector-index.js +116 -116
- package/dsh-mneme/package-lock.json +1936 -0
- package/dsh-mneme/package.json +80 -0
- package/{scripts → dsh-mneme/scripts}/benchmark-embed.js +201 -201
- package/{scripts → dsh-mneme/scripts}/benchmark-recall.js +133 -133
- package/{scripts → dsh-mneme/scripts}/benchmark-rerank.js +166 -166
- package/{scripts → dsh-mneme/scripts}/e2e-dsh.js +218 -218
- package/{scripts → dsh-mneme/scripts}/stress-dsh.js +255 -255
- package/{scripts → dsh-mneme/scripts}/sync-lib.js +52 -52
- package/{src → dsh-mneme/src}/api.js +783 -783
- package/{lib → dsh-mneme/src}/commands.js +64 -64
- package/{src → dsh-mneme/src}/config.js +298 -288
- package/{src → dsh-mneme/src}/dream/clustering.js +118 -118
- package/{lib → dsh-mneme/src}/dream/decisions.js +488 -439
- package/{src → dsh-mneme/src}/dream/sleep.js +561 -554
- package/{lib → dsh-mneme/src}/dream/tag-extractor.js +156 -156
- package/{src → dsh-mneme/src}/dream.js +958 -929
- package/{lib → dsh-mneme/src}/embedding.js +154 -154
- package/{lib → dsh-mneme/src}/entities/extractor.js +242 -242
- package/{lib → dsh-mneme/src}/hot-memory.js +53 -53
- package/{src → dsh-mneme/src}/index.js +361 -361
- package/{lib → dsh-mneme/src}/inject.js +208 -208
- package/{src → dsh-mneme/src}/local-embedder.js +282 -282
- package/{src → dsh-mneme/src}/mirror.js +170 -170
- package/{src → dsh-mneme/src}/parser/tag.js +59 -59
- package/{src → dsh-mneme/src}/parser/wiki-link.js +38 -38
- package/{lib → dsh-mneme/src}/quality-filter.js +123 -123
- package/{src → dsh-mneme/src}/reranker.js +218 -218
- package/{src → dsh-mneme/src}/search/adaptive.js +22 -22
- package/{src → dsh-mneme/src}/search/bm25.js +96 -96
- package/{lib → dsh-mneme/src}/search/tag-boost.js +61 -61
- package/{lib → dsh-mneme/src}/service.js +1726 -1726
- package/{src → dsh-mneme/src}/settings.js +172 -172
- package/{src → dsh-mneme/src}/store.js +2238 -2238
- package/{src → dsh-mneme/src}/summarize.js +236 -236
- package/{src → dsh-mneme/src}/tools.js +290 -290
- package/{src → dsh-mneme/src}/vector-index.js +116 -116
- package/{test → dsh-mneme/test}/api.test.js +594 -594
- package/{test → dsh-mneme/test}/audit.test.js +448 -448
- package/{test → dsh-mneme/test}/benchmark.test.js +35 -35
- package/{test → dsh-mneme/test}/boundary-v0625.test.js +82 -82
- package/{test → dsh-mneme/test}/client.test.js +368 -368
- package/{test → dsh-mneme/test}/clustering.test.js +100 -100
- package/{test → dsh-mneme/test}/commands.test.js +69 -69
- package/{test → dsh-mneme/test}/config.test.js +50 -50
- package/{test → dsh-mneme/test}/conflict-freeze.test.js +290 -290
- package/{test → dsh-mneme/test}/directory.test.js +134 -134
- package/{test → dsh-mneme/test}/dream.test.js +1060 -901
- package/{test → dsh-mneme/test}/entities.test.js +522 -522
- package/{test → dsh-mneme/test}/epistemic.test.js +298 -298
- package/{test → dsh-mneme/test}/fnew-0112.test.js +311 -311
- package/{test → dsh-mneme/test}/fnew-03.test.js +422 -422
- package/{test → dsh-mneme/test}/graph-api.test.js +175 -175
- package/{test → dsh-mneme/test}/helpers/dream-mock.js +82 -82
- package/{test → dsh-mneme/test}/hot-memory.test.js +174 -174
- package/{test → dsh-mneme/test}/inject.test.js +103 -103
- package/{test → dsh-mneme/test}/llm-audit.test.js +279 -279
- package/{test → dsh-mneme/test}/local-embedder.test.js +227 -227
- package/{test → dsh-mneme/test}/mirror-dirty.test.js +424 -424
- package/{test → dsh-mneme/test}/mirror-edit-digest.test.js +187 -187
- package/{test → dsh-mneme/test}/mirror-generation.test.js +499 -499
- package/{test → dsh-mneme/test}/mirror.test.js +249 -249
- package/{test → dsh-mneme/test}/normalize-decisions.test.js +120 -120
- package/{test → dsh-mneme/test}/peer-blockers.test.js +190 -190
- package/{test → dsh-mneme/test}/policy-epoch.test.js +259 -259
- package/{test → dsh-mneme/test}/provenance.test.js +103 -103
- package/{test → dsh-mneme/test}/quality-filter.test.js +118 -118
- package/{test → dsh-mneme/test}/reasoning-effort.test.js +199 -199
- package/{test → dsh-mneme/test}/recall-evals.test.js +235 -235
- package/{test → dsh-mneme/test}/recall-layer.test.js +315 -315
- package/{test → dsh-mneme/test}/receipt-chain.test.js +451 -451
- package/{test → dsh-mneme/test}/reflection.test.js +226 -226
- package/{test → dsh-mneme/test}/reranker.test.js +240 -240
- package/{test → dsh-mneme/test}/search-fusion.test.js +90 -90
- package/{test → dsh-mneme/test}/semantic.test.js +124 -124
- package/{test → dsh-mneme/test}/service-search.test.js +199 -199
- package/{test → dsh-mneme/test}/service.test.js +435 -435
- package/{test → dsh-mneme/test}/settings.test.js +118 -118
- package/{test → dsh-mneme/test}/sleep.test.js +365 -365
- package/{test → dsh-mneme/test}/store.test.js +436 -436
- package/{test → dsh-mneme/test}/stress.test.js +209 -209
- package/{test → dsh-mneme/test}/summarize.test.js +191 -191
- package/{test → dsh-mneme/test}/tag-boost.test.js +125 -125
- package/{test → dsh-mneme/test}/tag.test.js +312 -312
- package/{test → dsh-mneme/test}/tools.test.js +285 -285
- package/{test → dsh-mneme/test}/vector-index.test.js +221 -221
- package/{test → dsh-mneme/test}/wiki-link.test.js +332 -332
- package/package.json +18 -40
- package//346/250/252/345/271/205.png +0 -0
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
// v0.6.3 目录视图测试。
|
|
2
|
-
// 覆盖:store.getDirectory 按 tag 分组 / 组排序(tag 字典序)/
|
|
3
|
-
// 组内成员排序(importance DESC → updated_at DESC)/
|
|
4
|
-
// 无 tag 记忆进 untagged / disposed·archived·forgotten 过滤 /
|
|
5
|
-
// 多 tag 记忆出现在每个 tag 文件夹 / service.getDirectory 输出 wire DTO。
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import { createStore } from "../src/store.js";
|
|
9
|
-
import { createService } from "../src/service.js";
|
|
10
|
-
|
|
11
|
-
function setup() {
|
|
12
|
-
const store = createStore(":memory:");
|
|
13
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
14
|
-
return { store, service };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function seed(store, title, { importance = 3, type = "preference", tags, session_id } = {}) {
|
|
18
|
-
const mem = store.save({ type, title, content: `content of ${title}`, importance, session_id });
|
|
19
|
-
if (tags && tags.length) store.setMemoryTags(mem.id, tags);
|
|
20
|
-
return mem;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Bump a row's updated_at directly so ordering assertions are deterministic
|
|
24
|
-
// (nowIso() has ~ms resolution and a same-ms save loses the tiebreaker to the
|
|
25
|
-
// random id).
|
|
26
|
-
function setUpdatedAt(store, id, iso) {
|
|
27
|
-
store.db.prepare("UPDATE memories SET updated_at = ? WHERE id = ?").run(iso, id);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ============================================================ grouping
|
|
31
|
-
|
|
32
|
-
test("getDirectory groups live memories by their live tag set", () => {
|
|
33
|
-
const { store } = setup();
|
|
34
|
-
const a = seed(store, "Linux 笔记", { tags: ["linux"] });
|
|
35
|
-
const b = seed(store, "考研计划", { tags: ["考研"] });
|
|
36
|
-
const dir = store.getDirectory();
|
|
37
|
-
assert.equal(dir.groups.length, 2);
|
|
38
|
-
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
39
|
-
const exam = dir.groups.find((g) => g.tag === "考研");
|
|
40
|
-
assert.deepEqual(linux.memories.map((m) => m.id), [a.id]);
|
|
41
|
-
assert.deepEqual(exam.memories.map((m) => m.id), [b.id]);
|
|
42
|
-
assert.deepEqual(dir.untagged, []);
|
|
43
|
-
store.close();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("getDirectory sorts groups by tag (locale-aware)", () => {
|
|
47
|
-
const { store } = setup();
|
|
48
|
-
seed(store, "B", { tags: ["zebra"] });
|
|
49
|
-
seed(store, "A", { tags: ["alpha"] });
|
|
50
|
-
seed(store, "C", { tags: ["æon"] });
|
|
51
|
-
const dir = store.getDirectory();
|
|
52
|
-
const tags = dir.groups.map((g) => g.tag);
|
|
53
|
-
const sorted = [...tags].sort((x, y) => x.localeCompare(y));
|
|
54
|
-
assert.deepEqual(tags, sorted, "groups must come back tag-sorted");
|
|
55
|
-
store.close();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("getDirectory orders group members by importance DESC then updated_at DESC", () => {
|
|
59
|
-
const { store } = setup();
|
|
60
|
-
// same importance → updated_at decides
|
|
61
|
-
const older = seed(store, "older", { tags: ["linux"], importance: 2 });
|
|
62
|
-
const newer = seed(store, "newer", { tags: ["linux"], importance: 2 });
|
|
63
|
-
setUpdatedAt(store, older.id, "2026-01-01T00:00:00.000Z");
|
|
64
|
-
setUpdatedAt(store, newer.id, "2026-08-01T00:00:00.000Z");
|
|
65
|
-
// higher importance wins regardless of age
|
|
66
|
-
const important = seed(store, "important", { tags: ["linux"], importance: 5 });
|
|
67
|
-
setUpdatedAt(store, important.id, "2025-01-01T00:00:00.000Z");
|
|
68
|
-
const dir = store.getDirectory();
|
|
69
|
-
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
70
|
-
assert.deepEqual(
|
|
71
|
-
linux.memories.map((m) => m.id),
|
|
72
|
-
[important.id, newer.id, older.id],
|
|
73
|
-
"importance DESC primary, updated_at DESC secondary"
|
|
74
|
-
);
|
|
75
|
-
store.close();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("getDirectory collects untagged memories and orders them identically", () => {
|
|
79
|
-
const { store } = setup();
|
|
80
|
-
const a = seed(store, "untagged one", { importance: 1 });
|
|
81
|
-
const b = seed(store, "untagged two", { importance: 4 });
|
|
82
|
-
seed(store, "tagged", { tags: ["linux"], importance: 3 });
|
|
83
|
-
const dir = store.getDirectory();
|
|
84
|
-
assert.deepEqual(dir.untagged.map((m) => m.id), [b.id, a.id], "importance DESC inside untagged");
|
|
85
|
-
assert.equal(dir.groups.some((g) => g.tag === "linux"), true);
|
|
86
|
-
store.close();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("getDirectory excludes forgotten, archived and session-disposed memories", () => {
|
|
90
|
-
const { store } = setup();
|
|
91
|
-
const forgotten = seed(store, "forgotten", { tags: ["linux"] });
|
|
92
|
-
const archived = seed(store, "archived", { tags: ["linux"] });
|
|
93
|
-
const disposed = seed(store, "disposed", { tags: ["linux"], type: "summary", session_id: "sess-disposed" });
|
|
94
|
-
const keep = seed(store, "keep", { tags: ["linux"] });
|
|
95
|
-
store.setForget(forgotten.id, true);
|
|
96
|
-
store.setArchived(archived.id, true);
|
|
97
|
-
store.setDisposedBySession(disposed.session_id, true);
|
|
98
|
-
const dir = store.getDirectory();
|
|
99
|
-
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
100
|
-
assert.deepEqual(linux.memories.map((m) => m.id), [keep.id], "only the live row survives");
|
|
101
|
-
assert.deepEqual(dir.untagged, []);
|
|
102
|
-
store.close();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("getDirectory lists a multi-tag memory under every tag folder once each", () => {
|
|
106
|
-
const { store } = setup();
|
|
107
|
-
const multi = seed(store, "multi", { tags: ["linux", "bash"] });
|
|
108
|
-
const single = seed(store, "single", { tags: ["linux"] });
|
|
109
|
-
const dir = store.getDirectory();
|
|
110
|
-
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
111
|
-
const bash = dir.groups.find((g) => g.tag === "bash");
|
|
112
|
-
// `single` is written second → newer updated_at → ranks before `multi`.
|
|
113
|
-
assert.deepEqual(linux.memories.map((m) => m.id), [single.id, multi.id]);
|
|
114
|
-
assert.deepEqual(bash.memories.map((m) => m.id), [multi.id]);
|
|
115
|
-
assert.equal(dir.untagged.length, 0);
|
|
116
|
-
store.close();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// ============================================================ service
|
|
120
|
-
|
|
121
|
-
test("service.getDirectory returns the wire DTO shape with tags preserved", () => {
|
|
122
|
-
const { store, service } = setup();
|
|
123
|
-
const mem = seed(store, "笔记", { tags: ["linux"] });
|
|
124
|
-
seed(store, "裸条目", { importance: 2 });
|
|
125
|
-
const dir = service.getDirectory();
|
|
126
|
-
assert.deepEqual(Object.keys(dir).sort(), ["groups", "untagged"]);
|
|
127
|
-
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
128
|
-
assert.equal(linux.memories[0].id, mem.id);
|
|
129
|
-
assert.equal(linux.memories[0].title, "笔记");
|
|
130
|
-
assert.equal(typeof linux.memories[0].updated_at, "string", "DTO carries updated_at for the entry row");
|
|
131
|
-
assert.equal(dir.untagged.length, 1);
|
|
132
|
-
assert.equal(dir.untagged[0].title, "裸条目");
|
|
133
|
-
store.close();
|
|
134
|
-
});
|
|
1
|
+
// v0.6.3 目录视图测试。
|
|
2
|
+
// 覆盖:store.getDirectory 按 tag 分组 / 组排序(tag 字典序)/
|
|
3
|
+
// 组内成员排序(importance DESC → updated_at DESC)/
|
|
4
|
+
// 无 tag 记忆进 untagged / disposed·archived·forgotten 过滤 /
|
|
5
|
+
// 多 tag 记忆出现在每个 tag 文件夹 / service.getDirectory 输出 wire DTO。
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { createStore } from "../src/store.js";
|
|
9
|
+
import { createService } from "../src/service.js";
|
|
10
|
+
|
|
11
|
+
function setup() {
|
|
12
|
+
const store = createStore(":memory:");
|
|
13
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
14
|
+
return { store, service };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function seed(store, title, { importance = 3, type = "preference", tags, session_id } = {}) {
|
|
18
|
+
const mem = store.save({ type, title, content: `content of ${title}`, importance, session_id });
|
|
19
|
+
if (tags && tags.length) store.setMemoryTags(mem.id, tags);
|
|
20
|
+
return mem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Bump a row's updated_at directly so ordering assertions are deterministic
|
|
24
|
+
// (nowIso() has ~ms resolution and a same-ms save loses the tiebreaker to the
|
|
25
|
+
// random id).
|
|
26
|
+
function setUpdatedAt(store, id, iso) {
|
|
27
|
+
store.db.prepare("UPDATE memories SET updated_at = ? WHERE id = ?").run(iso, id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================ grouping
|
|
31
|
+
|
|
32
|
+
test("getDirectory groups live memories by their live tag set", () => {
|
|
33
|
+
const { store } = setup();
|
|
34
|
+
const a = seed(store, "Linux 笔记", { tags: ["linux"] });
|
|
35
|
+
const b = seed(store, "考研计划", { tags: ["考研"] });
|
|
36
|
+
const dir = store.getDirectory();
|
|
37
|
+
assert.equal(dir.groups.length, 2);
|
|
38
|
+
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
39
|
+
const exam = dir.groups.find((g) => g.tag === "考研");
|
|
40
|
+
assert.deepEqual(linux.memories.map((m) => m.id), [a.id]);
|
|
41
|
+
assert.deepEqual(exam.memories.map((m) => m.id), [b.id]);
|
|
42
|
+
assert.deepEqual(dir.untagged, []);
|
|
43
|
+
store.close();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("getDirectory sorts groups by tag (locale-aware)", () => {
|
|
47
|
+
const { store } = setup();
|
|
48
|
+
seed(store, "B", { tags: ["zebra"] });
|
|
49
|
+
seed(store, "A", { tags: ["alpha"] });
|
|
50
|
+
seed(store, "C", { tags: ["æon"] });
|
|
51
|
+
const dir = store.getDirectory();
|
|
52
|
+
const tags = dir.groups.map((g) => g.tag);
|
|
53
|
+
const sorted = [...tags].sort((x, y) => x.localeCompare(y));
|
|
54
|
+
assert.deepEqual(tags, sorted, "groups must come back tag-sorted");
|
|
55
|
+
store.close();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("getDirectory orders group members by importance DESC then updated_at DESC", () => {
|
|
59
|
+
const { store } = setup();
|
|
60
|
+
// same importance → updated_at decides
|
|
61
|
+
const older = seed(store, "older", { tags: ["linux"], importance: 2 });
|
|
62
|
+
const newer = seed(store, "newer", { tags: ["linux"], importance: 2 });
|
|
63
|
+
setUpdatedAt(store, older.id, "2026-01-01T00:00:00.000Z");
|
|
64
|
+
setUpdatedAt(store, newer.id, "2026-08-01T00:00:00.000Z");
|
|
65
|
+
// higher importance wins regardless of age
|
|
66
|
+
const important = seed(store, "important", { tags: ["linux"], importance: 5 });
|
|
67
|
+
setUpdatedAt(store, important.id, "2025-01-01T00:00:00.000Z");
|
|
68
|
+
const dir = store.getDirectory();
|
|
69
|
+
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
70
|
+
assert.deepEqual(
|
|
71
|
+
linux.memories.map((m) => m.id),
|
|
72
|
+
[important.id, newer.id, older.id],
|
|
73
|
+
"importance DESC primary, updated_at DESC secondary"
|
|
74
|
+
);
|
|
75
|
+
store.close();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("getDirectory collects untagged memories and orders them identically", () => {
|
|
79
|
+
const { store } = setup();
|
|
80
|
+
const a = seed(store, "untagged one", { importance: 1 });
|
|
81
|
+
const b = seed(store, "untagged two", { importance: 4 });
|
|
82
|
+
seed(store, "tagged", { tags: ["linux"], importance: 3 });
|
|
83
|
+
const dir = store.getDirectory();
|
|
84
|
+
assert.deepEqual(dir.untagged.map((m) => m.id), [b.id, a.id], "importance DESC inside untagged");
|
|
85
|
+
assert.equal(dir.groups.some((g) => g.tag === "linux"), true);
|
|
86
|
+
store.close();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("getDirectory excludes forgotten, archived and session-disposed memories", () => {
|
|
90
|
+
const { store } = setup();
|
|
91
|
+
const forgotten = seed(store, "forgotten", { tags: ["linux"] });
|
|
92
|
+
const archived = seed(store, "archived", { tags: ["linux"] });
|
|
93
|
+
const disposed = seed(store, "disposed", { tags: ["linux"], type: "summary", session_id: "sess-disposed" });
|
|
94
|
+
const keep = seed(store, "keep", { tags: ["linux"] });
|
|
95
|
+
store.setForget(forgotten.id, true);
|
|
96
|
+
store.setArchived(archived.id, true);
|
|
97
|
+
store.setDisposedBySession(disposed.session_id, true);
|
|
98
|
+
const dir = store.getDirectory();
|
|
99
|
+
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
100
|
+
assert.deepEqual(linux.memories.map((m) => m.id), [keep.id], "only the live row survives");
|
|
101
|
+
assert.deepEqual(dir.untagged, []);
|
|
102
|
+
store.close();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("getDirectory lists a multi-tag memory under every tag folder once each", () => {
|
|
106
|
+
const { store } = setup();
|
|
107
|
+
const multi = seed(store, "multi", { tags: ["linux", "bash"] });
|
|
108
|
+
const single = seed(store, "single", { tags: ["linux"] });
|
|
109
|
+
const dir = store.getDirectory();
|
|
110
|
+
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
111
|
+
const bash = dir.groups.find((g) => g.tag === "bash");
|
|
112
|
+
// `single` is written second → newer updated_at → ranks before `multi`.
|
|
113
|
+
assert.deepEqual(linux.memories.map((m) => m.id), [single.id, multi.id]);
|
|
114
|
+
assert.deepEqual(bash.memories.map((m) => m.id), [multi.id]);
|
|
115
|
+
assert.equal(dir.untagged.length, 0);
|
|
116
|
+
store.close();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ============================================================ service
|
|
120
|
+
|
|
121
|
+
test("service.getDirectory returns the wire DTO shape with tags preserved", () => {
|
|
122
|
+
const { store, service } = setup();
|
|
123
|
+
const mem = seed(store, "笔记", { tags: ["linux"] });
|
|
124
|
+
seed(store, "裸条目", { importance: 2 });
|
|
125
|
+
const dir = service.getDirectory();
|
|
126
|
+
assert.deepEqual(Object.keys(dir).sort(), ["groups", "untagged"]);
|
|
127
|
+
const linux = dir.groups.find((g) => g.tag === "linux");
|
|
128
|
+
assert.equal(linux.memories[0].id, mem.id);
|
|
129
|
+
assert.equal(linux.memories[0].title, "笔记");
|
|
130
|
+
assert.equal(typeof linux.memories[0].updated_at, "string", "DTO carries updated_at for the entry row");
|
|
131
|
+
assert.equal(dir.untagged.length, 1);
|
|
132
|
+
assert.equal(dir.untagged[0].title, "裸条目");
|
|
133
|
+
store.close();
|
|
134
|
+
});
|