@deepmaid/maid-memory-local 0.0.0-stage → 0.1.0-alpha.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kenyonxu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/lib/index.js ADDED
@@ -0,0 +1,164 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ //#region src/index.ts
5
+ const name = "maid-memory-local";
6
+ const inject = ["maidMemory"];
7
+ /**
8
+ * 策展扫描合成集(M3b 任务 10,铁律 7:只用 Doris/合成内容):global+null / global+已标注 /
9
+ * personal 混排的 canned 假件集,scanUnsorted 只回 global+null 子集(与 mslm 服务端显式筛选
10
+ * 同语义)。导出供契约测试推导期望集;ns 无关(local 是降级假件,无真实 global 层可盘)。
11
+ */
12
+ const SYNTHETIC_UNSORTED_SCAN = [
13
+ {
14
+ id: "synthetic-global-fresh",
15
+ content: "合成示例:主人吃辣时配冰牛奶。",
16
+ scope: "global"
17
+ },
18
+ {
19
+ id: "synthetic-global-world",
20
+ content: "合成示例:深蓝馆一层是大厅。",
21
+ scope: "global"
22
+ },
23
+ {
24
+ id: "synthetic-global-tagged",
25
+ content: "合成示例:已标注的世界事实,不该再进扫描集。",
26
+ scope: "global",
27
+ provenanceKind: "world"
28
+ },
29
+ {
30
+ id: "synthetic-personal",
31
+ content: "合成示例:个人记忆,不属共享层。",
32
+ scope: "personal"
33
+ }
34
+ ];
35
+ const apply = (ctx, config) => {
36
+ if (!config?.dataFile) throw new Error("maid-memory-local: config.dataFile 必填");
37
+ ctx.maidMemory.registerProvider(createLocalMemoryProvider(config));
38
+ };
39
+ function tokenize(text) {
40
+ const lower = text.toLowerCase();
41
+ return [...lower.match(/[a-z0-9]+/g) ?? [], ...lower.match(/[\u4e00-\u9fff]/g) ?? []];
42
+ }
43
+ function contentHash(record, ns) {
44
+ return createHash("sha256").update(`${ns}\0${record.title}\0${record.content}`).digest("hex");
45
+ }
46
+ /** local 降级假件:自包含 JSONL + 内存关键词打分(规格 §7.1;有意保持简单,不做向量/BM25)。 */
47
+ function createLocalMemoryProvider(config) {
48
+ const file = config.dataFile;
49
+ const synthetic = SYNTHETIC_UNSORTED_SCAN.map((f) => ({ ...f }));
50
+ const rewriteFile = (kept) => {
51
+ mkdirSync(dirname(file), { recursive: true });
52
+ const tmp = `${file}.tmp`;
53
+ writeFileSync(tmp, kept.map((r) => JSON.stringify(r)).join("\n") + (kept.length > 0 ? "\n" : ""));
54
+ renameSync(tmp, file);
55
+ };
56
+ const load = () => {
57
+ if (!existsSync(file)) return [];
58
+ const out = [];
59
+ for (const line of readFileSync(file, "utf8").split("\n")) {
60
+ if (line.trim().length === 0) continue;
61
+ try {
62
+ out.push(JSON.parse(line));
63
+ } catch {}
64
+ }
65
+ return out;
66
+ };
67
+ const append = (records) => {
68
+ mkdirSync(dirname(file), { recursive: true });
69
+ appendFileSync(file, records.map((r) => JSON.stringify(r)).join("\n") + "\n");
70
+ };
71
+ return {
72
+ id: "local",
73
+ capabilities: {
74
+ forget: true,
75
+ sharedScopes: false,
76
+ supportsRecent: true
77
+ },
78
+ status: async () => "ready",
79
+ async recall(ns, query, limit) {
80
+ const tokens = new Set(tokenize(query));
81
+ const hits = [];
82
+ for (const record of load()) {
83
+ if (record.ns !== ns) continue;
84
+ let score = 0;
85
+ for (const token of tokens) {
86
+ if (record.tags.some((t) => tokenize(t).includes(token))) score += 3;
87
+ if (tokenize(record.title).includes(token)) score += 2;
88
+ if (tokenize(record.content).includes(token)) score += 1;
89
+ }
90
+ if (score > 0) hits.push({
91
+ id: record.id,
92
+ title: record.title,
93
+ content: record.content,
94
+ score,
95
+ createdAt: record.createdAt
96
+ });
97
+ }
98
+ hits.sort((a, b) => b.score - a.score);
99
+ return hits.slice(0, limit);
100
+ },
101
+ async recent(ns, limit) {
102
+ return load().filter((r) => r.ns === ns).slice(-limit).reverse().map((r) => ({
103
+ id: r.id,
104
+ title: r.title,
105
+ content: r.content,
106
+ createdAt: r.createdAt
107
+ }));
108
+ },
109
+ async write(ns, records, opts) {
110
+ const existing = load();
111
+ const receipts = [];
112
+ const fresh = [];
113
+ for (const record of records) {
114
+ const hash = contentHash(record, ns);
115
+ const found = existing.find((r) => r.hash === hash) ?? fresh.find((r) => r.hash === hash);
116
+ if (found !== void 0) {
117
+ receipts.push({
118
+ id: found.id,
119
+ deduped: true,
120
+ ...opts?.scope !== void 0 ? { scope: opts.scope } : {}
121
+ });
122
+ continue;
123
+ }
124
+ const id = randomUUID();
125
+ fresh.push({
126
+ id,
127
+ ns,
128
+ title: record.title,
129
+ content: record.content,
130
+ tags: [...record.tags],
131
+ importance: record.importance,
132
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
133
+ hash
134
+ });
135
+ receipts.push({
136
+ id,
137
+ deduped: false,
138
+ ...opts?.scope !== void 0 ? { scope: opts.scope } : {}
139
+ });
140
+ }
141
+ if (fresh.length > 0) append(fresh);
142
+ return receipts;
143
+ },
144
+ async forget(ns, recordId) {
145
+ const kept = load().filter((r) => !(r.ns === ns && r.id === recordId));
146
+ const syntheticIdx = synthetic.findIndex((f) => f.id === recordId);
147
+ if (syntheticIdx >= 0) synthetic.splice(syntheticIdx, 1);
148
+ rewriteFile(kept);
149
+ },
150
+ async scanUnsorted() {
151
+ return synthetic.filter((f) => f.scope === "global" && f.provenanceKind === void 0);
152
+ },
153
+ async update(_ns, factId, opts) {
154
+ const target = synthetic.find((f) => f.id === factId);
155
+ if (target === void 0) throw new Error(`maid-memory-local: update 找不到合成集条目 ${factId}`);
156
+ if (opts.content !== void 0) target.content = opts.content;
157
+ if (opts.scope !== void 0) target.scope = opts.scope;
158
+ if (opts.provenanceKind !== void 0) target.provenanceKind = opts.provenanceKind;
159
+ },
160
+ async dispose() {}
161
+ };
162
+ }
163
+ //#endregion
164
+ export { SYNTHETIC_UNSORTED_SCAN, apply, createLocalMemoryProvider, inject, name };
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "@deepmaid/maid-memory-local",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0-alpha.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js"
9
+ },
10
+ "files": [
11
+ "lib"
12
+ ],
13
+ "devDependencies": {
14
+ "tsdown": "0.22.14",
15
+ "typescript": "6.0.3"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsdown"
22
+ }
6
23
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.