@modusensus/dsh-mneme 0.7.10 → 0.7.12
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.en.md +437 -0
- package/README.md +364 -166
- package/bin/cli.mjs +603 -0
- package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +1 -0
- package/lib/api-standalone.js +264 -0
- package/{dsh-mneme/lib → lib}/api.js +133 -289
- package/{dsh-mneme/lib → lib}/client.js +1008 -922
- package/{dsh-mneme/lib → lib}/config.js +54 -99
- package/{dsh-mneme/lib → lib}/dream/decisions.js +94 -143
- package/{dsh-mneme/lib → lib}/dream/sleep.js +12 -152
- package/{dsh-mneme/lib → lib}/dream.js +22 -168
- package/{dsh-mneme/src → lib}/hot-memory.js +0 -7
- package/{dsh-mneme/lib → lib}/index.js +45 -42
- package/{dsh-mneme/lib → lib}/inject.js +11 -53
- package/{dsh-mneme/src → lib}/mirror.js +1 -12
- package/{dsh-mneme/lib → lib}/quality-filter.js +1 -3
- package/{dsh-mneme/lib → lib}/reranker.js +1 -4
- package/{dsh-mneme/lib → lib}/service.js +20 -410
- package/{dsh-mneme/src → lib}/settings.js +34 -64
- package/{dsh-mneme/lib → lib}/store.js +38 -450
- package/{dsh-mneme/lib → lib}/summarize.js +10 -10
- package/lib/tools.js +274 -0
- package/package.json +45 -19
- package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +3 -4
- package/{dsh-mneme/scripts → scripts}/sync-lib.js +5 -10
- package/src/api-standalone.js +264 -0
- package/{dsh-mneme/src → src}/api.js +133 -289
- package/{dsh-mneme/src → src}/config.js +54 -99
- package/{dsh-mneme/src → src}/dream/decisions.js +94 -143
- package/{dsh-mneme/src → src}/dream/sleep.js +12 -152
- package/{dsh-mneme/src → src}/dream.js +22 -168
- package/{dsh-mneme/lib → src}/hot-memory.js +0 -7
- package/{dsh-mneme/src → src}/index.js +45 -42
- package/{dsh-mneme/src → src}/inject.js +11 -53
- package/{dsh-mneme/lib → src}/mirror.js +1 -12
- package/{dsh-mneme/src → src}/quality-filter.js +1 -3
- package/{dsh-mneme/src → src}/reranker.js +1 -4
- package/{dsh-mneme/src → src}/service.js +20 -410
- package/{dsh-mneme/lib → src}/settings.js +34 -64
- package/{dsh-mneme/src → src}/store.js +38 -450
- package/{dsh-mneme/src → src}/summarize.js +10 -10
- package/src/tools.js +274 -0
- package/{dsh-mneme/test → test}/api.test.js +45 -156
- package/{dsh-mneme/test → test}/client.test.js +10 -206
- package/{dsh-mneme/test → test}/config.test.js +0 -21
- package/{dsh-mneme/test → test}/dream.test.js +1 -160
- package/{dsh-mneme/test → test}/graph-api.test.js +0 -34
- package/{dsh-mneme/test → test}/hot-memory.test.js +0 -29
- package/test/inject.test.js +120 -0
- package/{dsh-mneme/test → test}/llm-audit.test.js +4 -4
- package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -27
- package/{dsh-mneme/test → test}/recall-layer.test.js +5 -26
- package/{dsh-mneme/test → test}/reranker.test.js +0 -43
- package/{dsh-mneme/test → test}/service-search.test.js +0 -25
- package/{dsh-mneme/test → test}/service.test.js +0 -106
- package/{dsh-mneme/test → test}/settings.test.js +16 -35
- package/{dsh-mneme/test → test}/sleep.test.js +4 -171
- package/test/standalone-api.test.js +326 -0
- package/{dsh-mneme/test → test}/store.test.js +0 -76
- package/{dsh-mneme/test → test}/summarize.test.js +16 -15
- package/test/tools.test.js +265 -0
- package/.github/workflows/publish.yml +0 -58
- package/.github/workflows/test.yml +0 -32
- package/CHANGELOG.md +0 -91
- package/CONTRIBUTING.md +0 -293
- package/SECURITY.md +0 -718
- package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
- package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
- package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
- package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
- package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
- package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
- package/dsh-mneme/CHANGELOG.md +0 -419
- package/dsh-mneme/LICENSE +0 -21
- package/dsh-mneme/README.md +0 -501
- package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
- package/dsh-mneme/docs/ENTITIES.md +0 -245
- package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
- package/dsh-mneme/docs/MIGRATION.md +0 -127
- package/dsh-mneme/docs/SEMANTIC.md +0 -256
- package/dsh-mneme/docs/SLEEP.md +0 -163
- package/dsh-mneme/lib/dream/tag-extractor.js +0 -156
- package/dsh-mneme/lib/heat.js +0 -136
- package/dsh-mneme/lib/parser/tag.js +0 -59
- package/dsh-mneme/lib/parser/wiki-link.js +0 -38
- package/dsh-mneme/lib/search/tag-boost.js +0 -61
- package/dsh-mneme/lib/tools.js +0 -458
- package/dsh-mneme/package-lock.json +0 -1936
- package/dsh-mneme/package.json +0 -83
- package/dsh-mneme/scripts/check-sync.js +0 -42
- package/dsh-mneme/src/client.js +0 -2242
- package/dsh-mneme/src/dream/tag-extractor.js +0 -156
- package/dsh-mneme/src/heat.js +0 -136
- package/dsh-mneme/src/parser/tag.js +0 -59
- package/dsh-mneme/src/parser/wiki-link.js +0 -38
- package/dsh-mneme/src/search/tag-boost.js +0 -61
- package/dsh-mneme/src/tools.js +0 -458
- package/dsh-mneme/test/boundary-v0625.test.js +0 -82
- package/dsh-mneme/test/directory.test.js +0 -134
- package/dsh-mneme/test/heat.test.js +0 -148
- package/dsh-mneme/test/inject.test.js +0 -206
- package/dsh-mneme/test/layered-types-stats.test.js +0 -144
- package/dsh-mneme/test/lib-smoke.test.js +0 -109
- package/dsh-mneme/test/normalize-decisions.test.js +0 -120
- package/dsh-mneme/test/provenance.test.js +0 -103
- package/dsh-mneme/test/recall-runs.test.js +0 -93
- package/dsh-mneme/test/sleep-heat.test.js +0 -112
- package/dsh-mneme/test/tag-boost.test.js +0 -125
- package/dsh-mneme/test/tag.test.js +0 -426
- package/dsh-mneme/test/tools.test.js +0 -674
- package/dsh-mneme/test/updated-at-semantics.test.js +0 -113
- package/dsh-mneme/test/wiki-link.test.js +0 -332
- package//346/250/252/345/271/205.png +0 -0
- /package/{dsh-mneme/lib → lib}/commands.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
- /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
- /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
- /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
- /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
- /package/{dsh-mneme/src → src}/commands.js +0 -0
- /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
- /package/{dsh-mneme/src → src}/embedding.js +0 -0
- /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
- /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
- /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
- /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
- /package/{dsh-mneme/src → src}/vector-index.js +0 -0
- /package/{dsh-mneme/test → test}/audit.test.js +0 -0
- /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
- /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
- /package/{dsh-mneme/test → test}/commands.test.js +0 -0
- /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
- /package/{dsh-mneme/test → test}/entities.test.js +0 -0
- /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
- /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
- /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
- /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
- /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
- /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
- /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
- /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
- /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
- /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
- /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
- /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
- /package/{dsh-mneme/test → test}/stress.test.js +0 -0
- /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { assertSupportedJsonSchema, validateJsonSchemaValue } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
import { createStore } from "../src/store.js";
|
|
5
|
+
import { createService } from "../src/service.js";
|
|
6
|
+
import { createTools } from "../src/tools.js";
|
|
7
|
+
|
|
8
|
+
function setup(embedder) {
|
|
9
|
+
const store = createStore(":memory:");
|
|
10
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
11
|
+
const registered = [];
|
|
12
|
+
const ctx = {
|
|
13
|
+
tools: {
|
|
14
|
+
register(def) {
|
|
15
|
+
registered.push(def);
|
|
16
|
+
return () => {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const tools = createTools(ctx, service, {}, embedder);
|
|
21
|
+
return { store, service, tools, registered };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Collect authoring-DSL regressions in a compiled schema: property-level
|
|
25
|
+
// `required: true` (must be projected to a top-level required array by
|
|
26
|
+
// defineTool) and `minimum`/`maximum` (outside the enforced subset) are
|
|
27
|
+
// rejected by the real harness, so they must never appear post-compilation.
|
|
28
|
+
function walkSchema(node, path, problems) {
|
|
29
|
+
if (typeof node !== "object" || node === null || Array.isArray(node)) return;
|
|
30
|
+
if (typeof node.required === "boolean") {
|
|
31
|
+
problems.push(`${path}.required must be an array (property-level required leaks through)`);
|
|
32
|
+
}
|
|
33
|
+
if (Object.hasOwn(node, "minimum") || Object.hasOwn(node, "maximum")) {
|
|
34
|
+
problems.push(`${path} uses minimum/maximum, which the enforced JSON Schema subset rejects`);
|
|
35
|
+
}
|
|
36
|
+
if (node.properties && typeof node.properties === "object") {
|
|
37
|
+
for (const [key, value] of Object.entries(node.properties)) {
|
|
38
|
+
walkSchema(value, `${path}.properties.${key}`, problems);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (node.items) walkSchema(node.items, `${path}.items`, problems);
|
|
42
|
+
if (Array.isArray(node.oneOf)) {
|
|
43
|
+
node.oneOf.forEach((branch, index) => walkSchema(branch, `${path}.oneOf[${index}]`, problems));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("registers seven tools with correct names", () => {
|
|
48
|
+
const { registered } = setup();
|
|
49
|
+
const names = registered.map((t) => t.name).sort();
|
|
50
|
+
assert.deepEqual(names, ["memory_archive", "memory_delete", "memory_forget", "memory_list", "memory_save", "memory_search", "memory_update"]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("compiled schemas pass the enforced DSH subset (defineTool projection)", () => {
|
|
54
|
+
const { registered } = setup();
|
|
55
|
+
assert.equal(registered.length, 7);
|
|
56
|
+
for (const tool of registered) {
|
|
57
|
+
assertSupportedJsonSchema(tool.parameters);
|
|
58
|
+
assertSupportedJsonSchema(tool.output.schema);
|
|
59
|
+
const problems = [];
|
|
60
|
+
walkSchema(tool.parameters, `parameters(${tool.name})`, problems);
|
|
61
|
+
walkSchema(tool.output.schema, `output.schema(${tool.name})`, problems);
|
|
62
|
+
assert.deepEqual(problems, []);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("memory_save executes and stores", async () => {
|
|
67
|
+
const { registered, store } = setup();
|
|
68
|
+
const save = registered.find((t) => t.name === "memory_save");
|
|
69
|
+
const result = await save.execute({ type: "preference", title: "语言", content: "中文", importance: 4 });
|
|
70
|
+
assert.equal(result.action, "created");
|
|
71
|
+
assert.equal(store.count(), 1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("memory_save merges on matching title within type", async () => {
|
|
75
|
+
const { registered, store } = setup();
|
|
76
|
+
const save = registered.find((t) => t.name === "memory_save");
|
|
77
|
+
const first = await save.execute({ type: "preference", title: "语言", content: "中文" });
|
|
78
|
+
const second = await save.execute({ type: "preference", title: "语言", content: "简体中文" });
|
|
79
|
+
assert.equal(first.action, "created");
|
|
80
|
+
assert.equal(second.action, "merged");
|
|
81
|
+
assert.equal(first.id, second.id);
|
|
82
|
+
assert.equal(store.count(), 1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("memory_search finds by CJK substring", async () => {
|
|
86
|
+
const { registered, store, service } = setup();
|
|
87
|
+
service.saveWithDedupe({ type: "project", title: "记忆插件", content: "SQLite 存储中文记忆", importance: 3 });
|
|
88
|
+
const search = registered.find((t) => t.name === "memory_search");
|
|
89
|
+
const result = await search.execute({ query: "中文" });
|
|
90
|
+
assert.ok(result.items.length >= 1);
|
|
91
|
+
assert.equal(result.items[0].title, "记忆插件");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("memory_list filters by type", async () => {
|
|
95
|
+
const { registered, service } = setup();
|
|
96
|
+
service.saveWithDedupe({ type: "preference", title: "a", content: "x" });
|
|
97
|
+
service.saveWithDedupe({ type: "project", title: "b", content: "y" });
|
|
98
|
+
const list = registered.find((t) => t.name === "memory_list");
|
|
99
|
+
const result = await list.execute({ type: "project" });
|
|
100
|
+
assert.equal(result.items.length, 1);
|
|
101
|
+
assert.equal(result.items[0].title, "b");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("memory_list total reflects the type filter", async () => {
|
|
105
|
+
const { registered, service } = setup();
|
|
106
|
+
service.saveWithDedupe({ type: "preference", title: "a", content: "x" });
|
|
107
|
+
service.saveWithDedupe({ type: "preference", title: "b", content: "y" });
|
|
108
|
+
service.saveWithDedupe({ type: "project", title: "c", content: "z" });
|
|
109
|
+
const list = registered.find((t) => t.name === "memory_list");
|
|
110
|
+
const projectRes = await list.execute({ type: "project" });
|
|
111
|
+
assert.equal(projectRes.total, 1);
|
|
112
|
+
const allRes = await list.execute({});
|
|
113
|
+
assert.equal(allRes.total, 3);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("memory_update modifies an entry", async () => {
|
|
117
|
+
const { registered, service } = setup();
|
|
118
|
+
const { memory } = service.saveWithDedupe({ type: "decision", title: "t", content: "c" });
|
|
119
|
+
const update = registered.find((t) => t.name === "memory_update");
|
|
120
|
+
const result = await update.execute({ id: memory.id, content: "updated" });
|
|
121
|
+
assert.equal(result.memory.content, "updated");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("memory_update on missing id rejects", async () => {
|
|
125
|
+
const { registered } = setup();
|
|
126
|
+
const update = registered.find((t) => t.name === "memory_update");
|
|
127
|
+
await assert.rejects(() => update.execute({ id: "missing", content: "x" }), /memory not found/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("memory_delete removes an entry", async () => {
|
|
131
|
+
const { registered, service, store } = setup();
|
|
132
|
+
const { memory } = service.saveWithDedupe({ type: "decision", title: "t", content: "c" });
|
|
133
|
+
const del = registered.find((t) => t.name === "memory_delete");
|
|
134
|
+
await del.execute({ id: memory.id });
|
|
135
|
+
assert.equal(store.count(), 0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("memory_delete on missing id returns deleted:false", async () => {
|
|
139
|
+
const { registered } = setup();
|
|
140
|
+
const del = registered.find((t) => t.name === "memory_delete");
|
|
141
|
+
const result = await del.execute({ id: "missing" });
|
|
142
|
+
assert.equal(result.deleted, false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("memory_forget suppresses injection without deleting", async () => {
|
|
146
|
+
const { registered, service, store } = setup();
|
|
147
|
+
const { memory } = service.saveWithDedupe({ type: "project", title: "t", content: "c", importance: 5 });
|
|
148
|
+
const forget = registered.find((t) => t.name === "memory_forget");
|
|
149
|
+
const result = await forget.execute({ id: memory.id });
|
|
150
|
+
assert.equal(result.memory.forgotten, true);
|
|
151
|
+
assert.equal(store.count(undefined, { includeForgotten: true }), 1, "still stored but suppressed");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("memory_forget on missing id rejects", async () => {
|
|
155
|
+
const { registered } = setup();
|
|
156
|
+
const forget = registered.find((t) => t.name === "memory_forget");
|
|
157
|
+
await assert.rejects(() => forget.execute({ id: "missing" }), /memory not found/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("memory_forget restores with forgotten:false", async () => {
|
|
161
|
+
const { registered, service } = setup();
|
|
162
|
+
const { memory } = service.saveWithDedupe({ type: "project", title: "t", content: "c", importance: 5 });
|
|
163
|
+
const forget = registered.find((t) => t.name === "memory_forget");
|
|
164
|
+
await forget.execute({ id: memory.id });
|
|
165
|
+
const restored = await forget.execute({ id: memory.id, forgotten: false });
|
|
166
|
+
assert.equal(restored.memory.forgotten, false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("execution results validate against their declared output schemas", async () => {
|
|
170
|
+
const { registered, service } = setup();
|
|
171
|
+
const byName = (n) => registered.find((t) => t.name === n);
|
|
172
|
+
|
|
173
|
+
const saved = await byName("memory_save").execute({ type: "decision", title: "x", content: "y", tags: ["k"], source: "test" });
|
|
174
|
+
assert.deepEqual(validateJsonSchemaValue(byName("memory_save").output.schema, saved), []);
|
|
175
|
+
|
|
176
|
+
service.saveWithDedupe({ type: "project", title: "p", content: "q" });
|
|
177
|
+
const searchRes = await byName("memory_search").execute({ query: "q" });
|
|
178
|
+
assert.deepEqual(validateJsonSchemaValue(byName("memory_search").output.schema, searchRes), []);
|
|
179
|
+
|
|
180
|
+
const listRes = await byName("memory_list").execute({});
|
|
181
|
+
assert.deepEqual(validateJsonSchemaValue(byName("memory_list").output.schema, listRes), []);
|
|
182
|
+
|
|
183
|
+
const updRes = await byName("memory_update").execute({ id: saved.id, content: "z" });
|
|
184
|
+
assert.deepEqual(validateJsonSchemaValue(byName("memory_update").output.schema, updRes), []);
|
|
185
|
+
|
|
186
|
+
const forgetRes = await byName("memory_forget").execute({ id: saved.id });
|
|
187
|
+
assert.deepEqual(validateJsonSchemaValue(byName("memory_forget").output.schema, forgetRes), []);
|
|
188
|
+
|
|
189
|
+
const delRes = await byName("memory_delete").execute({ id: saved.id });
|
|
190
|
+
assert.deepEqual(validateJsonSchemaValue(byName("memory_delete").output.schema, delRes), []);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("memory_search semantic mode merges vector recalls", async () => {
|
|
194
|
+
const store = createStore(":memory:");
|
|
195
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
196
|
+
const registered = [];
|
|
197
|
+
const ctx = { tools: { register(def) { registered.push(def); return () => {}; } } };
|
|
198
|
+
const embedder = { embed: async () => [1, 0, 0] };
|
|
199
|
+
createTools(ctx, service, {}, embedder);
|
|
200
|
+
|
|
201
|
+
const v = service.saveWithDedupe({ type: "preference", title: "猫", content: "喜欢猫" });
|
|
202
|
+
store.setEmbedding(v.memory.id, [1, 0, 0]);
|
|
203
|
+
service.saveWithDedupe({ type: "preference", title: "狗", content: "喜欢狗" });
|
|
204
|
+
|
|
205
|
+
const search = registered.find((t) => t.name === "memory_search");
|
|
206
|
+
// literal query matches only 猫; vector recall also surfaces it via embedding
|
|
207
|
+
const res = await search.execute({ query: "猫", semantic: true });
|
|
208
|
+
assert.ok(res.items.some((m) => m.title === "猫"), "vector + keyword merged result contains the hit");
|
|
209
|
+
// mode=vector keeps keyword items too (dedup merge)
|
|
210
|
+
const res2 = await search.execute({ query: "猫", mode: "vector" });
|
|
211
|
+
assert.ok(res2.items.some((m) => m.title === "猫"));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("memory_search falls back to keyword when embedder unavailable or returns null", async () => {
|
|
215
|
+
// No embedder passed → plain keyword search still works.
|
|
216
|
+
const { registered, service } = setup();
|
|
217
|
+
service.saveWithDedupe({ type: "preference", title: "语言", content: "中文交流" });
|
|
218
|
+
const search = registered.find((t) => t.name === "memory_search");
|
|
219
|
+
const res = await search.execute({ query: "中文", semantic: true });
|
|
220
|
+
assert.equal(res.items.length, 1);
|
|
221
|
+
assert.equal(res.items[0].title, "语言");
|
|
222
|
+
|
|
223
|
+
// Embedder that resolves null (provider disabled) → keyword fallback.
|
|
224
|
+
const store = createStore(":memory:");
|
|
225
|
+
const service2 = createService({ store, mirror: null, config: {} });
|
|
226
|
+
const registered2 = [];
|
|
227
|
+
const ctx = { tools: { register(def) { registered2.push(def); return () => {}; } } };
|
|
228
|
+
createTools(ctx, service2, {}, { embed: async () => null });
|
|
229
|
+
service2.saveWithDedupe({ type: "preference", title: "语言", content: "中文交流" });
|
|
230
|
+
const search2 = registered2.find((t) => t.name === "memory_search");
|
|
231
|
+
const res2 = await search2.execute({ query: "中文", semantic: true });
|
|
232
|
+
assert.equal(res2.items.length, 1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// --- memory_archive (item ⑤: archive + verifiable recovery) ----------------
|
|
236
|
+
|
|
237
|
+
test("memory_archive hides an entry and memory_unarchive restores it", async () => {
|
|
238
|
+
const { registered, service } = setup();
|
|
239
|
+
const { memory: m } = service.saveWithDedupe({ type: "project", title: "t", content: "c" });
|
|
240
|
+
const archive = registered.find((t) => t.name === "memory_archive");
|
|
241
|
+
const archived = await archive.execute({ id: m.id });
|
|
242
|
+
assert.equal(archived.memory.archived, true);
|
|
243
|
+
assert.ok(!service.list({ type: "project" }).some((x) => x.id === m.id), "archived hidden from default list");
|
|
244
|
+
const restored = await archive.execute({ id: m.id, archived: false });
|
|
245
|
+
assert.equal(restored.memory.archived, false);
|
|
246
|
+
assert.ok(service.list({ type: "project" }).some((x) => x.id === m.id), "restored entry visible again");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("memory_list include_archived lists hidden entries so they can be restored", async () => {
|
|
250
|
+
const { registered, service } = setup();
|
|
251
|
+
const { memory: m } = service.saveWithDedupe({ type: "preference", title: "a", content: "x" });
|
|
252
|
+
service.setArchived(m.id, true);
|
|
253
|
+
const list = registered.find((t) => t.name === "memory_list");
|
|
254
|
+
const hidden = await list.execute({ type: "preference", include_archived: true });
|
|
255
|
+
assert.ok(hidden.items.some((x) => x.id === m.id), "archived entry discoverable");
|
|
256
|
+
assert.equal(hidden.total, 1, "total counts the archived entry");
|
|
257
|
+
const visible = await list.execute({ type: "preference" });
|
|
258
|
+
assert.ok(!visible.items.some((x) => x.id === m.id), "archived entry hidden by default");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("memory_archive on missing id rejects", async () => {
|
|
262
|
+
const { registered } = setup();
|
|
263
|
+
const archive = registered.find((t) => t.name === "memory_archive");
|
|
264
|
+
await assert.rejects(() => archive.execute({ id: "missing" }), /memory not found/);
|
|
265
|
+
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
# 发布路径:registry.npmjs.org 在本机云服务器被出口 SNI 过滤 + 17897 隧道不稳定,
|
|
4
|
-
# 改由 GitHub runner 发布(不依赖本机出网)。打 v* tag 自动触发,或 workflow_dispatch 手动。
|
|
5
|
-
#
|
|
6
|
-
# ⚠️ 版本号机制(血的教训):
|
|
7
|
-
# root package.json 在 git 里永远停在 0.7.2,真实版本号在 dsh-mneme/package.json。
|
|
8
|
-
# npm 在跑 prepublishOnly 之前就已加载 manifest,脚本里改 root 版本不生效,
|
|
9
|
-
# 会把 root 的旧版本(0.7.2)发出去。必须在 npm publish 之前显式同步 root 版本。
|
|
10
|
-
|
|
11
|
-
on:
|
|
12
|
-
push:
|
|
13
|
-
tags:
|
|
14
|
-
- 'v*'
|
|
15
|
-
workflow_dispatch:
|
|
16
|
-
|
|
17
|
-
permissions:
|
|
18
|
-
contents: read
|
|
19
|
-
|
|
20
|
-
jobs:
|
|
21
|
-
publish:
|
|
22
|
-
runs-on: ubuntu-latest
|
|
23
|
-
steps:
|
|
24
|
-
- uses: actions/checkout@v4
|
|
25
|
-
|
|
26
|
-
- uses: actions/setup-node@v4
|
|
27
|
-
with:
|
|
28
|
-
node-version: 24
|
|
29
|
-
registry-url: https://registry.npmjs.org/
|
|
30
|
-
|
|
31
|
-
- name: Registry current state (diagnostic)
|
|
32
|
-
run: |
|
|
33
|
-
npm view @modusensus/dsh-mneme versions --json || true
|
|
34
|
-
npm view @modusensus/dsh-mneme time --json || true
|
|
35
|
-
|
|
36
|
-
# 关键:先同步 root 版本 → 再 publish(prepublishOnly 改版本不可靠)
|
|
37
|
-
- name: Sync root version from dsh-mneme
|
|
38
|
-
run: |
|
|
39
|
-
V="$(node -p "require('./dsh-mneme/package.json').version")"
|
|
40
|
-
echo "Syncing root package.json to v${V}"
|
|
41
|
-
node -e "const fs=require('fs');const v=process.argv[1];const p=JSON.parse(fs.readFileSync('./package.json','utf8'));p.version=v;fs.writeFileSync('./package.json',JSON.stringify(p,null,2)+'\n')" "$V"
|
|
42
|
-
node -p "console.log('root now:', require('./package.json').version)"
|
|
43
|
-
|
|
44
|
-
- name: Publish @modusensus/dsh-mneme (skip if exists)
|
|
45
|
-
run: |
|
|
46
|
-
V="$(node -p "require('./dsh-mneme/package.json').version")"
|
|
47
|
-
if npm view "@modusensus/dsh-mneme@${V}" version >/dev/null 2>&1; then
|
|
48
|
-
echo "v${V} already published on registry — skipping"
|
|
49
|
-
else
|
|
50
|
-
npm publish
|
|
51
|
-
fi
|
|
52
|
-
env:
|
|
53
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
54
|
-
|
|
55
|
-
- name: Verify published version
|
|
56
|
-
run: |
|
|
57
|
-
V="$(node -p "require('./dsh-mneme/package.json').version")"
|
|
58
|
-
echo "Published: $(npm view "@modusensus/dsh-mneme@${V}" version)"
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Test
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master, main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master, main]
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
test:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
node-version: [24]
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
- uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: ${{ matrix.node-version }}
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
working-directory: dsh-mneme
|
|
23
|
-
run: npm ci
|
|
24
|
-
- name: Run tests
|
|
25
|
-
working-directory: dsh-mneme
|
|
26
|
-
run: npm run test:coverage
|
|
27
|
-
- name: Upload coverage to Codecov
|
|
28
|
-
uses: codecov/codecov-action@v4
|
|
29
|
-
with:
|
|
30
|
-
token: ${{ secrets.CODECOV_TOKEN }}
|
|
31
|
-
files: dsh-mneme/coverage/lcov.info
|
|
32
|
-
fail_ci_if_error: false
|
package/CHANGELOG.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
> **完整版本历史见 [dsh-mneme/CHANGELOG.md](dsh-mneme/CHANGELOG.md)**(正式版本正源,0.3.8 → 0.7.9)。以下为仓库早期开发记录(0.1.0–0.2.x,2026-08-13~14,已归档)。
|
|
4
|
-
|
|
5
|
-
All notable changes to dsh-mneme are documented here.
|
|
6
|
-
|
|
7
|
-
## [Unreleased]
|
|
8
|
-
|
|
9
|
-
- **诚实审计修复(v0.2.7,F-03)**
|
|
10
|
-
- autoDream 决策无变更且 summary 为空时,不再误报 ok:true——新增 `noop` 状态(ok:false,baseline 不刷新),避免空跑循环
|
|
11
|
-
- 有变更但 summary 缺失 → `degraded`(ok:true 但如实标记 summary 缺失)
|
|
12
|
-
- parseReceipt 支持 ok/noop/degraded/reconcile/failed
|
|
13
|
-
- 测试 259 → **263**
|
|
14
|
-
|
|
15
|
-
- **社区贡献 · 嵌入式面板 UI 改进(PR #2,@Liuxin4950)**
|
|
16
|
-
- 记忆面板与设置面板的嵌入模式(settings.section 插槽)不再复用弹窗面板样式(去掉 boxShadow / background / borderRadius / padding),改为全宽平铺渲染,消除 DSH 设置页内多余的悬浮卡片与阴影
|
|
17
|
-
- 弹窗(portal)模式保持原有面板样式不变;新增回归测试锁定 embedded 分支不携带 modal chrome
|
|
18
|
-
|
|
19
|
-
- **安全审计修复(v0.2.5)**
|
|
20
|
-
- 并发安全:CAS 冲突守卫——过期快照不再覆盖并发写入(防丢更新)
|
|
21
|
-
- 事务化决策应用:merge/archive 原子提交,receipt 反映已提交子步骤,部分提交 = reconcile(绝不虚报 ok)
|
|
22
|
-
- 压测硬断言:lost-update 与多步原子性失败即非零退出(`npm run stress`)
|
|
23
|
-
- 运行时人工编辑三方合并(three-way),不再静默覆盖
|
|
24
|
-
- 新增 `memory_archive` 工具(第 7 个模型工具)+ `memory_list include_archived`,归档可恢复
|
|
25
|
-
- reranker 改为 opt-in:`rerankEnabled=false`、`rerankProvider=none` 默认(裸装不加载 onnxruntime)
|
|
26
|
-
- npm `files` 纳入 scripts/test,tarball 内可直接跑压测
|
|
27
|
-
- 测试 236 → **258**(+22)
|
|
28
|
-
|
|
29
|
-
- **安全加固(v0.2.4)**:API 鉴权 `apiToken`(写操作与密钥端点要求 Bearer 校验)+ `apiKey` 掩码回传 + timing-safe token 比对
|
|
30
|
-
|
|
31
|
-
- **审查修复补丁(v0.2.3)**
|
|
32
|
-
- failure 记录增加 `before` JSON 快照(title/content/importance 变更可追溯,不只 content)
|
|
33
|
-
- vector-index `rebuildIndex` guard 改为检查 `embedSingle`(修复 embed/embedSingle 不一致导致的静默跳过)
|
|
34
|
-
|
|
35
|
-
- **流水线补全(v0.2.2)**
|
|
36
|
-
- reflection 修复:failure 记录检查 title/importance 变化(不只 content);支持 query 上下文(memory_update 加 `reason` 参数);update 校验失败不污染 claimed;`failure_memories` 清理(`deleteOldFailures` + 启动自动清理 90 天前)
|
|
37
|
-
- 专项测试补全:`vector-index.test.js`(modelHash 漂移/重建/增量)+ `service-search.test.js`(hybrid/auto/rerank 端到端)
|
|
38
|
-
- api.js `/search` 的 mode 参数文档化
|
|
39
|
-
- 测试 212 → **233**
|
|
40
|
-
|
|
41
|
-
- **反思更新(v0.2.1)**:`update` 决策 + 失败追踪
|
|
42
|
-
- autoDream 新增 `update` 决策类型:修正单条记忆的过时/错误内容(单 id、必须实际变化、非 summary、24h 保护、每次 ≤2)
|
|
43
|
-
- 审计记录 update 的 `_before` 快照;update 后向量索引同步
|
|
44
|
-
- `failure_memories` 表:记录用户纠正(user_correction)+ 查询/统计接口
|
|
45
|
-
- 配置:`reflectionUpdateEnabled` / `reflectionFailureTracking` / `reflectionUpdateMaxPerRun` / `reflectionUpdateMinAgeHours`
|
|
46
|
-
|
|
47
|
-
- **Semantic 升级(v0.2.0-semantic)**:完全离线语义记忆引擎
|
|
48
|
-
- 本地 Embedding:`embedProvider: local`(ONNX bge-small-zh-v1.5,transformers.js/onnxruntime)或 `ollama`;原 `openai` 外部 API 保留为默认,向后兼容
|
|
49
|
-
- 向量索引层:`vector_meta` 模型指纹追踪 + 索引统计(`/api/dsh-mneme/semantic`)
|
|
50
|
-
- Rerank 精排:`rerankEnabled`(bge-reranker-base cross-encoder),召回后精排 Top-K,失败自动跳过
|
|
51
|
-
- 混合搜索:`memory_search` 新增 `mode: hybrid`(向量优先 + 关键词补位);`auto` 保持关键词优先 + 向量补位
|
|
52
|
-
- autoDream 语义增强:K-Means 聚类预分组(k-means++)、`[潜在冲突]` 向量相似度标记、整理后向量索引重建
|
|
53
|
-
- 模型下载:断点续传 + 缓存(transformers.js 内置);`.npmrc` 跳过 onnxruntime CUDA 下载避免安装失败
|
|
54
|
-
- 文档:`docs/SEMANTIC.md` / `docs/LOCAL_MODEL.md` / `docs/MIGRATION.md` + 基准脚本 `benchmark-embed` / `benchmark-rerank`
|
|
55
|
-
- autoDream 裁决审计:每次运行写入 `dream_runs`(输入快照 sha256 digest + 完整输入快照 + LLM 决策清单 + 逐 id 去向 + receipt `dsh-mneme:run:<id>:<status>:<hash>:<count>:<applied>`),可离线回放、定位静默错误
|
|
56
|
-
- 幂等决策应用:merge / conflict 重复应用无累积副作用(来源注释不重复追加),防并发/重放下的重复合并
|
|
57
|
-
- 三轴线压测 `npm run stress`:长会话检索(Recall@k、陈旧残留率)/ 冲突裁决(可重放仲裁集)/ 多 Agent 并发(丢更新、重复合并、事务/崩溃恢复)
|
|
58
|
-
|
|
59
|
-
## [0.1.6] - 2026-08-14
|
|
60
|
-
|
|
61
|
-
- Vector (semantic) search via OpenAI-compatible embeddings endpoint, with automatic fallback to LIKE keyword search on failure
|
|
62
|
-
- Web GUI memory panel: browse by type, full-text + semantic search
|
|
63
|
-
|
|
64
|
-
## [0.1.5] - 2026-08-14
|
|
65
|
-
|
|
66
|
-
- autoDream consolidation refinements: conflict resolution with source tracking, fail-safe decision validation
|
|
67
|
-
|
|
68
|
-
## [0.1.4] - 2026-08-14
|
|
69
|
-
|
|
70
|
-
- User settings: profile + behavior rules injected every turn
|
|
71
|
-
- Custom slash commands (register, route to agent)
|
|
72
|
-
|
|
73
|
-
## [0.1.3] - 2026-08-14
|
|
74
|
-
|
|
75
|
-
- autoDream background consolidation: dedup / merge / archive / conflict resolution
|
|
76
|
-
|
|
77
|
-
## [0.1.2] - 2026-08-14
|
|
78
|
-
|
|
79
|
-
- Session summarization: auto-distill preferences / decisions / lessons at session end
|
|
80
|
-
- Web panel improvements
|
|
81
|
-
|
|
82
|
-
## [0.1.1] - 2026-08-13
|
|
83
|
-
|
|
84
|
-
- Memory tools: memory_save / memory_search / memory_list / memory_update / memory_delete / memory_forget
|
|
85
|
-
- Markdown mirror sync (human-editable, manual edits take priority)
|
|
86
|
-
|
|
87
|
-
## [0.1.0] - 2026-08-13
|
|
88
|
-
|
|
89
|
-
- Initial release: cross-session memory for DeepSeek Harness
|
|
90
|
-
- SQLite store (node:sqlite, zero native deps) + human-editable Markdown mirror
|
|
91
|
-
- Auto-injection of relevant memories at session start
|