@synth-coder/memhub 0.2.3 → 0.2.4

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 (143) hide show
  1. package/.eslintrc.cjs +45 -45
  2. package/.factory/commands/opsx-apply.md +150 -150
  3. package/.factory/commands/opsx-archive.md +155 -155
  4. package/.factory/commands/opsx-explore.md +171 -171
  5. package/.factory/commands/opsx-propose.md +104 -104
  6. package/.factory/skills/openspec-apply-change/SKILL.md +156 -156
  7. package/.factory/skills/openspec-archive-change/SKILL.md +114 -114
  8. package/.factory/skills/openspec-explore/SKILL.md +288 -288
  9. package/.factory/skills/openspec-propose/SKILL.md +110 -110
  10. package/.github/workflows/ci.yml +110 -74
  11. package/.github/workflows/release.yml +67 -0
  12. package/.iflow/commands/opsx-apply.md +152 -152
  13. package/.iflow/commands/opsx-archive.md +157 -157
  14. package/.iflow/commands/opsx-explore.md +173 -173
  15. package/.iflow/commands/opsx-propose.md +106 -106
  16. package/.iflow/skills/openspec-apply-change/SKILL.md +156 -156
  17. package/.iflow/skills/openspec-archive-change/SKILL.md +114 -114
  18. package/.iflow/skills/openspec-explore/SKILL.md +288 -288
  19. package/.iflow/skills/openspec-propose/SKILL.md +110 -110
  20. package/.prettierrc +11 -11
  21. package/AGENTS.md +167 -169
  22. package/README.md +276 -195
  23. package/README.zh-CN.md +245 -193
  24. package/dist/src/cli/agents/claude-code.d.ts +5 -0
  25. package/dist/src/cli/agents/claude-code.d.ts.map +1 -0
  26. package/dist/src/cli/agents/claude-code.js +14 -0
  27. package/dist/src/cli/agents/claude-code.js.map +1 -0
  28. package/dist/src/cli/agents/cline.d.ts +5 -0
  29. package/dist/src/cli/agents/cline.d.ts.map +1 -0
  30. package/dist/src/cli/agents/cline.js +14 -0
  31. package/dist/src/cli/agents/cline.js.map +1 -0
  32. package/dist/src/cli/agents/codex.d.ts +5 -0
  33. package/dist/src/cli/agents/codex.d.ts.map +1 -0
  34. package/dist/src/cli/agents/codex.js +14 -0
  35. package/dist/src/cli/agents/codex.js.map +1 -0
  36. package/dist/src/cli/agents/cursor.d.ts +5 -0
  37. package/dist/src/cli/agents/cursor.d.ts.map +1 -0
  38. package/dist/src/cli/agents/cursor.js +14 -0
  39. package/dist/src/cli/agents/cursor.js.map +1 -0
  40. package/dist/src/cli/agents/factory-droid.d.ts +5 -0
  41. package/dist/src/cli/agents/factory-droid.d.ts.map +1 -0
  42. package/dist/src/cli/agents/factory-droid.js +14 -0
  43. package/dist/src/cli/agents/factory-droid.js.map +1 -0
  44. package/dist/src/cli/agents/gemini-cli.d.ts +5 -0
  45. package/dist/src/cli/agents/gemini-cli.d.ts.map +1 -0
  46. package/dist/src/cli/agents/gemini-cli.js +14 -0
  47. package/dist/src/cli/agents/gemini-cli.js.map +1 -0
  48. package/dist/src/cli/agents/index.d.ts +14 -0
  49. package/dist/src/cli/agents/index.d.ts.map +1 -0
  50. package/dist/src/cli/agents/index.js +30 -0
  51. package/dist/src/cli/agents/index.js.map +1 -0
  52. package/dist/src/cli/agents/windsurf.d.ts +5 -0
  53. package/dist/src/cli/agents/windsurf.d.ts.map +1 -0
  54. package/dist/src/cli/agents/windsurf.js +14 -0
  55. package/dist/src/cli/agents/windsurf.js.map +1 -0
  56. package/dist/src/cli/index.d.ts +8 -0
  57. package/dist/src/cli/index.d.ts.map +1 -0
  58. package/dist/src/cli/index.js +168 -0
  59. package/dist/src/cli/index.js.map +1 -0
  60. package/dist/src/cli/init.d.ts +34 -0
  61. package/dist/src/cli/init.d.ts.map +1 -0
  62. package/dist/src/cli/init.js +160 -0
  63. package/dist/src/cli/init.js.map +1 -0
  64. package/dist/src/cli/instructions.d.ts +29 -0
  65. package/dist/src/cli/instructions.d.ts.map +1 -0
  66. package/dist/src/cli/instructions.js +141 -0
  67. package/dist/src/cli/instructions.js.map +1 -0
  68. package/dist/src/cli/types.d.ts +22 -0
  69. package/dist/src/cli/types.d.ts.map +1 -0
  70. package/dist/src/cli/types.js +86 -0
  71. package/dist/src/cli/types.js.map +1 -0
  72. package/dist/src/contracts/mcp.js +34 -34
  73. package/dist/src/contracts/schemas.js.map +1 -1
  74. package/dist/src/server/mcp-server.d.ts.map +1 -1
  75. package/dist/src/server/mcp-server.js +7 -14
  76. package/dist/src/server/mcp-server.js.map +1 -1
  77. package/dist/src/services/embedding-service.d.ts.map +1 -1
  78. package/dist/src/services/embedding-service.js +1 -1
  79. package/dist/src/services/embedding-service.js.map +1 -1
  80. package/dist/src/services/memory-service.d.ts.map +1 -1
  81. package/dist/src/services/memory-service.js.map +1 -1
  82. package/dist/src/storage/markdown-storage.d.ts.map +1 -1
  83. package/dist/src/storage/markdown-storage.js +1 -1
  84. package/dist/src/storage/markdown-storage.js.map +1 -1
  85. package/dist/src/storage/vector-index.d.ts.map +1 -1
  86. package/dist/src/storage/vector-index.js +4 -5
  87. package/dist/src/storage/vector-index.js.map +1 -1
  88. package/docs/README.md +21 -0
  89. package/docs/mcp-tools.md +136 -0
  90. package/docs/user-guide.md +182 -0
  91. package/package.json +61 -59
  92. package/src/cli/agents/claude-code.ts +14 -0
  93. package/src/cli/agents/cline.ts +14 -0
  94. package/src/cli/agents/codex.ts +14 -0
  95. package/src/cli/agents/cursor.ts +14 -0
  96. package/src/cli/agents/factory-droid.ts +14 -0
  97. package/src/cli/agents/gemini-cli.ts +14 -0
  98. package/src/cli/agents/index.ts +36 -0
  99. package/src/cli/agents/windsurf.ts +14 -0
  100. package/src/cli/index.ts +192 -0
  101. package/src/cli/init.ts +218 -0
  102. package/src/cli/instructions.ts +156 -0
  103. package/src/cli/types.ts +112 -0
  104. package/src/contracts/index.ts +12 -12
  105. package/src/contracts/mcp.ts +223 -223
  106. package/src/contracts/schemas.ts +307 -307
  107. package/src/contracts/types.ts +410 -410
  108. package/src/index.ts +8 -8
  109. package/src/server/index.ts +5 -5
  110. package/src/server/mcp-server.ts +169 -186
  111. package/src/services/embedding-service.ts +114 -114
  112. package/src/services/index.ts +5 -5
  113. package/src/services/memory-service.ts +656 -663
  114. package/src/storage/frontmatter-parser.ts +243 -243
  115. package/src/storage/index.ts +6 -6
  116. package/src/storage/markdown-storage.ts +228 -236
  117. package/src/storage/vector-index.ts +159 -160
  118. package/src/utils/index.ts +5 -5
  119. package/src/utils/slugify.ts +63 -63
  120. package/test/cli/init.test.ts +380 -0
  121. package/test/contracts/schemas.test.ts +313 -313
  122. package/test/contracts/types.test.ts +21 -21
  123. package/test/frontmatter-parser-more.test.ts +94 -94
  124. package/test/server/mcp-server.test.ts +211 -210
  125. package/test/services/memory-service-edge.test.ts +248 -248
  126. package/test/services/memory-service.test.ts +291 -279
  127. package/test/storage/frontmatter-parser.test.ts +223 -223
  128. package/test/storage/markdown-storage.test.ts +226 -217
  129. package/test/storage/storage-edge.test.ts +238 -238
  130. package/test/storage/vector-index.test.ts +149 -153
  131. package/test/utils/slugify-edge.test.ts +94 -94
  132. package/test/utils/slugify.test.ts +72 -68
  133. package/tsconfig.json +25 -25
  134. package/tsconfig.test.json +8 -8
  135. package/vitest.config.ts +29 -29
  136. package/docs/architecture-diagrams.md +0 -368
  137. package/docs/architecture.md +0 -381
  138. package/docs/contracts.md +0 -190
  139. package/docs/prompt-template.md +0 -33
  140. package/docs/proposals/mcp-typescript-sdk-refactor.md +0 -568
  141. package/docs/proposals/proposal-close-gates.md +0 -58
  142. package/docs/tool-calling-policy.md +0 -101
  143. package/docs/vector-search.md +0 -306
@@ -1,217 +1,226 @@
1
- /**
2
- * Markdown Storage Tests
3
- * Tests for the MarkdownStorage class
4
- */
5
-
6
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
- import { mkdtempSync, rmSync, existsSync, readFileSync } from 'fs';
8
- import { tmpdir } from 'os';
9
- import { join } from 'path';
10
- import { MarkdownStorage, StorageError } from '../../src/storage/markdown-storage.js';
11
- import type { Memory } from '../../src/contracts/types.js';
12
-
13
- describe('MarkdownStorage', () => {
14
- let tempDir: string;
15
- let storage: MarkdownStorage;
16
-
17
- beforeEach(() => {
18
- tempDir = mkdtempSync(join(tmpdir(), 'memhub-storage-test-'));
19
- storage = new MarkdownStorage({ storagePath: tempDir });
20
- });
21
-
22
- afterEach(() => {
23
- rmSync(tempDir, { recursive: true, force: true });
24
- });
25
-
26
- describe('write', () => {
27
- const sampleMemory: Memory = {
28
- id: '550e8400-e29b-41d4-a716-446655440000',
29
- createdAt: '2024-03-15T10:30:00Z',
30
- updatedAt: '2024-03-15T10:30:00Z',
31
- sessionId: '550e8400-e29b-41d4-a716-446655440999',
32
- tags: ['test', 'memory'],
33
- category: 'testing',
34
- importance: 3,
35
- title: 'Test Memory',
36
- content: 'This is the content of the test memory.',
37
- };
38
-
39
- it('should write memory to markdown file', async () => {
40
- const result = await storage.write(sampleMemory);
41
- expect(existsSync(result)).toBe(true);
42
- expect(result).toContain(`${sampleMemory.createdAt.split('T')[0]}`);
43
- expect(result).toContain(sampleMemory.sessionId as string);
44
- });
45
-
46
- it('should write valid YAML front matter', async () => {
47
- const filePath = await storage.write(sampleMemory);
48
- const content = readFileSync(filePath, 'utf-8');
49
- expect(content).toMatch(/^---\n/);
50
- expect(content).toContain('id: "550e8400-e29b-41d4-a716-446655440000"');
51
- expect(content).toContain('created_at: "2024-03-15T10:30:00Z"');
52
- expect(content).toContain('session_id: "550e8400-e29b-41d4-a716-446655440999"');
53
- });
54
-
55
- it('should write markdown body with title', async () => {
56
- const filePath = await storage.write(sampleMemory);
57
- const content = readFileSync(filePath, 'utf-8');
58
- expect(content).toContain('# Test Memory');
59
- expect(content).toContain('This is the content');
60
- });
61
-
62
- it('should write tags as YAML array', async () => {
63
- const filePath = await storage.write(sampleMemory);
64
- const content = readFileSync(filePath, 'utf-8');
65
- expect(content).toContain('tags:');
66
- expect(content).toContain('test');
67
- expect(content).toContain('memory');
68
- });
69
-
70
- it('should return nested file path', async () => {
71
- const result = await storage.write(sampleMemory);
72
- expect(result).toContain('test-memory.md');
73
- expect(result).toContain('2024-03-15');
74
- expect(result).toContain('550e8400-e29b-41d4-a716-446655440999');
75
- });
76
- });
77
-
78
- describe('read', () => {
79
- it('should read memory from markdown file', async () => {
80
- // Create a test file first
81
- const testContent = `---
82
- id: "550e8400-e29b-41d4-a716-446655440000"
83
- created_at: "2024-03-15T10:30:00Z"
84
- updated_at: "2024-03-15T10:30:00Z"
85
- tags:
86
- - test
87
- category: "testing"
88
- importance: 3
89
- ---
90
-
91
- # Test Memory
92
-
93
- This is the content.
94
- `;
95
- const filePath = join(tempDir, 'test.md');
96
- // Use sync write for test setup
97
- const { writeFileSync } = await import('fs');
98
- writeFileSync(filePath, testContent);
99
-
100
- const memory = await storage.read('550e8400-e29b-41d4-a716-446655440000');
101
- expect(memory.id).toBe('550e8400-e29b-41d4-a716-446655440000');
102
- expect(memory.title).toBe('Test Memory');
103
- });
104
-
105
- it('should throw error when file not found', async () => {
106
- await expect(
107
- storage.read('550e8400-e29b-41d4-a716-446655440000')
108
- ).rejects.toThrow(StorageError);
109
- });
110
-
111
- it('should parse front matter correctly', async () => {
112
- const testContent = `---
113
- id: "550e8400-e29b-41d4-a716-446655440000"
114
- created_at: "2024-03-15T10:30:00Z"
115
- updated_at: "2024-03-15T14:20:00Z"
116
- tags:
117
- - tag1
118
- - tag2
119
- category: "work"
120
- importance: 4
121
- ---
122
-
123
- # Test Title
124
-
125
- Test content.
126
- `;
127
- const { writeFileSync } = await import('fs');
128
- writeFileSync(join(tempDir, 'test.md'), testContent);
129
-
130
- const memory = await storage.read('550e8400-e29b-41d4-a716-446655440000');
131
- expect(memory.tags).toEqual(['tag1', 'tag2']);
132
- expect(memory.category).toBe('work');
133
- expect(memory.importance).toBe(4);
134
- });
135
- });
136
-
137
- describe('delete', () => {
138
- it('should delete memory file', async () => {
139
- // Create a test file first
140
- const testContent = `---
141
- id: "550e8400-e29b-41d4-a716-446655440000"
142
- created_at: "2024-03-15T10:30:00Z"
143
- updated_at: "2024-03-15T10:30:00Z"
144
- tags: []
145
- category: "general"
146
- importance: 3
147
- ---
148
-
149
- # Test
150
- `;
151
- const filePath = join(tempDir, 'test.md');
152
- const { writeFileSync } = await import('fs');
153
- writeFileSync(filePath, testContent);
154
-
155
- await storage.delete('550e8400-e29b-41d4-a716-446655440000');
156
- expect(existsSync(filePath)).toBe(false);
157
- });
158
-
159
- it('should throw error when file not found', async () => {
160
- await expect(
161
- storage.delete('550e8400-e29b-41d4-a716-446655440000')
162
- ).rejects.toThrow(StorageError);
163
- });
164
- });
165
-
166
- describe('list', () => {
167
- it('should list all memory files', async () => {
168
- const { writeFileSync } = await import('fs');
169
- writeFileSync(join(tempDir, '2024-03-15-a.md'), '---\nid: "a"\ncreated_at: "2024-03-15T10:30:00Z"\nupdated_at: "2024-03-15T10:30:00Z"\ntags: []\ncategory: "general"\nimportance: 3\n---\n\n# A');
170
- writeFileSync(join(tempDir, '2024-03-16-b.md'), '---\nid: "b"\ncreated_at: "2024-03-16T10:30:00Z"\nupdated_at: "2024-03-16T10:30:00Z"\ntags: []\ncategory: "general"\nimportance: 3\n---\n\n# B');
171
-
172
- const files = await storage.list();
173
- expect(files).toHaveLength(2);
174
- });
175
-
176
- it('should return empty array when no files exist', async () => {
177
- const files = await storage.list();
178
- expect(files).toEqual([]);
179
- });
180
-
181
- it('should only include .md files', async () => {
182
- const { writeFileSync } = await import('fs');
183
- writeFileSync(join(tempDir, 'test.md'), '---\nid: "test"\ncreated_at: "2024-03-15T10:30:00Z"\nupdated_at: "2024-03-15T10:30:00Z"\ntags: []\ncategory: "general"\nimportance: 3\n---\n\n# Test');
184
- writeFileSync(join(tempDir, 'test.txt'), 'not markdown');
185
-
186
- const files = await storage.list();
187
- expect(files).toHaveLength(1);
188
- expect(files[0].filename).toBe('test.md');
189
- });
190
- });
191
-
192
- describe('findById', () => {
193
- it('should find file by memory ID', async () => {
194
- const testContent = `---
195
- id: "550e8400-e29b-41d4-a716-446655440000"
196
- created_at: "2024-03-15T10:30:00Z"
197
- updated_at: "2024-03-15T10:30:00Z"
198
- tags: []
199
- category: "general"
200
- importance: 3
201
- ---
202
-
203
- # Test
204
- `;
205
- const { writeFileSync } = await import('fs');
206
- writeFileSync(join(tempDir, 'test.md'), testContent);
207
-
208
- const filePath = await storage.findById('550e8400-e29b-41d4-a716-446655440000');
209
- expect(filePath).toContain('test.md');
210
- });
211
-
212
- it('should return null when ID not found', async () => {
213
- const filePath = await storage.findById('550e8400-e29b-41d4-a716-446655440000');
214
- expect(filePath).toBeNull();
215
- });
216
- });
217
- });
1
+ /**
2
+ * Markdown Storage Tests
3
+ * Tests for the MarkdownStorage class
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import { mkdtempSync, rmSync, existsSync, readFileSync } from 'fs';
8
+ import { tmpdir } from 'os';
9
+ import { join } from 'path';
10
+ import { MarkdownStorage, StorageError } from '../../src/storage/markdown-storage.js';
11
+ import type { Memory } from '../../src/contracts/types.js';
12
+
13
+ describe('MarkdownStorage', () => {
14
+ let tempDir: string;
15
+ let storage: MarkdownStorage;
16
+
17
+ beforeEach(() => {
18
+ tempDir = mkdtempSync(join(tmpdir(), 'memhub-storage-test-'));
19
+ storage = new MarkdownStorage({ storagePath: tempDir });
20
+ });
21
+
22
+ afterEach(() => {
23
+ rmSync(tempDir, { recursive: true, force: true });
24
+ });
25
+
26
+ describe('write', () => {
27
+ const sampleMemory: Memory = {
28
+ id: '550e8400-e29b-41d4-a716-446655440000',
29
+ createdAt: '2024-03-15T10:30:00Z',
30
+ updatedAt: '2024-03-15T10:30:00Z',
31
+ sessionId: '550e8400-e29b-41d4-a716-446655440999',
32
+ tags: ['test', 'memory'],
33
+ category: 'testing',
34
+ importance: 3,
35
+ title: 'Test Memory',
36
+ content: 'This is the content of the test memory.',
37
+ };
38
+
39
+ it('should write memory to markdown file', async () => {
40
+ const result = await storage.write(sampleMemory);
41
+ expect(existsSync(result)).toBe(true);
42
+ expect(result).toContain(`${sampleMemory.createdAt.split('T')[0]}`);
43
+ expect(result).toContain(sampleMemory.sessionId as string);
44
+ });
45
+
46
+ it('should write valid YAML front matter', async () => {
47
+ const filePath = await storage.write(sampleMemory);
48
+ const content = readFileSync(filePath, 'utf-8');
49
+ expect(content).toMatch(/^---\n/);
50
+ expect(content).toContain('id: "550e8400-e29b-41d4-a716-446655440000"');
51
+ expect(content).toContain('created_at: "2024-03-15T10:30:00Z"');
52
+ expect(content).toContain('session_id: "550e8400-e29b-41d4-a716-446655440999"');
53
+ });
54
+
55
+ it('should write markdown body with title', async () => {
56
+ const filePath = await storage.write(sampleMemory);
57
+ const content = readFileSync(filePath, 'utf-8');
58
+ expect(content).toContain('# Test Memory');
59
+ expect(content).toContain('This is the content');
60
+ });
61
+
62
+ it('should write tags as YAML array', async () => {
63
+ const filePath = await storage.write(sampleMemory);
64
+ const content = readFileSync(filePath, 'utf-8');
65
+ expect(content).toContain('tags:');
66
+ expect(content).toContain('test');
67
+ expect(content).toContain('memory');
68
+ });
69
+
70
+ it('should return nested file path', async () => {
71
+ const result = await storage.write(sampleMemory);
72
+ expect(result).toContain('test-memory.md');
73
+ expect(result).toContain('2024-03-15');
74
+ expect(result).toContain('550e8400-e29b-41d4-a716-446655440999');
75
+ });
76
+ });
77
+
78
+ describe('read', () => {
79
+ it('should read memory from markdown file', async () => {
80
+ // Create a test file first
81
+ const testContent = `---
82
+ id: "550e8400-e29b-41d4-a716-446655440000"
83
+ created_at: "2024-03-15T10:30:00Z"
84
+ updated_at: "2024-03-15T10:30:00Z"
85
+ tags:
86
+ - test
87
+ category: "testing"
88
+ importance: 3
89
+ ---
90
+
91
+ # Test Memory
92
+
93
+ This is the content.
94
+ `;
95
+ const filePath = join(tempDir, 'test.md');
96
+ // Use sync write for test setup
97
+ const { writeFileSync } = await import('fs');
98
+ writeFileSync(filePath, testContent);
99
+
100
+ const memory = await storage.read('550e8400-e29b-41d4-a716-446655440000');
101
+ expect(memory.id).toBe('550e8400-e29b-41d4-a716-446655440000');
102
+ expect(memory.title).toBe('Test Memory');
103
+ });
104
+
105
+ it('should throw error when file not found', async () => {
106
+ await expect(storage.read('550e8400-e29b-41d4-a716-446655440000')).rejects.toThrow(
107
+ StorageError
108
+ );
109
+ });
110
+
111
+ it('should parse front matter correctly', async () => {
112
+ const testContent = `---
113
+ id: "550e8400-e29b-41d4-a716-446655440000"
114
+ created_at: "2024-03-15T10:30:00Z"
115
+ updated_at: "2024-03-15T14:20:00Z"
116
+ tags:
117
+ - tag1
118
+ - tag2
119
+ category: "work"
120
+ importance: 4
121
+ ---
122
+
123
+ # Test Title
124
+
125
+ Test content.
126
+ `;
127
+ const { writeFileSync } = await import('fs');
128
+ writeFileSync(join(tempDir, 'test.md'), testContent);
129
+
130
+ const memory = await storage.read('550e8400-e29b-41d4-a716-446655440000');
131
+ expect(memory.tags).toEqual(['tag1', 'tag2']);
132
+ expect(memory.category).toBe('work');
133
+ expect(memory.importance).toBe(4);
134
+ });
135
+ });
136
+
137
+ describe('delete', () => {
138
+ it('should delete memory file', async () => {
139
+ // Create a test file first
140
+ const testContent = `---
141
+ id: "550e8400-e29b-41d4-a716-446655440000"
142
+ created_at: "2024-03-15T10:30:00Z"
143
+ updated_at: "2024-03-15T10:30:00Z"
144
+ tags: []
145
+ category: "general"
146
+ importance: 3
147
+ ---
148
+
149
+ # Test
150
+ `;
151
+ const filePath = join(tempDir, 'test.md');
152
+ const { writeFileSync } = await import('fs');
153
+ writeFileSync(filePath, testContent);
154
+
155
+ await storage.delete('550e8400-e29b-41d4-a716-446655440000');
156
+ expect(existsSync(filePath)).toBe(false);
157
+ });
158
+
159
+ it('should throw error when file not found', async () => {
160
+ await expect(storage.delete('550e8400-e29b-41d4-a716-446655440000')).rejects.toThrow(
161
+ StorageError
162
+ );
163
+ });
164
+ });
165
+
166
+ describe('list', () => {
167
+ it('should list all memory files', async () => {
168
+ const { writeFileSync } = await import('fs');
169
+ writeFileSync(
170
+ join(tempDir, '2024-03-15-a.md'),
171
+ '---\nid: "a"\ncreated_at: "2024-03-15T10:30:00Z"\nupdated_at: "2024-03-15T10:30:00Z"\ntags: []\ncategory: "general"\nimportance: 3\n---\n\n# A'
172
+ );
173
+ writeFileSync(
174
+ join(tempDir, '2024-03-16-b.md'),
175
+ '---\nid: "b"\ncreated_at: "2024-03-16T10:30:00Z"\nupdated_at: "2024-03-16T10:30:00Z"\ntags: []\ncategory: "general"\nimportance: 3\n---\n\n# B'
176
+ );
177
+
178
+ const files = await storage.list();
179
+ expect(files).toHaveLength(2);
180
+ });
181
+
182
+ it('should return empty array when no files exist', async () => {
183
+ const files = await storage.list();
184
+ expect(files).toEqual([]);
185
+ });
186
+
187
+ it('should only include .md files', async () => {
188
+ const { writeFileSync } = await import('fs');
189
+ writeFileSync(
190
+ join(tempDir, 'test.md'),
191
+ '---\nid: "test"\ncreated_at: "2024-03-15T10:30:00Z"\nupdated_at: "2024-03-15T10:30:00Z"\ntags: []\ncategory: "general"\nimportance: 3\n---\n\n# Test'
192
+ );
193
+ writeFileSync(join(tempDir, 'test.txt'), 'not markdown');
194
+
195
+ const files = await storage.list();
196
+ expect(files).toHaveLength(1);
197
+ expect(files[0].filename).toBe('test.md');
198
+ });
199
+ });
200
+
201
+ describe('findById', () => {
202
+ it('should find file by memory ID', async () => {
203
+ const testContent = `---
204
+ id: "550e8400-e29b-41d4-a716-446655440000"
205
+ created_at: "2024-03-15T10:30:00Z"
206
+ updated_at: "2024-03-15T10:30:00Z"
207
+ tags: []
208
+ category: "general"
209
+ importance: 3
210
+ ---
211
+
212
+ # Test
213
+ `;
214
+ const { writeFileSync } = await import('fs');
215
+ writeFileSync(join(tempDir, 'test.md'), testContent);
216
+
217
+ const filePath = await storage.findById('550e8400-e29b-41d4-a716-446655440000');
218
+ expect(filePath).toContain('test.md');
219
+ });
220
+
221
+ it('should return null when ID not found', async () => {
222
+ const filePath = await storage.findById('550e8400-e29b-41d4-a716-446655440000');
223
+ expect(filePath).toBeNull();
224
+ });
225
+ });
226
+ });