@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,248 +1,248 @@
1
- /**
2
- * Memory Service Edge Case Tests
3
- * Additional tests for better coverage
4
- */
5
-
6
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
- import { mkdtempSync, rmSync } from 'fs';
8
- import { tmpdir } from 'os';
9
- import { join } from 'path';
10
- import { MemoryService, ServiceError } from '../../src/services/memory-service.js';
11
-
12
- describe('MemoryService Edge Cases', () => {
13
- let tempDir: string;
14
- let memoryService: MemoryService;
15
-
16
- beforeEach(() => {
17
- tempDir = mkdtempSync(join(tmpdir(), 'memhub-edge-test-'));
18
- memoryService = new MemoryService({ storagePath: tempDir, vectorSearch: false });
19
- });
20
-
21
- afterEach(() => {
22
- rmSync(tempDir, { recursive: true, force: true });
23
- });
24
-
25
- describe('create with edge cases', () => {
26
- it('should handle empty content', async () => {
27
- const result = await memoryService.create({
28
- title: 'Empty Content',
29
- content: '',
30
- });
31
- expect(result.memory.content).toBe('');
32
- });
33
-
34
- it('should handle very long title', async () => {
35
- const longTitle = 'A'.repeat(200);
36
- const result = await memoryService.create({
37
- title: longTitle,
38
- content: 'Content',
39
- });
40
- expect(result.memory.title).toBe(longTitle);
41
- });
42
-
43
- it('should handle special characters in title', async () => {
44
- const result = await memoryService.create({
45
- title: 'Title with @#$%^&*() special chars!',
46
- content: 'Content',
47
- });
48
- expect(result.memory.title).toBe('Title with @#$%^&*() special chars!');
49
- });
50
-
51
- it('should handle many tags', async () => {
52
- const tags = Array.from({ length: 20 }, (_, i) => `tag${i}`);
53
- const result = await memoryService.create({
54
- title: 'Many Tags',
55
- content: 'Content',
56
- tags,
57
- });
58
- expect(result.memory.tags).toHaveLength(20);
59
- });
60
- });
61
-
62
- describe('update edge cases', () => {
63
- it('should update only tags', async () => {
64
- const created = await memoryService.create({
65
- title: 'Title',
66
- content: 'Content',
67
- tags: ['old'],
68
- });
69
- const updated = await memoryService.update({
70
- id: created.id,
71
- tags: ['new1', 'new2'],
72
- });
73
- expect(updated.memory.tags).toEqual(['new1', 'new2']);
74
- expect(updated.memory.title).toBe('Title');
75
- expect(updated.memory.content).toBe('Content');
76
- });
77
-
78
- it('should update only importance', async () => {
79
- const created = await memoryService.create({
80
- title: 'Title',
81
- content: 'Content',
82
- importance: 1,
83
- });
84
- const updated = await memoryService.update({
85
- id: created.id,
86
- importance: 5,
87
- });
88
- expect(updated.memory.importance).toBe(5);
89
- });
90
-
91
- it('should update only category', async () => {
92
- const created = await memoryService.create({
93
- title: 'Title',
94
- content: 'Content',
95
- category: 'old-category',
96
- });
97
- const updated = await memoryService.update({
98
- id: created.id,
99
- category: 'new-category',
100
- });
101
- expect(updated.memory.category).toBe('new-category');
102
- });
103
- });
104
-
105
- describe('list with various filters', () => {
106
- beforeEach(async () => {
107
- // Create test data with various dates
108
- const baseDate = new Date('2024-01-15');
109
- for (let i = 0; i < 5; i++) {
110
- const date = new Date(baseDate);
111
- date.setDate(date.getDate() + i);
112
- await memoryService.create({
113
- title: `Memory ${i}`,
114
- content: 'Content',
115
- category: i % 2 === 0 ? 'even' : 'odd',
116
- tags: [`tag${i}`, 'common'],
117
- });
118
- }
119
- });
120
-
121
- it('should filter by date range', async () => {
122
- // The created memories will have current timestamp, so we need to use a wide range
123
- const now = new Date();
124
- const yesterday = new Date(now);
125
- yesterday.setDate(yesterday.getDate() - 1);
126
- const tomorrow = new Date(now);
127
- tomorrow.setDate(tomorrow.getDate() + 1);
128
-
129
- const result = await memoryService.list({
130
- fromDate: yesterday.toISOString(),
131
- toDate: tomorrow.toISOString(),
132
- });
133
- expect(result.memories.length).toBeGreaterThan(0);
134
- });
135
-
136
- it('should handle empty result with strict filters', async () => {
137
- const result = await memoryService.list({
138
- category: 'non-existent',
139
- });
140
- expect(result.memories).toHaveLength(0);
141
- expect(result.total).toBe(0);
142
- expect(result.hasMore).toBe(false);
143
- });
144
-
145
- it('should sort by importance', async () => {
146
- await memoryService.create({
147
- title: 'High Importance',
148
- content: 'Content',
149
- importance: 5,
150
- });
151
- await memoryService.create({
152
- title: 'Low Importance',
153
- content: 'Content',
154
- importance: 1,
155
- });
156
-
157
- const result = await memoryService.list({
158
- sortBy: 'importance',
159
- sortOrder: 'desc',
160
- });
161
- expect(result.memories[0].importance).toBe(5);
162
- });
163
-
164
- it('should handle pagination across multiple pages', async () => {
165
- const page1 = await memoryService.list({ limit: 2, offset: 0 });
166
- expect(page1.memories).toHaveLength(2);
167
- expect(page1.hasMore).toBe(true);
168
-
169
- const page2 = await memoryService.list({ limit: 2, offset: 2 });
170
- expect(page2.memories).toHaveLength(2);
171
- expect(page2.hasMore).toBe(true);
172
-
173
- const page3 = await memoryService.list({ limit: 2, offset: 4 });
174
- expect(page3.memories.length).toBeGreaterThanOrEqual(1);
175
- });
176
- });
177
-
178
- describe('search edge cases', () => {
179
- beforeEach(async () => {
180
- await memoryService.create({
181
- title: 'Project Alpha',
182
- content: 'This is about the alpha project development.',
183
- tags: ['alpha', 'dev'],
184
- });
185
- await memoryService.create({
186
- title: 'Project Beta',
187
- content: 'Beta testing is in progress.',
188
- tags: ['beta', 'testing'],
189
- });
190
- });
191
-
192
- it('should search with case insensitivity', async () => {
193
- const result = await memoryService.search({ query: 'ALPHA' });
194
- expect(result.results.length).toBeGreaterThan(0);
195
- });
196
-
197
- it('should handle search with no results', async () => {
198
- const result = await memoryService.search({ query: 'xyznonexistent' });
199
- expect(result.results).toHaveLength(0);
200
- expect(result.total).toBe(0);
201
- });
202
-
203
- it('should limit search results', async () => {
204
- const result = await memoryService.search({ query: 'project', limit: 1 });
205
- expect(result.results.length).toBeLessThanOrEqual(1);
206
- });
207
-
208
- it('should search with category filter', async () => {
209
- await memoryService.create({
210
- title: 'Work Item',
211
- content: 'Work content',
212
- category: 'work',
213
- });
214
- const result = await memoryService.search({
215
- query: 'work',
216
- category: 'work',
217
- });
218
- expect(result.results.length).toBeGreaterThan(0);
219
- });
220
- });
221
-
222
- describe('getCategories and getTags edge cases', () => {
223
- it('should return sorted categories', async () => {
224
- await memoryService.create({ title: 'A', content: 'C', category: 'zebra' });
225
- await memoryService.create({ title: 'B', content: 'C', category: 'alpha' });
226
- await memoryService.create({ title: 'C', content: 'C', category: 'beta' });
227
-
228
- const result = await memoryService.getCategories();
229
- expect(result.categories).toEqual(['alpha', 'beta', 'zebra']);
230
- });
231
-
232
- it('should return sorted tags', async () => {
233
- await memoryService.create({ title: 'A', content: 'C', tags: ['zebra', 'alpha'] });
234
- await memoryService.create({ title: 'B', content: 'C', tags: ['beta'] });
235
-
236
- const result = await memoryService.getTags();
237
- expect(result.tags).toEqual(['alpha', 'beta', 'zebra']);
238
- });
239
-
240
- it('should handle duplicate tags across memories', async () => {
241
- await memoryService.create({ title: 'A', content: 'C', tags: ['shared'] });
242
- await memoryService.create({ title: 'B', content: 'C', tags: ['shared'] });
243
-
244
- const result = await memoryService.getTags();
245
- expect(result.tags).toEqual(['shared']);
246
- });
247
- });
248
- });
1
+ /**
2
+ * Memory Service Edge Case Tests
3
+ * Additional tests for better coverage
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import { mkdtempSync, rmSync } from 'fs';
8
+ import { tmpdir } from 'os';
9
+ import { join } from 'path';
10
+ import { MemoryService, ServiceError } from '../../src/services/memory-service.js';
11
+
12
+ describe('MemoryService Edge Cases', () => {
13
+ let tempDir: string;
14
+ let memoryService: MemoryService;
15
+
16
+ beforeEach(() => {
17
+ tempDir = mkdtempSync(join(tmpdir(), 'memhub-edge-test-'));
18
+ memoryService = new MemoryService({ storagePath: tempDir, vectorSearch: false });
19
+ });
20
+
21
+ afterEach(() => {
22
+ rmSync(tempDir, { recursive: true, force: true });
23
+ });
24
+
25
+ describe('create with edge cases', () => {
26
+ it('should handle empty content', async () => {
27
+ const result = await memoryService.create({
28
+ title: 'Empty Content',
29
+ content: '',
30
+ });
31
+ expect(result.memory.content).toBe('');
32
+ });
33
+
34
+ it('should handle very long title', async () => {
35
+ const longTitle = 'A'.repeat(200);
36
+ const result = await memoryService.create({
37
+ title: longTitle,
38
+ content: 'Content',
39
+ });
40
+ expect(result.memory.title).toBe(longTitle);
41
+ });
42
+
43
+ it('should handle special characters in title', async () => {
44
+ const result = await memoryService.create({
45
+ title: 'Title with @#$%^&*() special chars!',
46
+ content: 'Content',
47
+ });
48
+ expect(result.memory.title).toBe('Title with @#$%^&*() special chars!');
49
+ });
50
+
51
+ it('should handle many tags', async () => {
52
+ const tags = Array.from({ length: 20 }, (_, i) => `tag${i}`);
53
+ const result = await memoryService.create({
54
+ title: 'Many Tags',
55
+ content: 'Content',
56
+ tags,
57
+ });
58
+ expect(result.memory.tags).toHaveLength(20);
59
+ });
60
+ });
61
+
62
+ describe('update edge cases', () => {
63
+ it('should update only tags', async () => {
64
+ const created = await memoryService.create({
65
+ title: 'Title',
66
+ content: 'Content',
67
+ tags: ['old'],
68
+ });
69
+ const updated = await memoryService.update({
70
+ id: created.id,
71
+ tags: ['new1', 'new2'],
72
+ });
73
+ expect(updated.memory.tags).toEqual(['new1', 'new2']);
74
+ expect(updated.memory.title).toBe('Title');
75
+ expect(updated.memory.content).toBe('Content');
76
+ });
77
+
78
+ it('should update only importance', async () => {
79
+ const created = await memoryService.create({
80
+ title: 'Title',
81
+ content: 'Content',
82
+ importance: 1,
83
+ });
84
+ const updated = await memoryService.update({
85
+ id: created.id,
86
+ importance: 5,
87
+ });
88
+ expect(updated.memory.importance).toBe(5);
89
+ });
90
+
91
+ it('should update only category', async () => {
92
+ const created = await memoryService.create({
93
+ title: 'Title',
94
+ content: 'Content',
95
+ category: 'old-category',
96
+ });
97
+ const updated = await memoryService.update({
98
+ id: created.id,
99
+ category: 'new-category',
100
+ });
101
+ expect(updated.memory.category).toBe('new-category');
102
+ });
103
+ });
104
+
105
+ describe('list with various filters', () => {
106
+ beforeEach(async () => {
107
+ // Create test data with various dates
108
+ const baseDate = new Date('2024-01-15');
109
+ for (let i = 0; i < 5; i++) {
110
+ const date = new Date(baseDate);
111
+ date.setDate(date.getDate() + i);
112
+ await memoryService.create({
113
+ title: `Memory ${i}`,
114
+ content: 'Content',
115
+ category: i % 2 === 0 ? 'even' : 'odd',
116
+ tags: [`tag${i}`, 'common'],
117
+ });
118
+ }
119
+ });
120
+
121
+ it('should filter by date range', async () => {
122
+ // The created memories will have current timestamp, so we need to use a wide range
123
+ const now = new Date();
124
+ const yesterday = new Date(now);
125
+ yesterday.setDate(yesterday.getDate() - 1);
126
+ const tomorrow = new Date(now);
127
+ tomorrow.setDate(tomorrow.getDate() + 1);
128
+
129
+ const result = await memoryService.list({
130
+ fromDate: yesterday.toISOString(),
131
+ toDate: tomorrow.toISOString(),
132
+ });
133
+ expect(result.memories.length).toBeGreaterThan(0);
134
+ });
135
+
136
+ it('should handle empty result with strict filters', async () => {
137
+ const result = await memoryService.list({
138
+ category: 'non-existent',
139
+ });
140
+ expect(result.memories).toHaveLength(0);
141
+ expect(result.total).toBe(0);
142
+ expect(result.hasMore).toBe(false);
143
+ });
144
+
145
+ it('should sort by importance', async () => {
146
+ await memoryService.create({
147
+ title: 'High Importance',
148
+ content: 'Content',
149
+ importance: 5,
150
+ });
151
+ await memoryService.create({
152
+ title: 'Low Importance',
153
+ content: 'Content',
154
+ importance: 1,
155
+ });
156
+
157
+ const result = await memoryService.list({
158
+ sortBy: 'importance',
159
+ sortOrder: 'desc',
160
+ });
161
+ expect(result.memories[0].importance).toBe(5);
162
+ });
163
+
164
+ it('should handle pagination across multiple pages', async () => {
165
+ const page1 = await memoryService.list({ limit: 2, offset: 0 });
166
+ expect(page1.memories).toHaveLength(2);
167
+ expect(page1.hasMore).toBe(true);
168
+
169
+ const page2 = await memoryService.list({ limit: 2, offset: 2 });
170
+ expect(page2.memories).toHaveLength(2);
171
+ expect(page2.hasMore).toBe(true);
172
+
173
+ const page3 = await memoryService.list({ limit: 2, offset: 4 });
174
+ expect(page3.memories.length).toBeGreaterThanOrEqual(1);
175
+ });
176
+ });
177
+
178
+ describe('search edge cases', () => {
179
+ beforeEach(async () => {
180
+ await memoryService.create({
181
+ title: 'Project Alpha',
182
+ content: 'This is about the alpha project development.',
183
+ tags: ['alpha', 'dev'],
184
+ });
185
+ await memoryService.create({
186
+ title: 'Project Beta',
187
+ content: 'Beta testing is in progress.',
188
+ tags: ['beta', 'testing'],
189
+ });
190
+ });
191
+
192
+ it('should search with case insensitivity', async () => {
193
+ const result = await memoryService.search({ query: 'ALPHA' });
194
+ expect(result.results.length).toBeGreaterThan(0);
195
+ });
196
+
197
+ it('should handle search with no results', async () => {
198
+ const result = await memoryService.search({ query: 'xyznonexistent' });
199
+ expect(result.results).toHaveLength(0);
200
+ expect(result.total).toBe(0);
201
+ });
202
+
203
+ it('should limit search results', async () => {
204
+ const result = await memoryService.search({ query: 'project', limit: 1 });
205
+ expect(result.results.length).toBeLessThanOrEqual(1);
206
+ });
207
+
208
+ it('should search with category filter', async () => {
209
+ await memoryService.create({
210
+ title: 'Work Item',
211
+ content: 'Work content',
212
+ category: 'work',
213
+ });
214
+ const result = await memoryService.search({
215
+ query: 'work',
216
+ category: 'work',
217
+ });
218
+ expect(result.results.length).toBeGreaterThan(0);
219
+ });
220
+ });
221
+
222
+ describe('getCategories and getTags edge cases', () => {
223
+ it('should return sorted categories', async () => {
224
+ await memoryService.create({ title: 'A', content: 'C', category: 'zebra' });
225
+ await memoryService.create({ title: 'B', content: 'C', category: 'alpha' });
226
+ await memoryService.create({ title: 'C', content: 'C', category: 'beta' });
227
+
228
+ const result = await memoryService.getCategories();
229
+ expect(result.categories).toEqual(['alpha', 'beta', 'zebra']);
230
+ });
231
+
232
+ it('should return sorted tags', async () => {
233
+ await memoryService.create({ title: 'A', content: 'C', tags: ['zebra', 'alpha'] });
234
+ await memoryService.create({ title: 'B', content: 'C', tags: ['beta'] });
235
+
236
+ const result = await memoryService.getTags();
237
+ expect(result.tags).toEqual(['alpha', 'beta', 'zebra']);
238
+ });
239
+
240
+ it('should handle duplicate tags across memories', async () => {
241
+ await memoryService.create({ title: 'A', content: 'C', tags: ['shared'] });
242
+ await memoryService.create({ title: 'B', content: 'C', tags: ['shared'] });
243
+
244
+ const result = await memoryService.getTags();
245
+ expect(result.tags).toEqual(['shared']);
246
+ });
247
+ });
248
+ });