@scotthuang/engram 0.5.9 → 0.6.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.
Files changed (47) hide show
  1. package/README.md +42 -0
  2. package/dist/face/src/face-api.d.ts +36 -0
  3. package/dist/face/src/face-api.js +90 -0
  4. package/dist/face/src/face-api.js.map +1 -0
  5. package/dist/face/src/face-store.d.ts +101 -0
  6. package/dist/face/src/face-store.js +361 -0
  7. package/dist/face/src/face-store.js.map +1 -0
  8. package/dist/src/__tests__/bm25.test.d.ts +1 -0
  9. package/dist/src/__tests__/bm25.test.js +198 -0
  10. package/dist/src/__tests__/bm25.test.js.map +1 -0
  11. package/dist/src/__tests__/config.test.d.ts +1 -0
  12. package/dist/src/__tests__/config.test.js +31 -0
  13. package/dist/src/__tests__/config.test.js.map +1 -0
  14. package/dist/src/__tests__/profile.test.d.ts +1 -0
  15. package/dist/src/__tests__/profile.test.js +130 -0
  16. package/dist/src/__tests__/profile.test.js.map +1 -0
  17. package/dist/src/__tests__/recall.test.d.ts +1 -0
  18. package/dist/src/__tests__/recall.test.js +162 -0
  19. package/dist/src/__tests__/recall.test.js.map +1 -0
  20. package/dist/src/bm25.d.ts +60 -0
  21. package/dist/src/bm25.js +271 -0
  22. package/dist/src/bm25.js.map +1 -0
  23. package/dist/src/config.d.ts +47 -0
  24. package/dist/src/config.js +83 -0
  25. package/dist/src/config.js.map +1 -0
  26. package/dist/src/image-store.d.ts +146 -0
  27. package/dist/src/image-store.js +418 -0
  28. package/dist/src/image-store.js.map +1 -0
  29. package/dist/src/index.d.ts +7 -0
  30. package/dist/src/index.js +1236 -0
  31. package/dist/src/index.js.map +1 -0
  32. package/dist/src/logger.d.ts +32 -0
  33. package/dist/src/logger.js +106 -0
  34. package/dist/src/logger.js.map +1 -0
  35. package/dist/src/profile.d.ts +37 -0
  36. package/dist/src/profile.js +107 -0
  37. package/dist/src/profile.js.map +1 -0
  38. package/dist/src/recall.d.ts +98 -0
  39. package/dist/src/recall.js +729 -0
  40. package/dist/src/recall.js.map +1 -0
  41. package/dist/src/settle.d.ts +83 -0
  42. package/dist/src/settle.js +687 -0
  43. package/dist/src/settle.js.map +1 -0
  44. package/dist/src/vector.d.ts +67 -0
  45. package/dist/src/vector.js +284 -0
  46. package/dist/src/vector.js.map +1 -0
  47. package/package.json +3 -3
@@ -0,0 +1,198 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { BM25Index } from "../bm25.js";
3
+ describe("BM25Index", () => {
4
+ describe("tokenize (via search)", () => {
5
+ const index = new BM25Index();
6
+ it("finds Chinese bigram matches", async () => {
7
+ await index.addEntry({ text: "在体育西吃了潮汕牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
8
+ await index.addEntry({ text: "讨论了记忆系统方案", date: "2026-03-15", category: "技术", filePath: "/test" });
9
+ const results = await index.search("牛肉火锅", 2);
10
+ expect(results).toHaveLength(1);
11
+ expect(results[0].entry.category).toBe("饮食");
12
+ });
13
+ it("finds English word matches", async () => {
14
+ await index.addEntry({ text: "installed node modules successfully", date: "2026-03-15", category: "技术", filePath: "/test" });
15
+ await index.addEntry({ text: "天气很好适合出门", date: "2026-03-15", category: "随聊", filePath: "/test" });
16
+ const results = await index.search("node modules", 2);
17
+ expect(results).toHaveLength(1);
18
+ expect(results[0].entry.category).toBe("技术");
19
+ });
20
+ it("returns empty for no matches", async () => {
21
+ await index.addEntry({ text: "今天天气很好", date: "2026-03-15", category: "随聊", filePath: "/test" });
22
+ const results = await index.search("量子力学", 3);
23
+ expect(results).toHaveLength(0);
24
+ });
25
+ it("tokenizes stock query correctly", async () => {
26
+ const index = new BM25Index();
27
+ // Add the daily memory entry that has the stock preference
28
+ await index.addEntry({
29
+ text: "查股票或画K线图时必须默认使用 akshare-stock skill(~/.openclaw/workspace/skills/akshare-stock/scripts/stock_kline.py),不知道股票代码时用百度搜索查询。",
30
+ date: "2026-03-17",
31
+ category: "偏好",
32
+ filePath: "/test/2026-03-17.md"
33
+ });
34
+ await index.addEntry({
35
+ text: "腾讯股票代码是 00700",
36
+ date: "2026-03-17",
37
+ category: "技术",
38
+ filePath: "/test"
39
+ });
40
+ // Search for the user's query "你去画一条腾讯的K线图"
41
+ const query = "你去画一条腾讯的K线图";
42
+ const results = await index.search(query, 5);
43
+ // Log for debugging
44
+ console.log("\n----- BM25 检索日志 -----");
45
+ console.log("Query:", query);
46
+ // Log tokens and results - follow the same pattern as in bm25.ts
47
+ const mod = await import("jieba-wasm");
48
+ const jiebaInstance = await mod.default;
49
+ // @ts-ignore
50
+ console.log("Jieba 分词结果:", jiebaInstance.cut(query));
51
+ console.log("召回结果(按得分降序):");
52
+ results.forEach((r, i) => {
53
+ console.log(`${i + 1}. [${r.entry.category}] score=${r.score.toFixed(4)} → ${r.entry.text}`);
54
+ });
55
+ console.log("------------------------\n");
56
+ // Both entries should match
57
+ expect(results.length).toBe(2);
58
+ // The "腾讯股票代码" entry has exact "腾讯" match in a shorter doc → higher BM25 score
59
+ // This is actually the correct behavior, since we're looking for a stock about Tencent
60
+ expect(results[0].entry.category).toBe("技术");
61
+ expect(results[1].entry.category).toBe("偏好");
62
+ // Both entries contain matching tokens:
63
+ // Query tokens: 你 去 画 一条 腾讯 的 K线 图 → jieba tokenizes correctly
64
+ // Preference entry matches: 画, K线, 股票 → relevant to the query intent
65
+ });
66
+ });
67
+ describe("search", () => {
68
+ it("returns results sorted by score", async () => {
69
+ const index = new BM25Index();
70
+ await index.addEntry({ text: "牛肉火锅牛肉火锅牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
71
+ await index.addEntry({ text: "吃过一次牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
72
+ await index.addEntry({ text: "今天去跑步了", date: "2026-03-15", category: "健康", filePath: "/test" });
73
+ const results = await index.search("牛肉火锅", 3);
74
+ expect(results.length).toBeGreaterThanOrEqual(2);
75
+ // 更高频率的应排在前面
76
+ expect(results[0].score).toBeGreaterThanOrEqual(results[1].score);
77
+ });
78
+ it("respects topK limit", async () => {
79
+ const index = new BM25Index();
80
+ for (let i = 0; i < 10; i++) {
81
+ await index.addEntry({ text: `测试条目${i} 包含关键词`, date: "2026-03-15", category: "测试", filePath: "/test" });
82
+ }
83
+ const results = await index.search("关键词", 3);
84
+ expect(results.length).toBeLessThanOrEqual(3);
85
+ });
86
+ });
87
+ describe("addEntry", () => {
88
+ it("updates index size", async () => {
89
+ const index = new BM25Index();
90
+ expect(index.size).toBe(0);
91
+ await index.addEntry({ text: "test entry", date: "2026-03-15", category: "测试", filePath: "/test" });
92
+ expect(index.size).toBe(1);
93
+ await index.addEntry({ text: "another entry", date: "2026-03-15", category: "测试", filePath: "/test" });
94
+ expect(index.size).toBe(2);
95
+ });
96
+ });
97
+ describe("parseEntries", () => {
98
+ it("parses structured format correctly", async () => {
99
+ const index = new BM25Index();
100
+ // buildFromDirectory 会调用 parseEntries
101
+ // 直接测试 search 验证解析结果
102
+ const mockContent = `# 2026-03-15
103
+
104
+ ### 19:30 [饮食]
105
+ 在体育西吃了潮汕牛肉火锅,胸口捞很好吃
106
+
107
+ ### 14:00 [技术]
108
+ 讨论了记忆系统方案,确定三层架构
109
+
110
+ ### 10:00 [随聊]
111
+ 聊了股票
112
+ `;
113
+ // 使用 addEntry 模拟解析后的结果
114
+ await index.addEntry({ text: "在体育西吃了潮汕牛肉火锅,胸口捞很好吃", date: "2026-03-15", category: "饮食", filePath: "/test" });
115
+ await index.addEntry({ text: "讨论了记忆系统方案,确定三层架构", date: "2026-03-15", category: "技术", filePath: "/test" });
116
+ // 搜饮食
117
+ const foodResults = await index.search("牛肉火锅", 1);
118
+ expect(foodResults).toHaveLength(1);
119
+ expect(foodResults[0].entry.category).toBe("饮食");
120
+ // 搜技术
121
+ const techResults = await index.search("记忆系统", 1);
122
+ expect(techResults).toHaveLength(1);
123
+ expect(techResults[0].entry.category).toBe("技术");
124
+ });
125
+ });
126
+ describe("buildFromTestFile", () => {
127
+ it("builds index from test/2026-03-17.md and tests search", async () => {
128
+ const index = new BM25Index();
129
+ // Read the test file
130
+ const fs = require('fs');
131
+ const path = require('path');
132
+ const filePath = path.join(__dirname, '../../test/2026-03-17.md');
133
+ const content = fs.readFileSync(filePath, 'utf8');
134
+ // Parse all entries from the file
135
+ const lines = content.split('\n');
136
+ let currentEntry = null;
137
+ for (const line of lines) {
138
+ const headerMatch = line.match(/^###\s+\d{2}:\d{2}\s+\[([^\]]+)\]/);
139
+ if (headerMatch) {
140
+ // Save previous entry if exists
141
+ if (currentEntry && currentEntry.text.trim().length > 0) {
142
+ await index.addEntry({
143
+ text: currentEntry.text.trim(),
144
+ category: currentEntry.category,
145
+ date: "2026-03-17",
146
+ filePath,
147
+ });
148
+ }
149
+ // Start new entry
150
+ currentEntry = {
151
+ category: headerMatch[1],
152
+ date: "2026-03-17",
153
+ text: "",
154
+ };
155
+ }
156
+ else if (currentEntry) {
157
+ currentEntry.text += line + '\n';
158
+ }
159
+ }
160
+ // Add the last entry
161
+ if (currentEntry && currentEntry.text.trim().length > 0) {
162
+ await index.addEntry({
163
+ text: currentEntry.text.trim(),
164
+ category: currentEntry.category,
165
+ date: "2026-03-17",
166
+ filePath,
167
+ });
168
+ }
169
+ console.log("\n====== Test from file 2026-03-17.md ======");
170
+ console.log(`Total entries parsed: ${index.size}`);
171
+ console.log("==========================================\n");
172
+ // Test search for "bm25 检索"
173
+ const query1 = "bm25 检索 分词";
174
+ const results1 = await index.search(query1, 5);
175
+ console.log("\n----- Test search: 'bm25 检索 分词' -----");
176
+ console.log(`Found ${results1.length} results:`);
177
+ results1.forEach((r, i) => {
178
+ const preview = r.entry.text.length > 60 ? r.entry.text.substring(0, 60) + "..." : r.entry.text;
179
+ console.log(`${i + 1}. [${r.entry.category}] score=${r.score.toFixed(4)} → ${preview}`);
180
+ });
181
+ console.log("------------------------\n");
182
+ // Should find entries about BM25
183
+ expect(results1.length).toBeGreaterThan(0);
184
+ // Test search for "腾讯股票 K线"
185
+ const query2 = "腾讯股票 K线";
186
+ const results2 = await index.search(query2, 5);
187
+ console.log("\n----- Test search: '腾讯股票 K线' -----");
188
+ console.log(`Found ${results2.length} results:`);
189
+ results2.forEach((r, i) => {
190
+ const preview = r.entry.text.length > 60 ? r.entry.text.substring(0, 60) + "..." : r.entry.text;
191
+ console.log(`${i + 1}. [${r.entry.category}] score=${r.score.toFixed(4)} → ${preview}`);
192
+ });
193
+ console.log("------------------------\n");
194
+ expect(results2.length).toBeGreaterThan(0);
195
+ });
196
+ });
197
+ });
198
+ //# sourceMappingURL=bm25.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bm25.test.js","sourceRoot":"","sources":["../../../src/__tests__/bm25.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseConfig, DEFAULTS } from "../config.js";
3
+ describe("parseConfig", () => {
4
+ it("returns defaults when no config provided", () => {
5
+ const config = parseConfig();
6
+ expect(config.shortTermDays).toBe(DEFAULTS.shortTermDays);
7
+ expect(config.halfLifeDays).toBe(DEFAULTS.halfLifeDays);
8
+ expect(config.recallTopK).toBe(DEFAULTS.recallTopK);
9
+ expect(config.minScore).toBe(DEFAULTS.minScore);
10
+ expect(config.vectorWeight).toBe(DEFAULTS.vectorWeight);
11
+ expect(config.textWeight).toBe(DEFAULTS.textWeight);
12
+ });
13
+ it("returns defaults when empty object provided", () => {
14
+ const config = parseConfig({});
15
+ expect(config.shortTermDays).toBe(7);
16
+ expect(config.halfLifeDays).toBe(30);
17
+ });
18
+ it("overrides defaults with provided values", () => {
19
+ const config = parseConfig({ shortTermDays: 14, minScore: 0.5 });
20
+ expect(config.shortTermDays).toBe(14);
21
+ expect(config.minScore).toBe(0.5);
22
+ // 其他值保持默认
23
+ expect(config.halfLifeDays).toBe(DEFAULTS.halfLifeDays);
24
+ });
25
+ it("ignores invalid types and uses defaults", () => {
26
+ const config = parseConfig({ shortTermDays: "abc", halfLifeDays: null });
27
+ expect(config.shortTermDays).toBe(DEFAULTS.shortTermDays);
28
+ expect(config.halfLifeDays).toBe(DEFAULTS.halfLifeDays);
29
+ });
30
+ });
31
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAErD,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,UAAU;QACV,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,aAAa,EAAE,KAAY,EAAE,YAAY,EAAE,IAAW,EAAE,CAAC,CAAC;QACvF,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,130 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { ProfileManager, EMPTY_PROFILE } from "../profile.js";
3
+ import { promises as fs } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ describe("ProfileManager", () => {
7
+ let manager;
8
+ let tempDir;
9
+ beforeEach(async () => {
10
+ tempDir = join(tmpdir(), `engram-test-${Date.now()}`);
11
+ await fs.mkdir(tempDir, { recursive: true });
12
+ manager = new ProfileManager(tempDir);
13
+ });
14
+ describe("load", () => {
15
+ it("returns empty profile when no file exists", async () => {
16
+ const profile = await manager.load();
17
+ expect(profile.summary).toBe("");
18
+ expect(profile.coreTags).toEqual([]);
19
+ expect(profile.tags).toEqual({});
20
+ });
21
+ it("loads existing profile from file", async () => {
22
+ const profileDir = join(tempDir, "memory", "profile");
23
+ await fs.mkdir(profileDir, { recursive: true });
24
+ const saved = { ...EMPTY_PROFILE, summary: "test summary", coreTags: ["tag1"] };
25
+ await fs.writeFile(join(profileDir, "semantic_profile.json"), JSON.stringify(saved));
26
+ // 创建新的 manager 实例测试从文件加载
27
+ const manager2 = new ProfileManager(tempDir);
28
+ const loaded = await manager2.load();
29
+ expect(loaded.summary).toBe("test summary");
30
+ expect(loaded.coreTags).toEqual(["tag1"]);
31
+ });
32
+ });
33
+ describe("addTag", () => {
34
+ it("adds a new tag to a new dimension", async () => {
35
+ const profile = { ...EMPTY_PROFILE };
36
+ const result = manager.addTag(profile, "口味偏好", "喜欢辣");
37
+ expect(result.tags["口味偏好"]).toHaveLength(1);
38
+ expect(result.tags["口味偏好"][0].value).toBe("喜欢辣");
39
+ expect(result.tags["口味偏好"][0].confidence).toBe(0.7);
40
+ });
41
+ it("increases confidence for existing tag", async () => {
42
+ const profile = {
43
+ ...EMPTY_PROFILE,
44
+ tags: { 口味偏好: [{ value: "喜欢辣", confidence: 0.5, lastSeen: "2026-01-01" }] },
45
+ };
46
+ const result = manager.addTag(profile, "口味偏好", "喜欢辣");
47
+ expect(result.tags["口味偏好"]).toHaveLength(1);
48
+ expect(result.tags["口味偏好"][0].confidence).toBeCloseTo(0.6);
49
+ });
50
+ it("adds multiple tags to same dimension", async () => {
51
+ const profile = { ...EMPTY_PROFILE };
52
+ manager.addTag(profile, "口味偏好", "喜欢辣");
53
+ manager.addTag(profile, "口味偏好", "不吃香菜");
54
+ expect(profile.tags["口味偏好"]).toHaveLength(2);
55
+ });
56
+ });
57
+ describe("decayTags", () => {
58
+ it("reduces confidence of all tags", () => {
59
+ const profile = {
60
+ ...EMPTY_PROFILE,
61
+ tags: {
62
+ 口味偏好: [
63
+ { value: "喜欢辣", confidence: 0.9, lastSeen: "2026-03-17" },
64
+ ],
65
+ },
66
+ };
67
+ const result = manager.decayTags(profile, 0.5);
68
+ expect(result.tags["口味偏好"][0].confidence).toBeCloseTo(0.45);
69
+ });
70
+ it("removes tags below threshold", () => {
71
+ const profile = {
72
+ ...EMPTY_PROFILE,
73
+ tags: {
74
+ 过时: [{ value: "旧标签", confidence: 0.15, lastSeen: "2026-01-01" }],
75
+ },
76
+ };
77
+ const result = manager.decayTags(profile, 1.0);
78
+ expect(result.tags["过时"]).toBeUndefined();
79
+ });
80
+ it("removes empty dimensions after filtering", () => {
81
+ const profile = {
82
+ ...EMPTY_PROFILE,
83
+ tags: {
84
+ 空维度: [{ value: "很低", confidence: 0.1, lastSeen: "2026-01-01" }],
85
+ },
86
+ };
87
+ const result = manager.decayTags(profile, 1.0);
88
+ expect("空维度" in result.tags).toBe(false);
89
+ });
90
+ });
91
+ describe("getRecallContext", () => {
92
+ it("returns empty string for empty profile", () => {
93
+ const ctx = manager.getRecallContext(EMPTY_PROFILE);
94
+ expect(ctx).toBe("");
95
+ });
96
+ it("returns summary and core tags", () => {
97
+ const profile = { ...EMPTY_PROFILE, summary: "辣味中餐爱好者", coreTags: ["辣味中餐", "天河"] };
98
+ const ctx = manager.getRecallContext(profile);
99
+ expect(ctx).toContain("辣味中餐爱好者");
100
+ expect(ctx).toContain("辣味中餐");
101
+ expect(ctx).toContain("天河");
102
+ });
103
+ it("works with only core tags", () => {
104
+ const profile = { ...EMPTY_PROFILE, summary: "", coreTags: ["标签1"] };
105
+ const ctx = manager.getRecallContext(profile);
106
+ expect(ctx).toContain("标签1");
107
+ });
108
+ });
109
+ describe("save", () => {
110
+ it("saves profile to file", async () => {
111
+ const profile = { ...EMPTY_PROFILE, summary: "test" };
112
+ await manager.save(profile);
113
+ const raw = await fs.readFile(join(tempDir, "memory", "profile", "semantic_profile.json"), "utf-8");
114
+ const loaded = JSON.parse(raw);
115
+ expect(loaded.summary).toBe("test");
116
+ });
117
+ it("updates updatedAt on save", async () => {
118
+ const profile = { ...EMPTY_PROFILE };
119
+ const before = new Date();
120
+ await manager.save(profile);
121
+ const after = new Date();
122
+ const raw = await fs.readFile(join(tempDir, "memory", "profile", "semantic_profile.json"), "utf-8");
123
+ const loaded = JSON.parse(raw);
124
+ const updated = new Date(loaded.updatedAt);
125
+ expect(updated.getTime()).toBeGreaterThanOrEqual(before.getTime());
126
+ expect(updated.getTime()).toBeLessThanOrEqual(after.getTime());
127
+ });
128
+ });
129
+ });
130
+ //# sourceMappingURL=profile.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.test.js","sourceRoot":"","sources":["../../../src/__tests__/profile.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,IAAI,OAAuB,CAAC;IAC5B,IAAI,OAAe,CAAC;IAEpB,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,OAAO,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;YACtD,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,MAAM,KAAK,GAAG,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;YAChF,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,uBAAuB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YAErF,yBAAyB;YACzB,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;YAC7C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YACtD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,OAAO,GAAQ;gBACnB,GAAG,aAAa;gBAChB,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,EAAE;aAC5E,CAAC;YACF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YACtD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YACvC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,OAAO,GAAQ;gBACnB,GAAG,aAAa;gBAChB,IAAI,EAAE;oBACJ,IAAI,EAAE;wBACJ,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE;qBAC1D;iBACF;aACF,CAAC;YACF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,OAAO,GAAQ;gBACnB,GAAG,aAAa;gBAChB,IAAI,EAAE;oBACJ,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;iBACjE;aACF,CAAC;YACF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,OAAO,GAAQ;gBACnB,GAAG,aAAa;gBAChB,IAAI,EAAE;oBACJ,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;iBAChE;aACF,CAAC;YACF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC/C,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;YACpD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;YACnF,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;YACnC,MAAM,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YACrE,MAAM,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;YACrC,MAAM,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YACtD,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE5B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,uBAAuB,CAAC,EAAE,OAAO,CAAC,CAAC;YACpG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;YACzC,MAAM,OAAO,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YAC1B,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC5B,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;YAEzB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,uBAAuB,CAAC,EAAE,OAAO,CAAC,CAAC;YACpG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;YACnE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,162 @@
1
+ import { describe, it, expect } from "vitest";
2
+ // 纯函数测试,不依赖外部模块
3
+ describe("Recall helpers", () => {
4
+ describe("temporalDecay", () => {
5
+ function temporalDecay(score, ageInDays, halfLifeDays) {
6
+ if (ageInDays <= 0)
7
+ return score;
8
+ const lambda = Math.log(2) / halfLifeDays;
9
+ return score * Math.exp(-lambda * ageInDays);
10
+ }
11
+ it("returns original score for 0 days", () => {
12
+ expect(temporalDecay(1.0, 0, 30)).toBeCloseTo(1.0);
13
+ });
14
+ it("returns ~50% at half-life", () => {
15
+ expect(temporalDecay(1.0, 30, 30)).toBeCloseTo(0.5, 1);
16
+ });
17
+ it("returns ~84% at 7 days with 30-day half-life", () => {
18
+ expect(temporalDecay(1.0, 7, 30)).toBeCloseTo(0.846, 2);
19
+ });
20
+ it("returns ~12.5% at 90 days with 30-day half-life", () => {
21
+ expect(temporalDecay(1.0, 90, 30)).toBeCloseTo(0.125, 2);
22
+ });
23
+ it("shorter half-life decays faster", () => {
24
+ const short = temporalDecay(1.0, 7, 7);
25
+ const long = temporalDecay(1.0, 7, 30);
26
+ expect(short).toBeLessThan(long);
27
+ });
28
+ });
29
+ describe("jaccard", () => {
30
+ function jaccard(a, b) {
31
+ const setA = new Set(a.split(""));
32
+ const setB = new Set(b.split(""));
33
+ const intersection = new Set([...setA].filter(x => setB.has(x)));
34
+ const union = new Set([...setA, ...setB]);
35
+ return union.size === 0 ? 0 : intersection.size / union.size;
36
+ }
37
+ it("returns 1.0 for identical strings", () => {
38
+ expect(jaccard("abc", "abc")).toBeCloseTo(1.0);
39
+ });
40
+ it("returns 0.0 for completely different strings", () => {
41
+ expect(jaccard("abc", "xyz")).toBeCloseTo(0.0);
42
+ });
43
+ it("returns partial similarity for overlapping strings", () => {
44
+ const sim = jaccard("abc", "abd");
45
+ expect(sim).toBeGreaterThan(0);
46
+ expect(sim).toBeLessThan(1);
47
+ });
48
+ it("handles empty strings", () => {
49
+ expect(jaccard("", "")).toBe(0);
50
+ expect(jaccard("abc", "")).toBe(0);
51
+ });
52
+ });
53
+ describe("MMR rerank", () => {
54
+ function mmrRerank(candidates, lambda = 0.7) {
55
+ if (candidates.length <= 1)
56
+ return candidates;
57
+ const jaccard = (a, b) => {
58
+ const setA = new Set(a.split(""));
59
+ const setB = new Set(b.split(""));
60
+ const intersection = new Set([...setA].filter(x => setB.has(x)));
61
+ const union = new Set([...setA, ...setB]);
62
+ return union.size === 0 ? 0 : intersection.size / union.size;
63
+ };
64
+ const selected = [];
65
+ const remaining = [...candidates];
66
+ remaining.sort((a, b) => b.finalScore - a.finalScore);
67
+ selected.push(remaining.shift());
68
+ while (remaining.length > 0) {
69
+ let bestIdx = -1;
70
+ let bestMmr = -Infinity;
71
+ for (let i = 0; i < remaining.length; i++) {
72
+ const relevance = remaining[i].finalScore;
73
+ const maxSim = Math.max(...selected.map(s => jaccard(remaining[i].text, s.text)));
74
+ const mmrScore = lambda * relevance - (1 - lambda) * maxSim;
75
+ if (mmrScore > bestMmr) {
76
+ bestMmr = mmrScore;
77
+ bestIdx = i;
78
+ }
79
+ }
80
+ if (bestIdx >= 0) {
81
+ selected.push(remaining.splice(bestIdx, 1)[0]);
82
+ }
83
+ else
84
+ break;
85
+ }
86
+ return selected;
87
+ }
88
+ it("returns single item as-is", () => {
89
+ const candidates = [{ text: "hello", finalScore: 0.9 }];
90
+ expect(mmrRerank(candidates)).toHaveLength(1);
91
+ });
92
+ it("returns empty for empty input", () => {
93
+ expect(mmrRerank([])).toHaveLength(0);
94
+ });
95
+ it("promotes diverse results over duplicates", () => {
96
+ const candidates = [
97
+ { text: "在体育西吃了潮汕牛肉火锅", finalScore: 0.92 },
98
+ { text: "在天河吃了潮汕牛肉火锅", finalScore: 0.89 },
99
+ { text: "在越秀吃了日本料理", finalScore: 0.70 },
100
+ ];
101
+ const reranked = mmrRerank(candidates, 0.7);
102
+ expect(reranked).toHaveLength(3);
103
+ // 第一条应该还是分数最高的
104
+ expect(reranked[0].finalScore).toBe(0.92);
105
+ });
106
+ it("lambda=1.0 is pure relevance (no diversity)", () => {
107
+ const candidates = [
108
+ { text: "aaa", finalScore: 0.9 },
109
+ { text: "aaa", finalScore: 0.8 },
110
+ { text: "bbb", finalScore: 0.5 },
111
+ ];
112
+ const reranked = mmrRerank(candidates, 1.0);
113
+ expect(reranked.map(r => r.finalScore)).toEqual([0.9, 0.8, 0.5]);
114
+ });
115
+ });
116
+ describe("dedup", () => {
117
+ function jaccard(a, b) {
118
+ const setA = new Set(a.split(""));
119
+ const setB = new Set(b.split(""));
120
+ const intersection = new Set([...setA].filter(x => setB.has(x)));
121
+ const union = new Set([...setA, ...setB]);
122
+ return union.size === 0 ? 0 : intersection.size / union.size;
123
+ }
124
+ function dedup(results) {
125
+ const sorted = [...results].sort((a, b) => b.score - a.score);
126
+ const unique = [];
127
+ for (const item of sorted) {
128
+ const isDup = unique.some(u => jaccard(item.text, u.text) > 0.7);
129
+ if (!isDup)
130
+ unique.push(item);
131
+ }
132
+ return unique;
133
+ }
134
+ it("removes near-duplicates", () => {
135
+ const results = [
136
+ { text: "在体育西吃了潮汕牛肉火锅", score: 0.92 },
137
+ { text: "在体育西吃潮汕牛肉火锅", score: 0.85 },
138
+ { text: "在天河城看到特斯拉展厅", score: 0.70 },
139
+ ];
140
+ const deduped = dedup(results);
141
+ expect(deduped.length).toBeLessThan(3);
142
+ });
143
+ it("keeps distinct results", () => {
144
+ const results = [
145
+ { text: "今天天气很好", score: 0.9 },
146
+ { text: "股票涨了", score: 0.8 },
147
+ { text: "新开了家餐厅", score: 0.7 },
148
+ ];
149
+ expect(dedup(results)).toHaveLength(3);
150
+ });
151
+ it("keeps higher-scored duplicate", () => {
152
+ const results = [
153
+ { text: "abcde fghij", score: 0.9 },
154
+ { text: "abcde fghij", score: 0.7 },
155
+ ];
156
+ const deduped = dedup(results);
157
+ expect(deduped).toHaveLength(1);
158
+ expect(deduped[0].score).toBe(0.9);
159
+ });
160
+ });
161
+ });
162
+ //# sourceMappingURL=recall.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recall.test.js","sourceRoot":"","sources":["../../../src/__tests__/recall.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Memory System Plugin - BM25 Short-term Memory Index
3
+ *
4
+ * 短期记忆的 BM25 索引构建与搜索
5
+ * 纯 TypeScript 实现,不依赖外部 BM25 库
6
+ */
7
+ export type ShortTermEntry = {
8
+ text: string;
9
+ date: string;
10
+ category: string;
11
+ filePath: string;
12
+ };
13
+ export declare function initJieba(): Promise<void>;
14
+ export declare function tokenize(text: string): string[];
15
+ export declare class BM25Index {
16
+ private docs;
17
+ private idf;
18
+ private avgDl;
19
+ /**
20
+ * 从短期记忆文件构建索引
21
+ * v0.5: 全量索引所有 .md 文件(不再按 maxAgeDays 截断)
22
+ * maxAgeDays 参数保留但仅用于向下兼容(不再生效)
23
+ */
24
+ buildFromDirectory(dir: string, _maxAgeDays?: number): Promise<void>;
25
+ /**
26
+ * 解析结构化的短期记忆文件
27
+ * 格式:### HH:MM [分类标签]\n摘要内容
28
+ *
29
+ * 特殊处理 [对话记录]: 按 Scott / Shadow 切分每一轮对话
30
+ * 支持两种格式:
31
+ * - 旧格式: Scott: / Shadow:
32
+ * - 新格式: Scott [MM-DD HH:MM]: / Shadow [MM-DD HH:MM]:
33
+ * 每一轮单独做一个 BM25 文档,精准召回不浪费
34
+ */
35
+ private parseEntries;
36
+ /**
37
+ * 处理文本块,支持三种格式:
38
+ * 1. 精简格式(每行 | 分隔)
39
+ * 2. 对话记录(Scott/Shadow 切分)
40
+ * 3. 普通文本(整块索引)
41
+ */
42
+ private processTextBlock;
43
+ /**
44
+ * 构建 IDF(逆文档频率)
45
+ */
46
+ private buildIDF;
47
+ /**
48
+ * BM25 搜索
49
+ * @returns 带分数的结果列表
50
+ */
51
+ search(query: string, topK?: number): Promise<Array<{
52
+ entry: ShortTermEntry;
53
+ score: number;
54
+ }>>;
55
+ /**
56
+ * 添加单条记录(用于实时写入后的即时索引)
57
+ */
58
+ addEntry(entry: ShortTermEntry): Promise<void>;
59
+ get size(): number;
60
+ }