@aitytech/agentkits-memory 1.0.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 (116) hide show
  1. package/README.md +250 -0
  2. package/dist/cache-manager.d.ts +134 -0
  3. package/dist/cache-manager.d.ts.map +1 -0
  4. package/dist/cache-manager.js +407 -0
  5. package/dist/cache-manager.js.map +1 -0
  6. package/dist/cli/save.d.ts +20 -0
  7. package/dist/cli/save.d.ts.map +1 -0
  8. package/dist/cli/save.js +94 -0
  9. package/dist/cli/save.js.map +1 -0
  10. package/dist/cli/setup.d.ts +18 -0
  11. package/dist/cli/setup.d.ts.map +1 -0
  12. package/dist/cli/setup.js +163 -0
  13. package/dist/cli/setup.js.map +1 -0
  14. package/dist/cli/viewer.d.ts +21 -0
  15. package/dist/cli/viewer.d.ts.map +1 -0
  16. package/dist/cli/viewer.js +182 -0
  17. package/dist/cli/viewer.js.map +1 -0
  18. package/dist/hnsw-index.d.ts +111 -0
  19. package/dist/hnsw-index.d.ts.map +1 -0
  20. package/dist/hnsw-index.js +781 -0
  21. package/dist/hnsw-index.js.map +1 -0
  22. package/dist/hooks/cli.d.ts +20 -0
  23. package/dist/hooks/cli.d.ts.map +1 -0
  24. package/dist/hooks/cli.js +102 -0
  25. package/dist/hooks/cli.js.map +1 -0
  26. package/dist/hooks/context.d.ts +31 -0
  27. package/dist/hooks/context.d.ts.map +1 -0
  28. package/dist/hooks/context.js +64 -0
  29. package/dist/hooks/context.js.map +1 -0
  30. package/dist/hooks/index.d.ts +16 -0
  31. package/dist/hooks/index.d.ts.map +1 -0
  32. package/dist/hooks/index.js +20 -0
  33. package/dist/hooks/index.js.map +1 -0
  34. package/dist/hooks/observation.d.ts +30 -0
  35. package/dist/hooks/observation.d.ts.map +1 -0
  36. package/dist/hooks/observation.js +79 -0
  37. package/dist/hooks/observation.js.map +1 -0
  38. package/dist/hooks/service.d.ts +102 -0
  39. package/dist/hooks/service.d.ts.map +1 -0
  40. package/dist/hooks/service.js +454 -0
  41. package/dist/hooks/service.js.map +1 -0
  42. package/dist/hooks/session-init.d.ts +30 -0
  43. package/dist/hooks/session-init.d.ts.map +1 -0
  44. package/dist/hooks/session-init.js +54 -0
  45. package/dist/hooks/session-init.js.map +1 -0
  46. package/dist/hooks/summarize.d.ts +30 -0
  47. package/dist/hooks/summarize.d.ts.map +1 -0
  48. package/dist/hooks/summarize.js +74 -0
  49. package/dist/hooks/summarize.js.map +1 -0
  50. package/dist/hooks/types.d.ts +193 -0
  51. package/dist/hooks/types.d.ts.map +1 -0
  52. package/dist/hooks/types.js +137 -0
  53. package/dist/hooks/types.js.map +1 -0
  54. package/dist/index.d.ts +173 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +564 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/mcp/index.d.ts +9 -0
  59. package/dist/mcp/index.d.ts.map +1 -0
  60. package/dist/mcp/index.js +9 -0
  61. package/dist/mcp/index.js.map +1 -0
  62. package/dist/mcp/server.d.ts +22 -0
  63. package/dist/mcp/server.d.ts.map +1 -0
  64. package/dist/mcp/server.js +368 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/mcp/tools.d.ts +14 -0
  67. package/dist/mcp/tools.d.ts.map +1 -0
  68. package/dist/mcp/tools.js +110 -0
  69. package/dist/mcp/tools.js.map +1 -0
  70. package/dist/mcp/types.d.ts +100 -0
  71. package/dist/mcp/types.d.ts.map +1 -0
  72. package/dist/mcp/types.js +9 -0
  73. package/dist/mcp/types.js.map +1 -0
  74. package/dist/migration.d.ts +77 -0
  75. package/dist/migration.d.ts.map +1 -0
  76. package/dist/migration.js +457 -0
  77. package/dist/migration.js.map +1 -0
  78. package/dist/sqljs-backend.d.ts +128 -0
  79. package/dist/sqljs-backend.d.ts.map +1 -0
  80. package/dist/sqljs-backend.js +623 -0
  81. package/dist/sqljs-backend.js.map +1 -0
  82. package/dist/types.d.ts +481 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +73 -0
  85. package/dist/types.js.map +1 -0
  86. package/hooks.json +46 -0
  87. package/package.json +67 -0
  88. package/src/__tests__/index.test.ts +407 -0
  89. package/src/__tests__/sqljs-backend.test.ts +410 -0
  90. package/src/cache-manager.ts +515 -0
  91. package/src/cli/save.ts +109 -0
  92. package/src/cli/setup.ts +203 -0
  93. package/src/cli/viewer.ts +218 -0
  94. package/src/hnsw-index.ts +1013 -0
  95. package/src/hooks/__tests__/handlers.test.ts +298 -0
  96. package/src/hooks/__tests__/integration.test.ts +431 -0
  97. package/src/hooks/__tests__/service.test.ts +487 -0
  98. package/src/hooks/__tests__/types.test.ts +341 -0
  99. package/src/hooks/cli.ts +121 -0
  100. package/src/hooks/context.ts +77 -0
  101. package/src/hooks/index.ts +23 -0
  102. package/src/hooks/observation.ts +102 -0
  103. package/src/hooks/service.ts +582 -0
  104. package/src/hooks/session-init.ts +70 -0
  105. package/src/hooks/summarize.ts +89 -0
  106. package/src/hooks/types.ts +365 -0
  107. package/src/index.ts +755 -0
  108. package/src/mcp/__tests__/server.test.ts +181 -0
  109. package/src/mcp/index.ts +9 -0
  110. package/src/mcp/server.ts +441 -0
  111. package/src/mcp/tools.ts +113 -0
  112. package/src/mcp/types.ts +109 -0
  113. package/src/migration.ts +574 -0
  114. package/src/sql.js.d.ts +70 -0
  115. package/src/sqljs-backend.ts +789 -0
  116. package/src/types.ts +715 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * ProjectMemoryService Tests
3
+ *
4
+ * Tests for the high-level memory service.
5
+ *
6
+ * @module @agentkits/memory/__tests__/index.test
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import { tmpdir } from 'node:os';
13
+ import {
14
+ ProjectMemoryService,
15
+ createProjectMemory,
16
+ DEFAULT_NAMESPACES,
17
+ MemoryEntry,
18
+ createDefaultEntry,
19
+ } from '../index.js';
20
+
21
+ describe('ProjectMemoryService', () => {
22
+ let service: ProjectMemoryService;
23
+ let testDir: string;
24
+
25
+ beforeEach(async () => {
26
+ // Create temp directory for tests
27
+ testDir = path.join(tmpdir(), `memory-test-${Date.now()}`);
28
+ fs.mkdirSync(testDir, { recursive: true });
29
+
30
+ service = new ProjectMemoryService({
31
+ baseDir: testDir,
32
+ dbFilename: 'test.db',
33
+ cacheEnabled: false,
34
+ enableVectorIndex: false,
35
+ autoPersistInterval: 0,
36
+ });
37
+ await service.initialize();
38
+ });
39
+
40
+ afterEach(async () => {
41
+ await service.shutdown();
42
+ // Cleanup temp directory
43
+ fs.rmSync(testDir, { recursive: true, force: true });
44
+ });
45
+
46
+ describe('Initialization', () => {
47
+ it('should initialize successfully', async () => {
48
+ const newService = new ProjectMemoryService({
49
+ baseDir: path.join(testDir, 'init-test'),
50
+ dbFilename: 'init.db',
51
+ });
52
+
53
+ await newService.initialize();
54
+ const health = await newService.healthCheck();
55
+
56
+ expect(health.status).toBe('healthy');
57
+ await newService.shutdown();
58
+ });
59
+
60
+ it('should create directory if not exists', async () => {
61
+ const newDir = path.join(testDir, 'new-dir');
62
+
63
+ const newService = new ProjectMemoryService({
64
+ baseDir: newDir,
65
+ dbFilename: 'new.db',
66
+ });
67
+
68
+ await newService.initialize();
69
+ expect(fs.existsSync(newDir)).toBe(true);
70
+
71
+ await newService.shutdown();
72
+ });
73
+
74
+ it('should accept string as baseDir', async () => {
75
+ const dir = path.join(testDir, 'string-dir');
76
+ const newService = new ProjectMemoryService(dir);
77
+
78
+ await newService.initialize();
79
+ expect(fs.existsSync(dir)).toBe(true);
80
+
81
+ await newService.shutdown();
82
+ });
83
+ });
84
+
85
+ describe('Store and Retrieve', () => {
86
+ it('should store entry via storeEntry convenience method', async () => {
87
+ const entry = await service.storeEntry({
88
+ key: 'test-key',
89
+ content: 'Test content',
90
+ namespace: 'test',
91
+ tags: ['tag1'],
92
+ });
93
+
94
+ expect(entry.id).toBeDefined();
95
+ expect(entry.key).toBe('test-key');
96
+ expect(entry.content).toBe('Test content');
97
+ });
98
+
99
+ it('should get entry by id', async () => {
100
+ const stored = await service.storeEntry({
101
+ key: 'get-test',
102
+ content: 'Content',
103
+ namespace: 'test',
104
+ });
105
+
106
+ const retrieved = await service.get(stored.id);
107
+
108
+ expect(retrieved).not.toBeNull();
109
+ expect(retrieved!.id).toBe(stored.id);
110
+ });
111
+
112
+ it('should get entry by namespace and key', async () => {
113
+ await service.storeEntry({
114
+ key: 'ns-key-test',
115
+ content: 'Content',
116
+ namespace: 'my-namespace',
117
+ });
118
+
119
+ const retrieved = await service.getByKey('my-namespace', 'ns-key-test');
120
+
121
+ expect(retrieved).not.toBeNull();
122
+ expect(retrieved!.key).toBe('ns-key-test');
123
+ expect(retrieved!.namespace).toBe('my-namespace');
124
+ });
125
+
126
+ it('should update entry', async () => {
127
+ const stored = await service.storeEntry({
128
+ key: 'update-test',
129
+ content: 'Original',
130
+ namespace: 'test',
131
+ });
132
+
133
+ const updated = await service.update(stored.id, { content: 'Updated' });
134
+
135
+ expect(updated!.content).toBe('Updated');
136
+ });
137
+
138
+ it('should delete entry', async () => {
139
+ const stored = await service.storeEntry({
140
+ key: 'delete-test',
141
+ content: 'To delete',
142
+ namespace: 'test',
143
+ });
144
+
145
+ expect(await service.count()).toBe(1);
146
+
147
+ const deleted = await service.delete(stored.id);
148
+ expect(deleted).toBe(true);
149
+ expect(await service.count()).toBe(0);
150
+ });
151
+ });
152
+
153
+ describe('Query Operations', () => {
154
+ beforeEach(async () => {
155
+ await service.storeEntry({ key: 'p1', content: 'Pattern 1', namespace: 'patterns', tags: ['auth'] });
156
+ await service.storeEntry({ key: 'p2', content: 'Pattern 2', namespace: 'patterns', tags: ['api'] });
157
+ await service.storeEntry({ key: 'd1', content: 'Decision 1', namespace: 'decisions', tags: ['db'] });
158
+ });
159
+
160
+ it('should query all entries', async () => {
161
+ const results = await service.query({ type: 'hybrid', limit: 10 });
162
+ expect(results.length).toBe(3);
163
+ });
164
+
165
+ it('should query by namespace', async () => {
166
+ const results = await service.query({ type: 'hybrid', namespace: 'patterns', limit: 10 });
167
+ expect(results.length).toBe(2);
168
+ });
169
+
170
+ it('should use getByNamespace convenience method', async () => {
171
+ const results = await service.getByNamespace('patterns');
172
+ expect(results.length).toBe(2);
173
+ });
174
+ });
175
+
176
+ describe('Get or Create', () => {
177
+ it('should create entry if not exists', async () => {
178
+ const entry = await service.getOrCreate('test-ns', 'new-key', () => ({
179
+ key: 'new-key',
180
+ content: 'New content',
181
+ namespace: 'test-ns',
182
+ }));
183
+
184
+ expect(entry.content).toBe('New content');
185
+ });
186
+
187
+ it('should return existing entry if exists', async () => {
188
+ await service.storeEntry({
189
+ key: 'existing-key',
190
+ content: 'Existing content',
191
+ namespace: 'test-ns',
192
+ });
193
+
194
+ const entry = await service.getOrCreate('test-ns', 'existing-key', () => ({
195
+ key: 'existing-key',
196
+ content: 'New content',
197
+ namespace: 'test-ns',
198
+ }));
199
+
200
+ expect(entry.content).toBe('Existing content');
201
+ });
202
+ });
203
+
204
+ describe('Session Management', () => {
205
+ it('should start session', async () => {
206
+ const session = await service.startSession();
207
+
208
+ expect(session.id).toBeDefined();
209
+ expect(session.status).toBe('active');
210
+ expect(session.startedAt).toBeDefined();
211
+ });
212
+
213
+ it('should get current session', async () => {
214
+ expect(service.getCurrentSession()).toBeNull();
215
+
216
+ await service.startSession();
217
+ const current = service.getCurrentSession();
218
+
219
+ expect(current).not.toBeNull();
220
+ expect(current!.status).toBe('active');
221
+ });
222
+
223
+ it('should create checkpoint', async () => {
224
+ await service.startSession();
225
+ await service.checkpoint('Test checkpoint');
226
+
227
+ const session = service.getCurrentSession();
228
+ expect(session!.lastCheckpoint).toBe('Test checkpoint');
229
+ });
230
+
231
+ it('should throw error when checkpoint without session', async () => {
232
+ await expect(service.checkpoint('Test')).rejects.toThrow('No active session');
233
+ });
234
+
235
+ it('should end session', async () => {
236
+ await service.startSession();
237
+ const ended = await service.endSession('Session summary');
238
+
239
+ expect(ended).not.toBeNull();
240
+ expect(ended!.status).toBe('completed');
241
+ expect(ended!.summary).toBe('Session summary');
242
+ expect(ended!.endedAt).toBeDefined();
243
+ });
244
+
245
+ it('should add session id to entries', async () => {
246
+ const session = await service.startSession();
247
+
248
+ const entry = await service.storeEntry({
249
+ key: 'session-entry',
250
+ content: 'Content',
251
+ namespace: 'test',
252
+ });
253
+
254
+ expect(entry.sessionId).toBe(session.id);
255
+ });
256
+
257
+ it('should get recent sessions', async () => {
258
+ await service.startSession();
259
+ await service.endSession('Session 1');
260
+
261
+ await service.startSession();
262
+ await service.endSession('Session 2');
263
+
264
+ const sessions = await service.getRecentSessions();
265
+ expect(sessions.length).toBeGreaterThanOrEqual(2);
266
+ });
267
+ });
268
+
269
+ describe('Namespace Operations', () => {
270
+ beforeEach(async () => {
271
+ await service.storeEntry({ key: 'ns1-1', content: 'C1', namespace: 'ns1' });
272
+ await service.storeEntry({ key: 'ns1-2', content: 'C2', namespace: 'ns1' });
273
+ await service.storeEntry({ key: 'ns2-1', content: 'C3', namespace: 'ns2' });
274
+ });
275
+
276
+ it('should list namespaces', async () => {
277
+ const namespaces = await service.listNamespaces();
278
+ expect(namespaces).toContain('ns1');
279
+ expect(namespaces).toContain('ns2');
280
+ });
281
+
282
+ it('should count by namespace', async () => {
283
+ const count = await service.count('ns1');
284
+ expect(count).toBe(2);
285
+ });
286
+
287
+ it('should clear namespace', async () => {
288
+ const cleared = await service.clearNamespace('ns1');
289
+ expect(cleared).toBe(2);
290
+ expect(await service.count('ns1')).toBe(0);
291
+ expect(await service.count('ns2')).toBe(1);
292
+ });
293
+ });
294
+
295
+ describe('Bulk Operations', () => {
296
+ it('should bulk insert entries', async () => {
297
+ const entries = [
298
+ createDefaultEntry({ key: 'b1', content: 'C1', namespace: 'bulk' }),
299
+ createDefaultEntry({ key: 'b2', content: 'C2', namespace: 'bulk' }),
300
+ ];
301
+
302
+ await service.bulkInsert(entries);
303
+ expect(await service.count('bulk')).toBe(2);
304
+ });
305
+
306
+ it('should bulk delete entries', async () => {
307
+ const e1 = await service.storeEntry({ key: 'bd1', content: 'C1', namespace: 'bd' });
308
+ const e2 = await service.storeEntry({ key: 'bd2', content: 'C2', namespace: 'bd' });
309
+
310
+ const deleted = await service.bulkDelete([e1.id, e2.id]);
311
+ expect(deleted).toBe(2);
312
+ expect(await service.count('bd')).toBe(0);
313
+ });
314
+ });
315
+
316
+ describe('Statistics and Health', () => {
317
+ it('should get stats', async () => {
318
+ await service.storeEntry({ key: 's1', content: 'C1', namespace: 'ns1' });
319
+ await service.storeEntry({ key: 's2', content: 'C2', namespace: 'ns2' });
320
+
321
+ const stats = await service.getStats();
322
+
323
+ expect(stats.totalEntries).toBe(2);
324
+ expect(stats.entriesByNamespace).toBeDefined();
325
+ });
326
+
327
+ it('should health check', async () => {
328
+ const health = await service.healthCheck();
329
+
330
+ expect(health.status).toBe('healthy');
331
+ expect(health.components).toBeDefined();
332
+ });
333
+ });
334
+
335
+ describe('Events', () => {
336
+ it('should emit entry:stored event', async () => {
337
+ const listener = vi.fn();
338
+ service.on('entry:stored', listener);
339
+
340
+ await service.storeEntry({ key: 'event-test', content: 'Content', namespace: 'test' });
341
+
342
+ expect(listener).toHaveBeenCalled();
343
+ });
344
+
345
+ it('should emit session:started event', async () => {
346
+ const listener = vi.fn();
347
+ service.on('session:started', listener);
348
+
349
+ await service.startSession();
350
+
351
+ expect(listener).toHaveBeenCalled();
352
+ });
353
+
354
+ it('should emit session:ended event', async () => {
355
+ const listener = vi.fn();
356
+ service.on('session:ended', listener);
357
+
358
+ await service.startSession();
359
+ await service.endSession();
360
+
361
+ expect(listener).toHaveBeenCalled();
362
+ });
363
+ });
364
+ });
365
+
366
+ describe('createProjectMemory factory', () => {
367
+ let testDir: string;
368
+
369
+ beforeEach(() => {
370
+ testDir = path.join(tmpdir(), `factory-test-${Date.now()}`);
371
+ });
372
+
373
+ afterEach(() => {
374
+ fs.rmSync(testDir, { recursive: true, force: true });
375
+ });
376
+
377
+ it('should create memory service with defaults', async () => {
378
+ const service = createProjectMemory(testDir);
379
+ await service.initialize();
380
+
381
+ expect(fs.existsSync(testDir)).toBe(true);
382
+
383
+ await service.shutdown();
384
+ });
385
+
386
+ it('should accept options', async () => {
387
+ const service = createProjectMemory(testDir, {
388
+ cacheEnabled: false,
389
+ verbose: true,
390
+ });
391
+
392
+ await service.initialize();
393
+ await service.shutdown();
394
+ });
395
+ });
396
+
397
+ describe('DEFAULT_NAMESPACES', () => {
398
+ it('should have all required namespaces', () => {
399
+ expect(DEFAULT_NAMESPACES.CONTEXT).toBe('context');
400
+ expect(DEFAULT_NAMESPACES.ACTIVE).toBe('active-context');
401
+ expect(DEFAULT_NAMESPACES.SESSION).toBe('session-state');
402
+ expect(DEFAULT_NAMESPACES.PROGRESS).toBe('progress');
403
+ expect(DEFAULT_NAMESPACES.PATTERNS).toBe('patterns');
404
+ expect(DEFAULT_NAMESPACES.DECISIONS).toBe('decisions');
405
+ expect(DEFAULT_NAMESPACES.ERRORS).toBe('errors');
406
+ });
407
+ });