@yun-zero/claw-memory 0.1.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 (131) hide show
  1. package/.claude/settings.local.json +68 -0
  2. package/README.md +323 -0
  3. package/dist/config/llm.d.ts +13 -0
  4. package/dist/config/llm.d.ts.map +1 -0
  5. package/dist/config/llm.js +96 -0
  6. package/dist/config/llm.js.map +1 -0
  7. package/dist/config/plugin.d.ts +15 -0
  8. package/dist/config/plugin.d.ts.map +1 -0
  9. package/dist/config/plugin.js +32 -0
  10. package/dist/config/plugin.js.map +1 -0
  11. package/dist/db/entityRepository.d.ts +21 -0
  12. package/dist/db/entityRepository.d.ts.map +1 -0
  13. package/dist/db/entityRepository.js +55 -0
  14. package/dist/db/entityRepository.js.map +1 -0
  15. package/dist/db/repository.d.ts +22 -0
  16. package/dist/db/repository.d.ts.map +1 -0
  17. package/dist/db/repository.js +77 -0
  18. package/dist/db/repository.js.map +1 -0
  19. package/dist/db/schema.d.ts +5 -0
  20. package/dist/db/schema.d.ts.map +1 -0
  21. package/dist/db/schema.js +112 -0
  22. package/dist/db/schema.js.map +1 -0
  23. package/dist/db/todoRepository.d.ts +26 -0
  24. package/dist/db/todoRepository.d.ts.map +1 -0
  25. package/dist/db/todoRepository.js +54 -0
  26. package/dist/db/todoRepository.js.map +1 -0
  27. package/dist/hooks/bootstrap.d.ts +3 -0
  28. package/dist/hooks/bootstrap.d.ts.map +1 -0
  29. package/dist/hooks/bootstrap.js +28 -0
  30. package/dist/hooks/bootstrap.js.map +1 -0
  31. package/dist/hooks/message.d.ts +18 -0
  32. package/dist/hooks/message.d.ts.map +1 -0
  33. package/dist/hooks/message.js +52 -0
  34. package/dist/hooks/message.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +46 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mcp/tools.d.ts +26 -0
  40. package/dist/mcp/tools.d.ts.map +1 -0
  41. package/dist/mcp/tools.js +360 -0
  42. package/dist/mcp/tools.js.map +1 -0
  43. package/dist/plugin.d.ts +18 -0
  44. package/dist/plugin.d.ts.map +1 -0
  45. package/dist/plugin.js +62 -0
  46. package/dist/plugin.js.map +1 -0
  47. package/dist/services/entityGraphService.d.ts +87 -0
  48. package/dist/services/entityGraphService.d.ts.map +1 -0
  49. package/dist/services/entityGraphService.js +271 -0
  50. package/dist/services/entityGraphService.js.map +1 -0
  51. package/dist/services/memory.d.ts +26 -0
  52. package/dist/services/memory.d.ts.map +1 -0
  53. package/dist/services/memory.js +281 -0
  54. package/dist/services/memory.js.map +1 -0
  55. package/dist/services/memoryIndex.d.ts +34 -0
  56. package/dist/services/memoryIndex.d.ts.map +1 -0
  57. package/dist/services/memoryIndex.js +100 -0
  58. package/dist/services/memoryIndex.js.map +1 -0
  59. package/dist/services/metadataExtractor.d.ts +16 -0
  60. package/dist/services/metadataExtractor.d.ts.map +1 -0
  61. package/dist/services/metadataExtractor.js +75 -0
  62. package/dist/services/metadataExtractor.js.map +1 -0
  63. package/dist/services/retrieval.d.ts +24 -0
  64. package/dist/services/retrieval.d.ts.map +1 -0
  65. package/dist/services/retrieval.js +40 -0
  66. package/dist/services/retrieval.js.map +1 -0
  67. package/dist/services/scheduler.d.ts +122 -0
  68. package/dist/services/scheduler.d.ts.map +1 -0
  69. package/dist/services/scheduler.js +434 -0
  70. package/dist/services/scheduler.js.map +1 -0
  71. package/dist/services/summarizer.d.ts +43 -0
  72. package/dist/services/summarizer.d.ts.map +1 -0
  73. package/dist/services/summarizer.js +252 -0
  74. package/dist/services/summarizer.js.map +1 -0
  75. package/dist/services/tagService.d.ts +64 -0
  76. package/dist/services/tagService.d.ts.map +1 -0
  77. package/dist/services/tagService.js +281 -0
  78. package/dist/services/tagService.js.map +1 -0
  79. package/dist/tools/memory.d.ts +3 -0
  80. package/dist/tools/memory.d.ts.map +1 -0
  81. package/dist/tools/memory.js +114 -0
  82. package/dist/tools/memory.js.map +1 -0
  83. package/dist/types.d.ts +128 -0
  84. package/dist/types.d.ts.map +1 -0
  85. package/dist/types.js +6 -0
  86. package/dist/types.js.map +1 -0
  87. package/docs/plans/2026-03-02-claw-memory-design.md +445 -0
  88. package/docs/plans/2026-03-02-incremental-summary-design.md +157 -0
  89. package/docs/plans/2026-03-02-incremental-summary-implementation.md +468 -0
  90. package/docs/plans/2026-03-02-memory-index-design.md +163 -0
  91. package/docs/plans/2026-03-02-memory-index-implementation.md +836 -0
  92. package/docs/plans/2026-03-02-mvp-implementation.md +1703 -0
  93. package/docs/plans/2026-03-02-testing-implementation.md +395 -0
  94. package/docs/plans/2026-03-02-testing-plan.md +93 -0
  95. package/docs/plans/2026-03-03-claw-memory-openclaw-plugin-design.md +285 -0
  96. package/docs/plans/2026-03-03-claw-memory-plugin-implementation.md +642 -0
  97. package/docs/plans/2026-03-03-entity-graph-design.md +121 -0
  98. package/docs/plans/2026-03-03-entity-graph-implementation.md +687 -0
  99. package/docs/plans/2026-03-03-llm-generic-config-design.md +43 -0
  100. package/docs/plans/2026-03-03-llm-generic-config-implementation.md +186 -0
  101. package/docs/plans/2026-03-03-memory-e2e-stress-test-design.md +110 -0
  102. package/docs/plans/2026-03-03-memory-e2e-stress-test-implementation.md +464 -0
  103. package/docs/plans/2026-03-03-minimax-llm-fix.md +156 -0
  104. package/docs/plans/2026-03-03-scheduler-design.md +165 -0
  105. package/docs/plans/2026-03-03-scheduler-implementation.md +777 -0
  106. package/docs/plans/2026-03-03-tags-visualization-design.md +73 -0
  107. package/docs/plans/2026-03-03-tags-visualization-implementation.md +539 -0
  108. package/openclaw.plugin.json +11 -0
  109. package/package.json +41 -0
  110. package/src/config/llm.ts +129 -0
  111. package/src/config/plugin.ts +47 -0
  112. package/src/db/entityRepository.ts +80 -0
  113. package/src/db/repository.ts +106 -0
  114. package/src/db/schema.ts +121 -0
  115. package/src/db/todoRepository.ts +76 -0
  116. package/src/hooks/bootstrap.ts +36 -0
  117. package/src/hooks/message.ts +84 -0
  118. package/src/index.ts +50 -0
  119. package/src/plugin.ts +85 -0
  120. package/src/services/entityGraphService.ts +367 -0
  121. package/src/services/memory.ts +338 -0
  122. package/src/services/memoryIndex.ts +140 -0
  123. package/src/services/metadataExtractor.ts +89 -0
  124. package/src/services/retrieval.ts +71 -0
  125. package/src/services/scheduler.ts +529 -0
  126. package/src/services/summarizer.ts +318 -0
  127. package/src/services/tagService.ts +335 -0
  128. package/src/tools/memory.ts +137 -0
  129. package/src/types.ts +139 -0
  130. package/tsconfig.json +20 -0
  131. package/vitest.config.ts +16 -0
@@ -0,0 +1,836 @@
1
+ # 记忆索引与自动提取功能实现计划
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** 实现 LLM 自动提取元数据、记忆索引 API、待办事项功能,支持 OpenClaw Hook 在对话开始时获取记忆摘要
6
+
7
+ **Architecture:** 分三个阶段实现:1) LLM 自动提取元数据 2) 记忆索引 API 3) 待办事项功能。每个阶段使用 TDD,先写测试再实现。
8
+
9
+ **Tech Stack:** TypeScript, better-sqlite3, LLM API (OpenAI/Anthropic)
10
+
11
+ ---
12
+
13
+ ## 阶段一:LLM 自动提取元数据
14
+
15
+ ### Task 1: 创建 MetadataExtractor 服务
16
+
17
+ **Files:**
18
+ - Create: `src/services/metadataExtractor.ts`
19
+ - Modify: `src/services/memory.ts`
20
+ - Test: `tests/services/metadataExtractor.test.ts`
21
+
22
+ **Step 1: 编写失败的测试**
23
+
24
+ ```typescript
25
+ // tests/services/metadataExtractor.test.ts
26
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
27
+ import { MetadataExtractor } from '../../src/services/metadataExtractor.js';
28
+
29
+ describe('MetadataExtractor', () => {
30
+ let extractor: MetadataExtractor;
31
+
32
+ beforeEach(() => {
33
+ extractor = new MetadataExtractor();
34
+ });
35
+
36
+ it('should extract metadata from content', async () => {
37
+ const content = '用户讨论了 React Hooks 的使用,包括 useState 和 useEffect';
38
+ const result = await extractor.extract(content);
39
+
40
+ expect(result).toHaveProperty('tags');
41
+ expect(result).toHaveProperty('keywords');
42
+ expect(result).toHaveProperty('subjects');
43
+ expect(result).toHaveProperty('importance');
44
+ expect(result).toHaveProperty('summary');
45
+ });
46
+ });
47
+ ```
48
+
49
+ **Step 2: 运行测试验证失败**
50
+
51
+ ```bash
52
+ npm test tests/services/metadataExtractor.test.ts
53
+ ```
54
+ Expected: FAIL - MetadataExtractor not found
55
+
56
+ **Step 3: 最小实现**
57
+
58
+ ```typescript
59
+ // src/services/metadataExtractor.ts
60
+ export interface ExtractedMetadata {
61
+ tags: string[];
62
+ keywords: string[];
63
+ subjects: string[];
64
+ importance: number;
65
+ summary: string;
66
+ }
67
+
68
+ export class MetadataExtractor {
69
+ async extract(content: string): Promise<ExtractedMetadata> {
70
+ // TODO: 实现 LLM 提取逻辑
71
+ return {
72
+ tags: [],
73
+ keywords: [],
74
+ subjects: [],
75
+ importance: 0.5,
76
+ summary: ''
77
+ };
78
+ }
79
+ }
80
+ ```
81
+
82
+ **Step 4: 运行测试验证通过**
83
+
84
+ ```bash
85
+ npm test tests/services/metadataExtractor.test.ts
86
+ ```
87
+ Expected: PASS
88
+
89
+ **Step 5: 提交**
90
+
91
+ ```bash
92
+ git add src/services/metadataExtractor.ts tests/services/metadataExtractor.test.ts
93
+ git commit -m "feat: add MetadataExtractor service stub"
94
+ ```
95
+
96
+ ---
97
+
98
+ ### Task 2: 集成 LLM 提取逻辑
99
+
100
+ **Files:**
101
+ - Modify: `src/services/metadataExtractor.ts:15-60`
102
+ - Test: `tests/services/metadataExtractor.test.ts`
103
+
104
+ **Step 1: 更新测试添加 LLM 模拟**
105
+
106
+ ```typescript
107
+ // 在 metadataExtractor.test.ts 添加
108
+ it('should call LLM and parse response', async () => {
109
+ const mockLLMResponse = {
110
+ tags: ['技术/前端/React'],
111
+ keywords: ['useState', 'useEffect'],
112
+ subjects: ['React Hooks'],
113
+ importance: 0.8,
114
+ summary: '讨论 React Hooks 使用'
115
+ };
116
+
117
+ // Mock LLM call
118
+ vi.spyOn(extractor, 'callLLM').mockResolvedValue(mockLLMResponse);
119
+
120
+ const result = await extractor.extract('讨论 React Hooks');
121
+ expect(result.tags).toContain('技术/前端/React');
122
+ });
123
+ ```
124
+
125
+ **Step 2: 运行测试**
126
+
127
+ ```bash
128
+ npm test tests/services/metadataExtractor.test.ts
129
+ ```
130
+ Expected: FAIL - callLLM not implemented
131
+
132
+ **Step 3: 实现 LLM 提取**
133
+
134
+ ```typescript
135
+ // src/services/metadataExtractor.ts 完整实现
136
+ import { generateSummaryWithLLM } from '../config/llm.js';
137
+
138
+ const EXTRACTION_PROMPT = `请从以下对话内容中提取结构化元数据:
139
+
140
+ 内容:{content}
141
+
142
+ 请以 JSON 格式返回:
143
+ {
144
+ "tags": ["一级分类/二级分类/三级分类"],
145
+ "keywords": ["关键词1", "关键词2"],
146
+ "subjects": ["主题1", "主题2"],
147
+ "importance": 0.0-1.0,
148
+ "summary": "一句话摘要"
149
+ }
150
+
151
+ 注意:
152
+ - tags 使用层级结构,如 "技术/前端/React"
153
+ - importance 表示内容重要性,0.0-1.0
154
+ - 只返回 JSON,不要其他内容`;
155
+
156
+ export class MetadataExtractor {
157
+ async extract(content: string): Promise<ExtractedMetadata> {
158
+ const prompt = EXTRACTION_PROMPT.replace('{content}', content);
159
+
160
+ try {
161
+ const response = await generateSummaryWithLLM(prompt);
162
+ return this.parseLLMResponse(response);
163
+ } catch (error) {
164
+ console.warn('LLM extraction failed:', error);
165
+ return this.fallbackExtract(content);
166
+ }
167
+ }
168
+
169
+ private parseLLMResponse(response: string): ExtractedMetadata {
170
+ try {
171
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
172
+ if (jsonMatch) {
173
+ return JSON.parse(jsonMatch[0]);
174
+ }
175
+ } catch {}
176
+ return this.fallbackExtract('');
177
+ }
178
+
179
+ private fallbackExtract(content: string): ExtractedMetadata {
180
+ return {
181
+ tags: [],
182
+ keywords: [],
183
+ subjects: [],
184
+ importance: 0.5,
185
+ summary: content.substring(0, 100)
186
+ };
187
+ }
188
+ }
189
+ ```
190
+
191
+ **Step 4: 运行测试**
192
+
193
+ ```bash
194
+ npm test tests/services/metadataExtractor.test.ts
195
+ ```
196
+ Expected: PASS
197
+
198
+ **Step 5: 提交**
199
+
200
+ ```bash
201
+ git add src/services/metadataExtractor.ts
202
+ git commit -m "feat: implement LLM metadata extraction"
203
+ ```
204
+
205
+ ---
206
+
207
+ ### Task 3: 修改 MemoryService 集成提取
208
+
209
+ **Files:**
210
+ - Modify: `src/services/memory.ts:28-50`
211
+ - Test: `tests/services/memory.test.ts`
212
+
213
+ **Step 1: 添加集成测试**
214
+
215
+ ```typescript
216
+ // tests/services/memory.test.ts 添加
217
+ it('should extract metadata with LLM when saving memory', async () => {
218
+ vi.spyOn(extractor, 'extract').mockResolvedValue({
219
+ tags: ['技术/前端'],
220
+ keywords: ['React'],
221
+ subjects: ['React学习'],
222
+ importance: 0.8,
223
+ summary: '测试'
224
+ });
225
+
226
+ const result = await service.saveMemory({
227
+ content: '讨论 React Hooks',
228
+ metadata: {}
229
+ });
230
+
231
+ expect(result).toBeDefined();
232
+ });
233
+ ```
234
+
235
+ **Step 2: 运行测试**
236
+
237
+ ```bash
238
+ npm test tests/services/memory.test.ts
239
+ ```
240
+ Expected: FAIL - extractor not integrated
241
+
242
+ **Step 3: 修改 MemoryService**
243
+
244
+ ```typescript
245
+ // src/services/memory.ts 导入
246
+ import { MetadataExtractor } from './metadataExtractor.js';
247
+
248
+ export class MemoryService {
249
+ private extractor: MetadataExtractor;
250
+
251
+ constructor(...) {
252
+ // ... existing code
253
+ this.extractor = new MetadataExtractor();
254
+ }
255
+
256
+ async saveMemory(input: SaveMemoryInput): Promise<Memory> {
257
+ // 调用 LLM 提取元数据
258
+ const extracted = await this.extractor.extract(input.content);
259
+
260
+ // 合并:LLM 提取的覆盖用户传入的
261
+ const metadata = {
262
+ tags: extracted.tags,
263
+ keywords: extracted.keywords,
264
+ subjects: extracted.subjects,
265
+ importance: extracted.importance,
266
+ summary: extracted.summary
267
+ };
268
+
269
+ // ... 后续处理不变
270
+ }
271
+ }
272
+ ```
273
+
274
+ **Step 4: 运行测试**
275
+
276
+ ```bash
277
+ npm test tests/services/memory.test.ts
278
+ ```
279
+ Expected: PASS
280
+
281
+ **Step 5: 提交**
282
+
283
+ ```bash
284
+ git add src/services/memory.ts tests/services/memory.test.ts
285
+ git commit -m "feat: integrate LLM metadata extraction in saveMemory"
286
+ ```
287
+
288
+ ---
289
+
290
+ ## 阶段二:记忆索引 API
291
+
292
+ ### Task 4: 添加 todos 表 schema
293
+
294
+ **Files:**
295
+ - Modify: `src/db/schema.ts:69-85`
296
+ - Test: `tests/db/schema.test.ts`
297
+
298
+ **Step 1: 编写失败的测试**
299
+
300
+ ```typescript
301
+ // tests/db/schema.test.ts 添加
302
+ it('should create todos table', () => {
303
+ const stmt = db.prepare(`
304
+ SELECT name FROM sqlite_master WHERE type='table' AND name='todos'
305
+ `);
306
+ const result = stmt.get();
307
+ expect(result).toBeDefined();
308
+ });
309
+ ```
310
+
311
+ **Step 2: 运行测试**
312
+
313
+ ```bash
314
+ npm test tests/db/schema.test.ts
315
+ ```
316
+ Expected: FAIL - todos table not found
317
+
318
+ **Step 3: 添加 schema**
319
+
320
+ ```typescript
321
+ // src/db/schema.ts 添加
322
+ db.exec(`
323
+ CREATE TABLE IF NOT EXISTS todos (
324
+ id TEXT PRIMARY KEY,
325
+ content TEXT NOT NULL,
326
+ period TEXT NOT NULL,
327
+ period_date TEXT,
328
+ created_at TEXT NOT NULL,
329
+ completed_at TEXT,
330
+ memory_id TEXT
331
+ )
332
+ `);
333
+ ```
334
+
335
+ **Step 4: 运行测试**
336
+
337
+ ```bash
338
+ npm test tests/db/schema.test.ts
339
+ ```
340
+ Expected: PASS
341
+
342
+ **Step 5: 提交**
343
+
344
+ ```bash
345
+ git add src/db/schema.ts
346
+ git commit -m "feat: add todos table schema"
347
+ ```
348
+
349
+ ---
350
+
351
+ ### Task 5: 创建 TodoRepository
352
+
353
+ **Files:**
354
+ - Create: `src/db/todoRepository.ts`
355
+ - Test: `tests/db/todoRepository.test.ts`
356
+
357
+ **Step 1: 编写测试**
358
+
359
+ ```typescript
360
+ // tests/db/todoRepository.test.ts
361
+ import { describe, it, expect, beforeEach } from 'vitest';
362
+ import Database from 'better-sqlite3';
363
+ import { initializeDatabase } from '../../src/db/schema.js';
364
+ import { TodoRepository } from '../../src/db/todoRepository.js';
365
+
366
+ describe('TodoRepository', () => {
367
+ let db: Database.Database;
368
+ let repo: TodoRepository;
369
+
370
+ beforeEach(() => {
371
+ db = new Database(':memory:');
372
+ initializeDatabase(db);
373
+ repo = new TodoRepository(db);
374
+ });
375
+
376
+ it('should create a todo', () => {
377
+ const todo = repo.create({
378
+ content: '完成某事',
379
+ period: 'week',
380
+ periodDate: '2026-03-02'
381
+ });
382
+ expect(todo.id).toBeDefined();
383
+ expect(todo.content).toBe('完成某事');
384
+ });
385
+
386
+ it('should list todos by period', () => {
387
+ repo.create({ content: '任务1', period: 'week', periodDate: '2026-03-02' });
388
+ repo.create({ content: '任务2', period: 'week', periodDate: '2026-03-02' });
389
+
390
+ const todos = repo.findByPeriod('week', '2026-03-02');
391
+ expect(todos.length).toBe(2);
392
+ });
393
+
394
+ it('should mark todo as completed', () => {
395
+ const todo = repo.create({ content: '任务', period: 'day', periodDate: '2026-03-02' });
396
+ repo.markCompleted(todo.id);
397
+
398
+ const updated = repo.findById(todo.id);
399
+ expect(updated.completedAt).toBeDefined();
400
+ });
401
+ });
402
+ ```
403
+
404
+ **Step 2: 运行测试**
405
+
406
+ ```bash
407
+ npm test tests/db/todoRepository.test.ts
408
+ ```
409
+ Expected: FAIL - TodoRepository not found
410
+
411
+ **Step 3: 实现 TodoRepository**
412
+
413
+ ```typescript
414
+ // src/db/todoRepository.ts
415
+ import { v4 as uuidv4 } from 'uuid';
416
+
417
+ export interface Todo {
418
+ id: string;
419
+ content: string;
420
+ period: 'day' | 'week' | 'month';
421
+ periodDate: string;
422
+ createdAt: Date;
423
+ completedAt: Date | null;
424
+ memoryId: string | null;
425
+ }
426
+
427
+ export interface CreateTodoInput {
428
+ content: string;
429
+ period: 'day' | 'week' | 'month';
430
+ periodDate: string;
431
+ memoryId?: string;
432
+ }
433
+
434
+ export class TodoRepository {
435
+ constructor(private db: Database.Database) {}
436
+
437
+ create(input: CreateTodoInput): Todo {
438
+ const id = uuidv4();
439
+ const createdAt = new Date().toISOString();
440
+
441
+ this.db.prepare(`
442
+ INSERT INTO todos (id, content, period, period_date, created_at, memory_id)
443
+ VALUES (?, ?, ?, ?, ?, ?)
444
+ `).run(id, input.content, input.period, input.periodDate, createdAt, input.memoryId || null);
445
+
446
+ return { id, content: input.content, period: input.period, periodDate: input.periodDate, createdAt: new Date(createdAt), completedAt: null, memoryId: input.memoryId || null };
447
+ }
448
+
449
+ findByPeriod(period: string, periodDate: string): Todo[] {
450
+ return this.db.prepare(`
451
+ SELECT * FROM todos WHERE period = ? AND period_date = ?
452
+ ORDER BY created_at DESC
453
+ `).all(period, periodDate) as Todo[];
454
+ }
455
+
456
+ findById(id: string): Todo | null {
457
+ return this.db.prepare('SELECT * FROM todos WHERE id = ?').get(id) as Todo | null;
458
+ }
459
+
460
+ markCompleted(id: string): void {
461
+ this.db.prepare(`
462
+ UPDATE todos SET completed_at = ? WHERE id = ?
463
+ `).run(new Date().toISOString(), id);
464
+ }
465
+ }
466
+ ```
467
+
468
+ **Step 4: 运行测试**
469
+
470
+ ```bash
471
+ npm test tests/db/todoRepository.test.ts
472
+ ```
473
+ Expected: PASS
474
+
475
+ **Step 5: 提交**
476
+
477
+ ```bash
478
+ git add src/db/todoRepository.ts tests/db/todoRepository.test.ts
479
+ git commit -m "feat: add TodoRepository for todo management"
480
+ ```
481
+
482
+ ---
483
+
484
+ ### Task 6: 创建 get_memory_index MCP 工具
485
+
486
+ **Files:**
487
+ - Modify: `src/mcp/tools.ts`
488
+ - Create: `src/services/memoryIndex.ts`
489
+ - Test: `tests/mcp/memoryIndex.test.ts`
490
+
491
+ **Step 1: 编写测试**
492
+
493
+ ```typescript
494
+ // tests/mcp/memoryIndex.test.ts
495
+ import { describe, it, expect, beforeEach } from 'vitest';
496
+ import Database from 'better-sqlite3';
497
+ import { initializeDatabase } from '../../src/db/schema.js';
498
+ import { getMemoryIndex } from '../../src/services/memoryIndex.js';
499
+
500
+ describe('getMemoryIndex', () => {
501
+ let db: Database.Database;
502
+
503
+ beforeEach(() => {
504
+ db = new Database(':memory:');
505
+ initializeDatabase(db);
506
+ });
507
+
508
+ it('should return memory index with all sections', async () => {
509
+ // 创建测试数据...
510
+
511
+ const result = await getMemoryIndex(db, {
512
+ period: 'week',
513
+ includeTodos: true,
514
+ includeRecent: true
515
+ });
516
+
517
+ expect(result).toHaveProperty('period');
518
+ expect(result).toHaveProperty('activeAreas');
519
+ expect(result).toHaveProperty('todos');
520
+ expect(result).toHaveProperty('recentActivity');
521
+ });
522
+ });
523
+ ```
524
+
525
+ **Step 2: 运行测试**
526
+
527
+ ```bash
528
+ npm test tests/mcp/memoryIndex.test.ts
529
+ ```
530
+ Expected: FAIL - getMemoryIndex not found
531
+
532
+ **Step 3: 实现 memoryIndex 服务**
533
+
534
+ ```typescript
535
+ // src/services/memoryIndex.ts
536
+ import { TodoRepository } from '../db/todoRepository.js';
537
+ import { MemoryRepository } from '../db/repository.js';
538
+ import { EntityRepository } from '../db/entityRepository.js';
539
+
540
+ export interface MemoryIndexOptions {
541
+ period: 'day' | 'week' | 'month';
542
+ date?: string;
543
+ includeTodos?: boolean;
544
+ includeRecent?: boolean;
545
+ recentLimit?: number;
546
+ }
547
+
548
+ export interface MemoryIndex {
549
+ period: { start: string; end: string };
550
+ activeAreas: {
551
+ tags: { name: string; count: number }[];
552
+ keywords: string[];
553
+ };
554
+ todos: { id: string; content: string; period: string }[];
555
+ recentActivity: { date: string; summary: string }[];
556
+ }
557
+
558
+ export async function getMemoryIndex(db: Database.Database, options: MemoryIndexOptions): Promise<MemoryIndex> {
559
+ const { startDate, endDate } = calculatePeriodRange(options.period, options.date);
560
+
561
+ // 获取活跃领域
562
+ const tagRepo = new EntityRepository(db);
563
+ const tags = getTopTags(db, startDate, endDate, 10);
564
+ const keywords = getTopKeywords(db, startDate, endDate, 10);
565
+
566
+ // 获取待办
567
+ const todoRepo = new TodoRepository(db);
568
+ const todos = options.includeTodos
569
+ ? todoRepo.findByPeriod(options.period, endDate).filter(t => !t.completedAt)
570
+ : [];
571
+
572
+ // 获取最近动态
573
+ const memoryRepo = new MemoryRepository(db);
574
+ const recentMemories = options.includeRecent
575
+ ? memoryRepo.findByDateRange(startDate, endDate, options.recentLimit || 5)
576
+ : [];
577
+
578
+ return {
579
+ period: { start: startDate, end: endDate },
580
+ activeAreas: { tags, keywords },
581
+ todos: todos.map(t => ({ id: t.id, content: t.content, period: t.period })),
582
+ recentActivity: recentMemories.map(m => ({
583
+ date: m.createdAt.toISOString().split('T')[0],
584
+ summary: m.summary || ''
585
+ }))
586
+ };
587
+ }
588
+
589
+ function calculatePeriodRange(period: string, date?: string) {
590
+ const endDate = date || new Date().toISOString().split('T')[0];
591
+ const startDate = new Date(endDate);
592
+
593
+ switch (period) {
594
+ case 'day': break;
595
+ case 'week': startDate.setDate(startDate.getDate() - 7); break;
596
+ case 'month': startDate.setMonth(startDate.getMonth() - 1); break;
597
+ }
598
+
599
+ return {
600
+ startDate: startDate.toISOString().split('T')[0],
601
+ endDate
602
+ };
603
+ }
604
+
605
+ function getTopTags(db: Database.Database, startDate: string, endDate: string, limit: number) {
606
+ return db.prepare(`
607
+ SELECT e.name, COUNT(me.memory_id) as count
608
+ FROM memory_entities me
609
+ JOIN entities e ON me.entity_id = e.id
610
+ JOIN memories m ON me.memory_id = m.id
611
+ WHERE e.type = 'tag' AND date(m.created_at) BETWEEN ? AND ?
612
+ GROUP BY e.id ORDER BY count DESC LIMIT ?
613
+ `).all(startDate, endDate, limit) as { name: string; count: number }[];
614
+ }
615
+
616
+ function getTopKeywords(db: Database.Database, startDate: string, endDate: string, limit: number) {
617
+ const rows = db.prepare(`
618
+ SELECT e.name FROM memory_entities me
619
+ JOIN entities e ON me.entity_id = e.id
620
+ JOIN memories m ON me.memory_id = m.id
621
+ WHERE e.type = 'keyword' AND date(m.created_at) BETWEEN ? AND ?
622
+ GROUP BY e.id ORDER BY COUNT(*) DESC LIMIT ?
623
+ `).all(startDate, endDate, limit) as { name: string }[];
624
+ return rows.map(r => r.name);
625
+ }
626
+ ```
627
+
628
+ **Step 4: 添加 MCP 工具**
629
+
630
+ ```typescript
631
+ // src/mcp/tools.ts 添加
632
+ import { getMemoryIndex } from '../services/memoryIndex.js';
633
+
634
+ export function createGetMemoryIndexTool(db: Database.Database): MCPTool {
635
+ return {
636
+ name: 'get_memory_index',
637
+ description: 'Get memory index summary for conversation context',
638
+ inputSchema: {
639
+ type: 'object' as const,
640
+ properties: {
641
+ period: {
642
+ type: 'string',
643
+ enum: ['day', 'week', 'month'],
644
+ default: 'week'
645
+ },
646
+ date: { type: 'string' },
647
+ includeTodos: { type: 'boolean', default: true },
648
+ includeRecent: { type: 'boolean', default: true },
649
+ recentLimit: { type: 'number', default: 5 }
650
+ }
651
+ },
652
+ handler: async (params) => {
653
+ return await getMemoryIndex(db, params);
654
+ }
655
+ };
656
+ }
657
+ ```
658
+
659
+ **Step 5: 运行测试**
660
+
661
+ ```bash
662
+ npm test tests/mcp/memoryIndex.test.ts
663
+ ```
664
+ Expected: PASS
665
+
666
+ **Step 6: 提交**
667
+
668
+ ```bash
669
+ git add src/services/memoryIndex.ts src/mcp/tools.ts tests/mcp/memoryIndex.test.ts
670
+ git commit -m "feat: add get_memory_index MCP tool"
671
+ ```
672
+
673
+ ---
674
+
675
+ ## 阶段三:待办事项 MCP 工具
676
+
677
+ ### Task 7: 添加待办事项 MCP 工具
678
+
679
+ **Files:**
680
+ - Modify: `src/mcp/tools.ts`
681
+ - Test: `tests/mcp/todo.test.ts`
682
+
683
+ **Step 1: 编写测试**
684
+
685
+ ```typescript
686
+ // tests/mcp/todo.test.ts
687
+ import { describe, it, expect, beforeEach } from 'vitest';
688
+ import Database from 'better-sqlite3';
689
+ import { initializeDatabase } from '../../src/db/schema.js';
690
+ import { createAddTodoTool, createListTodosTool, createCompleteTodoTool } from '../../src/mcp/tools.js';
691
+
692
+ describe('Todo MCP Tools', () => {
693
+ let db: Database.Database;
694
+
695
+ beforeEach(() => {
696
+ db = new Database(':memory:');
697
+ initializeDatabase(db);
698
+ });
699
+
700
+ it('should add todo', async () => {
701
+ const addTool = createAddTodoTool(db);
702
+ const result = await addTool.handler({ content: '新任务', period: 'week', periodDate: '2026-03-02' });
703
+ expect(result.id).toBeDefined();
704
+ });
705
+
706
+ it('should list todos', async () => {
707
+ const addTool = createAddTodoTool(db);
708
+ const listTool = createListTodosTool(db);
709
+
710
+ await addTool.handler({ content: '任务1', period: 'week', periodDate: '2026-03-02' });
711
+ await addTool.handler({ content: '任务2', period: 'week', periodDate: '2026-03-02' });
712
+
713
+ const result = await listTool.handler({ period: 'week', periodDate: '2026-03-02' });
714
+ expect(result.todos.length).toBe(2);
715
+ });
716
+
717
+ it('should complete todo', async () => {
718
+ const addTool = createAddTodoTool(db);
719
+ const completeTool = createCompleteTodoTool(db);
720
+
721
+ const created = await addTool.handler({ content: '任务', period: 'day', periodDate: '2026-03-02' });
722
+ await completeTool.handler({ id: created.id });
723
+
724
+ const listTool = createListTodosTool(db);
725
+ const result = await listTool.handler({ period: 'day', periodDate: '2026-03-02' });
726
+ expect(result.todos[0].completedAt).toBeDefined();
727
+ });
728
+ });
729
+ ```
730
+
731
+ **Step 2: 运行测试**
732
+
733
+ ```bash
734
+ npm test tests/mcp/todo.test.ts
735
+ ```
736
+ Expected: FAIL - tools not implemented
737
+
738
+ **Step 3: 实现工具**
739
+
740
+ ```typescript
741
+ // src/mcp/tools.ts 添加
742
+ import { TodoRepository, CreateTodoInput } from '../db/todoRepository.js';
743
+
744
+ export function createAddTodoTool(db: Database.Database): MCPTool {
745
+ return {
746
+ name: 'add_todo',
747
+ description: 'Add a todo item',
748
+ inputSchema: {
749
+ type: 'object' as const,
750
+ properties: {
751
+ content: { type: 'string' },
752
+ period: { type: 'string', enum: ['day', 'week', 'month'] },
753
+ periodDate: { type: 'string' },
754
+ memoryId: { type: 'string' }
755
+ },
756
+ required: ['content', 'period', 'periodDate']
757
+ },
758
+ handler: async (params) => {
759
+ const repo = new TodoRepository(db);
760
+ const todo = repo.create(params as CreateTodoInput);
761
+ return todo;
762
+ }
763
+ };
764
+ }
765
+
766
+ export function createListTodosTool(db: Database.Database): MCPTool {
767
+ return {
768
+ name: 'list_todos',
769
+ description: 'List todo items',
770
+ inputSchema: {
771
+ type: 'object' as const,
772
+ properties: {
773
+ period: { type: 'string', enum: ['day', 'week', 'month'] },
774
+ periodDate: { type: 'string' },
775
+ includeCompleted: { type: 'boolean', default: false }
776
+ },
777
+ required: ['period', 'periodDate']
778
+ },
779
+ handler: async (params) => {
780
+ const repo = new TodoRepository(db);
781
+ let todos = repo.findByPeriod(params.period, params.periodDate);
782
+ if (!params.includeCompleted) {
783
+ todos = todos.filter(t => !t.completedAt);
784
+ }
785
+ return { todos };
786
+ }
787
+ };
788
+ }
789
+
790
+ export function createCompleteTodoTool(db: Database.Database): MCPTool {
791
+ return {
792
+ name: 'complete_todo',
793
+ description: 'Mark a todo as completed',
794
+ inputSchema: {
795
+ type: 'object' as const,
796
+ properties: {
797
+ id: { type: 'string' }
798
+ },
799
+ required: ['id']
800
+ },
801
+ handler: async (params) => {
802
+ const repo = new TodoRepository(db);
803
+ repo.markCompleted(params.id);
804
+ return { success: true };
805
+ }
806
+ };
807
+ }
808
+ ```
809
+
810
+ **Step 4: 运行测试**
811
+
812
+ ```bash
813
+ npm test tests/mcp/todo.test.ts
814
+ ```
815
+ Expected: PASS
816
+
817
+ **Step 5: 提交**
818
+
819
+ ```bash
820
+ git add src/mcp/tools.ts tests/mcp/todo.test.ts
821
+ git commit -m "feat: add todo MCP tools (add_todo, list_todos, complete_todo)"
822
+ ```
823
+
824
+ ---
825
+
826
+ ## 总结
827
+
828
+ 完成所有任务后,运行完整测试:
829
+
830
+ ```bash
831
+ npm test
832
+ ```
833
+
834
+ 预期:所有测试通过
835
+
836
+ ---