@cartisien/engram-mcp 0.2.0 → 0.3.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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/dist/analysis/emotions.d.ts +16 -0
  3. package/dist/analysis/emotions.js +130 -0
  4. package/dist/analysis/emotions.js.map +1 -0
  5. package/dist/analysis/entities.d.ts +15 -0
  6. package/dist/analysis/entities.js +182 -0
  7. package/dist/analysis/entities.js.map +1 -0
  8. package/dist/analysis/themes.d.ts +11 -0
  9. package/dist/analysis/themes.js +151 -0
  10. package/dist/analysis/themes.js.map +1 -0
  11. package/dist/client.d.ts +3 -0
  12. package/dist/client.js +31 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +13 -0
  15. package/dist/config.js +20 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/response.d.ts +17 -0
  18. package/dist/response.js +17 -0
  19. package/dist/response.js.map +1 -0
  20. package/dist/server.d.ts +7 -0
  21. package/dist/server.js +104 -0
  22. package/dist/server.js.map +1 -0
  23. package/dist/tools/consolidate.d.ts +41 -0
  24. package/dist/tools/consolidate.js +53 -0
  25. package/dist/tools/consolidate.js.map +1 -0
  26. package/dist/tools/context.d.ts +44 -0
  27. package/dist/tools/context.js +186 -0
  28. package/dist/tools/context.js.map +1 -0
  29. package/dist/tools/detect_changes.d.ts +35 -0
  30. package/dist/tools/detect_changes.js +178 -0
  31. package/dist/tools/detect_changes.js.map +1 -0
  32. package/dist/tools/forget.d.ts +38 -0
  33. package/dist/tools/forget.js +38 -0
  34. package/dist/tools/forget.js.map +1 -0
  35. package/dist/tools/graph.d.ts +40 -0
  36. package/dist/tools/graph.js +74 -0
  37. package/dist/tools/graph.js.map +1 -0
  38. package/dist/tools/impact.d.ts +39 -0
  39. package/dist/tools/impact.js +143 -0
  40. package/dist/tools/impact.js.map +1 -0
  41. package/dist/tools/process_detect.d.ts +49 -0
  42. package/dist/tools/process_detect.js +218 -0
  43. package/dist/tools/process_detect.js.map +1 -0
  44. package/dist/tools/recall.d.ts +81 -0
  45. package/dist/tools/recall.js +134 -0
  46. package/dist/tools/recall.js.map +1 -0
  47. package/dist/tools/remember.d.ts +47 -0
  48. package/dist/tools/remember.js +79 -0
  49. package/dist/tools/remember.js.map +1 -0
  50. package/dist/tools/search.d.ts +72 -0
  51. package/dist/tools/search.js +96 -0
  52. package/dist/tools/search.js.map +1 -0
  53. package/dist/tools/stats.d.ts +23 -0
  54. package/dist/tools/stats.js +47 -0
  55. package/dist/tools/stats.js.map +1 -0
  56. package/package.json +29 -14
  57. package/README.md +0 -114
  58. package/dist/engram.d.ts +0 -99
  59. package/dist/engram.js +0 -283
  60. package/dist/src/index.d.ts +0 -2
  61. package/dist/src/index.js +0 -322
  62. package/engram.ts +0 -385
  63. package/src/index.ts +0 -336
  64. package/tests/mcp.test.ts +0 -274
  65. package/tsconfig.json +0 -17
  66. package/tsconfig.test.json +0 -5
package/src/index.ts DELETED
@@ -1,336 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import {
5
- CallToolRequestSchema,
6
- ListToolsRequestSchema,
7
- } from '@modelcontextprotocol/sdk/types.js';
8
- import { Engram } from '@cartisien/engram';
9
- import * as os from 'os';
10
- import * as path from 'path';
11
- import * as fs from 'fs';
12
-
13
- const DB_PATH = process.env.ENGRAM_DB || path.join(os.homedir(), '.engram', 'memory.db');
14
- const EMBEDDING_URL = process.env.ENGRAM_EMBEDDING_URL || 'http://192.168.68.73:11434';
15
- const GRAPH_MODEL = process.env.ENGRAM_GRAPH_MODEL || 'qwen2.5:32b';
16
- const CONSOLIDATE_MODEL = process.env.ENGRAM_CONSOLIDATE_MODEL || 'qwen2.5:32b';
17
-
18
- const dbDir = path.dirname(DB_PATH);
19
- if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
20
-
21
- const memory = new Engram({
22
- dbPath: DB_PATH,
23
- embeddingUrl: EMBEDDING_URL,
24
- semanticSearch: true,
25
- graphMemory: process.env.ENGRAM_GRAPH === '1',
26
- graphModel: GRAPH_MODEL,
27
- consolidateModel: CONSOLIDATE_MODEL,
28
- });
29
-
30
- const server = new Server(
31
- { name: 'engram', version: '0.2.0' },
32
- { capabilities: { tools: {} } }
33
- );
34
-
35
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
- tools: [
37
- // ── Session memory ──────────────────────────────────────────────────
38
- {
39
- name: 'remember',
40
- description: 'Store a memory entry for a session. Embeddings and graph extraction happen automatically.',
41
- inputSchema: {
42
- type: 'object',
43
- properties: {
44
- sessionId: { type: 'string', description: 'Session identifier' },
45
- content: { type: 'string', description: 'Memory content to store' },
46
- role: { type: 'string', enum: ['user', 'assistant', 'system'], default: 'user' },
47
- metadata: { type: 'object', description: 'Optional key-value metadata' },
48
- },
49
- required: ['sessionId', 'content'],
50
- },
51
- },
52
- {
53
- name: 'recall',
54
- description: 'Retrieve relevant memories via semantic search (falls back to keyword). Searches working + long_term tiers. Pass userId to blend in cross-session user facts.',
55
- inputSchema: {
56
- type: 'object',
57
- properties: {
58
- sessionId: { type: 'string', description: 'Session identifier' },
59
- query: { type: 'string', description: 'Search query' },
60
- limit: { type: 'number', description: 'Max results (default 10)', default: 10 },
61
- userId: { type: 'string', description: 'Optional: also blend in this user\'s cross-session memories' },
62
- tiers: { type: 'string', description: 'Comma-separated tiers to search: working,long_term,archived (default: working,long_term)' },
63
- },
64
- required: ['sessionId', 'query'],
65
- },
66
- },
67
- {
68
- name: 'history',
69
- description: 'Get recent conversation history for a session in chronological order.',
70
- inputSchema: {
71
- type: 'object',
72
- properties: {
73
- sessionId: { type: 'string', description: 'Session identifier' },
74
- limit: { type: 'number', description: 'Max entries (default 20)', default: 20 },
75
- },
76
- required: ['sessionId'],
77
- },
78
- },
79
- {
80
- name: 'forget',
81
- description: 'Delete session memories. Delete all, one by ID, or entries before a date.',
82
- inputSchema: {
83
- type: 'object',
84
- properties: {
85
- sessionId: { type: 'string', description: 'Session identifier' },
86
- id: { type: 'string', description: 'Specific memory ID to delete (optional)' },
87
- before: { type: 'string', description: 'ISO date — delete entries before this date (optional)' },
88
- },
89
- required: ['sessionId'],
90
- },
91
- },
92
- {
93
- name: 'stats',
94
- description: 'Memory statistics for a session — total, by role, by tier (working/long_term/archived), graph counts.',
95
- inputSchema: {
96
- type: 'object',
97
- properties: {
98
- sessionId: { type: 'string', description: 'Session identifier' },
99
- },
100
- required: ['sessionId'],
101
- },
102
- },
103
- {
104
- name: 'consolidate',
105
- description: 'Consolidate old working memories into dense long-term summaries via LLM. Archives originals.',
106
- inputSchema: {
107
- type: 'object',
108
- properties: {
109
- sessionId: { type: 'string', description: 'Session identifier' },
110
- batch: { type: 'number', description: 'Number of memories to consolidate (default 50)' },
111
- keep: { type: 'number', description: 'Most recent N to leave untouched (default 20)' },
112
- dryRun: { type: 'boolean', description: 'Preview summaries without writing (default false)' },
113
- },
114
- required: ['sessionId'],
115
- },
116
- },
117
- {
118
- name: 'graph',
119
- description: 'Query the knowledge graph for an entity — returns relationships and source memories. Requires ENGRAM_GRAPH=1.',
120
- inputSchema: {
121
- type: 'object',
122
- properties: {
123
- sessionId: { type: 'string', description: 'Session identifier' },
124
- entity: { type: 'string', description: 'Entity name to look up (case-insensitive)' },
125
- },
126
- required: ['sessionId', 'entity'],
127
- },
128
- },
129
- // ── User-scoped (cross-session) memory ──────────────────────────────
130
- {
131
- name: 'remember_user',
132
- description: 'Store a user-scoped memory that persists across all sessions. Use for preferences, identity, long-term facts.',
133
- inputSchema: {
134
- type: 'object',
135
- properties: {
136
- userId: { type: 'string', description: 'User identifier' },
137
- content: { type: 'string', description: 'Memory content to store' },
138
- role: { type: 'string', enum: ['user', 'assistant', 'system'], default: 'user' },
139
- metadata: { type: 'object', description: 'Optional key-value metadata' },
140
- },
141
- required: ['userId', 'content'],
142
- },
143
- },
144
- {
145
- name: 'recall_user',
146
- description: 'Recall user-scoped memories — works from any session context.',
147
- inputSchema: {
148
- type: 'object',
149
- properties: {
150
- userId: { type: 'string', description: 'User identifier' },
151
- query: { type: 'string', description: 'Search query (optional — returns all if omitted)' },
152
- limit: { type: 'number', description: 'Max results (default 10)', default: 10 },
153
- },
154
- required: ['userId'],
155
- },
156
- },
157
- {
158
- name: 'forget_user',
159
- description: 'Delete user-scoped memories.',
160
- inputSchema: {
161
- type: 'object',
162
- properties: {
163
- userId: { type: 'string', description: 'User identifier' },
164
- id: { type: 'string', description: 'Specific memory ID (optional)' },
165
- before: { type: 'string', description: 'ISO date — delete entries before this date (optional)' },
166
- },
167
- required: ['userId'],
168
- },
169
- },
170
- {
171
- name: 'consolidate_user',
172
- description: 'Consolidate user-scoped working memories into long-term summaries.',
173
- inputSchema: {
174
- type: 'object',
175
- properties: {
176
- userId: { type: 'string', description: 'User identifier' },
177
- batch: { type: 'number', description: 'Number of memories to consolidate (default 50)' },
178
- keep: { type: 'number', description: 'Most recent N to leave untouched (default 20)' },
179
- dryRun: { type: 'boolean', description: 'Preview without writing (default false)' },
180
- },
181
- required: ['userId'],
182
- },
183
- },
184
- {
185
- name: 'user_stats',
186
- description: 'Memory statistics for a user — total, by role, by tier.',
187
- inputSchema: {
188
- type: 'object',
189
- properties: {
190
- userId: { type: 'string', description: 'User identifier' },
191
- },
192
- required: ['userId'],
193
- },
194
- },
195
- ],
196
- }));
197
-
198
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
199
- const { name, arguments: args = {} } = request.params;
200
-
201
- try {
202
- switch (name) {
203
-
204
- // ── Session ──────────────────────────────────────────────────────
205
-
206
- case 'remember': {
207
- const entry = await memory.remember(
208
- args.sessionId as string,
209
- args.content as string,
210
- (args.role as 'user' | 'assistant' | 'system') || 'user',
211
- args.metadata as Record<string, unknown> | undefined
212
- );
213
- return { content: [{ type: 'text', text: JSON.stringify(entry, null, 2) }] };
214
- }
215
-
216
- case 'recall': {
217
- const tiers = args.tiers
218
- ? (args.tiers as string).split(',').map(t => t.trim()) as any
219
- : undefined;
220
- const entries = await memory.recall(
221
- args.sessionId as string,
222
- args.query as string,
223
- (args.limit as number) || 10,
224
- {
225
- tiers,
226
- userId: args.userId as string | undefined,
227
- }
228
- );
229
- return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
230
- }
231
-
232
- case 'history': {
233
- const entries = await memory.history(
234
- args.sessionId as string,
235
- (args.limit as number) || 20
236
- );
237
- return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
238
- }
239
-
240
- case 'forget': {
241
- const opts: Parameters<typeof memory.forget>[1] = {};
242
- if (args.id) opts.id = args.id as string;
243
- if (args.before) opts.before = new Date(args.before as string);
244
- const deleted = await memory.forget(args.sessionId as string, opts);
245
- return { content: [{ type: 'text', text: `Deleted ${deleted} memor${deleted === 1 ? 'y' : 'ies'}.` }] };
246
- }
247
-
248
- case 'stats': {
249
- const stats = await memory.stats(args.sessionId as string);
250
- return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
251
- }
252
-
253
- case 'consolidate': {
254
- const result = await memory.consolidate(args.sessionId as string, {
255
- batch: args.batch as number | undefined,
256
- keep: args.keep as number | undefined,
257
- dryRun: args.dryRun as boolean | undefined,
258
- });
259
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
260
- }
261
-
262
- case 'graph': {
263
- const result = await memory.graph(
264
- args.sessionId as string,
265
- args.entity as string
266
- );
267
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
268
- }
269
-
270
- // ── User-scoped ───────────────────────────────────────────────────
271
-
272
- case 'remember_user': {
273
- const entry = await memory.rememberUser(
274
- args.userId as string,
275
- args.content as string,
276
- (args.role as 'user' | 'assistant' | 'system') || 'user',
277
- args.metadata as Record<string, unknown> | undefined
278
- );
279
- return { content: [{ type: 'text', text: JSON.stringify(entry, null, 2) }] };
280
- }
281
-
282
- case 'recall_user': {
283
- const entries = await memory.recallUser(
284
- args.userId as string,
285
- args.query as string | undefined,
286
- (args.limit as number) || 10
287
- );
288
- return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };
289
- }
290
-
291
- case 'forget_user': {
292
- const opts: Parameters<typeof memory.forgetUser>[1] = {};
293
- if (args.id) opts.id = args.id as string;
294
- if (args.before) opts.before = new Date(args.before as string);
295
- const deleted = await memory.forgetUser(args.userId as string, opts);
296
- return { content: [{ type: 'text', text: `Deleted ${deleted} user memor${deleted === 1 ? 'y' : 'ies'}.` }] };
297
- }
298
-
299
- case 'consolidate_user': {
300
- const result = await memory.consolidateUser(args.userId as string, {
301
- batch: args.batch as number | undefined,
302
- keep: args.keep as number | undefined,
303
- dryRun: args.dryRun as boolean | undefined,
304
- });
305
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
306
- }
307
-
308
- case 'user_stats': {
309
- const stats = await memory.userStats(args.userId as string);
310
- return { content: [{ type: 'text', text: JSON.stringify(stats, null, 2) }] };
311
- }
312
-
313
- default:
314
- return {
315
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
316
- isError: true,
317
- };
318
- }
319
- } catch (err: any) {
320
- return {
321
- content: [{ type: 'text', text: `Error: ${err.message}` }],
322
- isError: true,
323
- };
324
- }
325
- });
326
-
327
- async function main() {
328
- const transport = new StdioServerTransport();
329
- await server.connect(transport);
330
- process.stderr.write('Engram MCP server v0.2.0 running\n');
331
- }
332
-
333
- main().catch((err) => {
334
- process.stderr.write(`Fatal: ${err.message}\n`);
335
- process.exit(1);
336
- });
package/tests/mcp.test.ts DELETED
@@ -1,274 +0,0 @@
1
- /**
2
- * MCP server tool coverage tests.
3
- * We test the tool definitions (schema) and the handler dispatch logic
4
- * by exercising the Engram SDK directly — the same code paths the MCP
5
- * server uses. This avoids needing a running MCP transport in CI.
6
- */
7
-
8
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
- import { Engram } from '@cartisien/engram';
10
-
11
- // Mirror the handler logic so we can test every case without transport
12
- async function dispatch(memory: Engram, name: string, args: Record<string, unknown>) {
13
- switch (name) {
14
- case 'remember':
15
- return memory.remember(
16
- args.sessionId as string,
17
- args.content as string,
18
- (args.role as any) || 'user',
19
- args.metadata as any
20
- );
21
- case 'recall':
22
- return memory.recall(
23
- args.sessionId as string,
24
- args.query as string,
25
- (args.limit as number) || 10,
26
- { tiers: args.tiers ? (args.tiers as string).split(',') as any : undefined,
27
- userId: args.userId as string | undefined }
28
- );
29
- case 'history':
30
- return memory.history(args.sessionId as string, (args.limit as number) || 20);
31
- case 'forget': {
32
- const opts: any = {};
33
- if (args.id) opts.id = args.id;
34
- if (args.before) opts.before = new Date(args.before as string);
35
- return memory.forget(args.sessionId as string, opts);
36
- }
37
- case 'stats':
38
- return memory.stats(args.sessionId as string);
39
- case 'consolidate':
40
- return memory.consolidate(args.sessionId as string, {
41
- batch: args.batch as any, keep: args.keep as any, dryRun: args.dryRun as any
42
- });
43
- case 'graph':
44
- return memory.graph(args.sessionId as string, args.entity as string);
45
- case 'remember_user':
46
- return memory.rememberUser(args.userId as string, args.content as string, (args.role as any) || 'user', args.metadata as any);
47
- case 'recall_user':
48
- return memory.recallUser(args.userId as string, args.query as string | undefined, (args.limit as number) || 10);
49
- case 'forget_user': {
50
- const opts: any = {};
51
- if (args.id) opts.id = args.id;
52
- if (args.before) opts.before = new Date(args.before as string);
53
- return memory.forgetUser(args.userId as string, opts);
54
- }
55
- case 'consolidate_user':
56
- return memory.consolidateUser(args.userId as string, {
57
- batch: args.batch as any, keep: args.keep as any, dryRun: args.dryRun as any
58
- });
59
- case 'user_stats':
60
- return memory.userStats(args.userId as string);
61
- default:
62
- throw new Error(`Unknown tool: ${name}`);
63
- }
64
- }
65
-
66
- describe('MCP tool handlers — all 12 tools', () => {
67
- let memory: Engram;
68
-
69
- beforeEach(() => {
70
- memory = new Engram({ dbPath: ':memory:', semanticSearch: false });
71
- });
72
- afterEach(async () => { await memory.close(); });
73
-
74
- // ── remember ─────────────────────────────────────────────────────────────
75
-
76
- it('remember: stores entry and returns id', async () => {
77
- const entry: any = await dispatch(memory, 'remember', {
78
- sessionId: 's1', content: 'Test fact', role: 'user'
79
- });
80
- expect(entry.id).toBeTruthy();
81
- expect(entry.content).toBe('Test fact');
82
- expect(entry.tier).toBe('working');
83
- });
84
-
85
- it('remember: stores with metadata', async () => {
86
- const entry: any = await dispatch(memory, 'remember', {
87
- sessionId: 's1', content: 'Fact with meta', metadata: { source: 'test' }
88
- });
89
- expect(entry.id).toBeTruthy();
90
- });
91
-
92
- // ── recall ────────────────────────────────────────────────────────────────
93
-
94
- it('recall: returns stored memories', async () => {
95
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'GovScout uses React' });
96
- const results: any = await dispatch(memory, 'recall', { sessionId: 's1', query: 'GovScout' });
97
- expect(results.length).toBeGreaterThanOrEqual(1);
98
- expect(results[0].content).toContain('GovScout');
99
- });
100
-
101
- it('recall: returns empty for unknown session', async () => {
102
- const results: any = await dispatch(memory, 'recall', { sessionId: 'nobody', query: 'anything' });
103
- expect(results).toEqual([]);
104
- });
105
-
106
- it('recall: blends user memories when userId provided', async () => {
107
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'Session fact' });
108
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'User fact' });
109
- const results: any = await dispatch(memory, 'recall', { sessionId: 's1', query: 'fact', userId: 'u1' });
110
- const contents = results.map((r: any) => r.content);
111
- expect(contents).toContain('Session fact');
112
- expect(contents).toContain('User fact');
113
- });
114
-
115
- it('recall: tier filter works', async () => {
116
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'Working tier fact' });
117
- const results: any = await dispatch(memory, 'recall', {
118
- sessionId: 's1', query: 'fact', tiers: 'working'
119
- });
120
- expect(results.every((r: any) => r.tier === 'working')).toBe(true);
121
- });
122
-
123
- // ── history ───────────────────────────────────────────────────────────────
124
-
125
- it('history: returns chronological entries', async () => {
126
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'First' });
127
- await new Promise(r => setTimeout(r, 5));
128
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'Second' });
129
- const entries: any = await dispatch(memory, 'history', { sessionId: 's1' });
130
- expect(entries[0].content).toBe('First');
131
- expect(entries[1].content).toBe('Second');
132
- });
133
-
134
- // ── forget ────────────────────────────────────────────────────────────────
135
-
136
- it('forget: deletes all session memories', async () => {
137
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'To delete' });
138
- const deleted: any = await dispatch(memory, 'forget', { sessionId: 's1' });
139
- expect(deleted).toBe(1);
140
- const results: any = await dispatch(memory, 'recall', { sessionId: 's1', query: 'delete' });
141
- expect(results).toEqual([]);
142
- });
143
-
144
- it('forget: deletes by id', async () => {
145
- const e: any = await dispatch(memory, 'remember', { sessionId: 's1', content: 'Target' });
146
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'Keep' });
147
- const deleted: any = await dispatch(memory, 'forget', { sessionId: 's1', id: e.id });
148
- expect(deleted).toBe(1);
149
- const left: any = await dispatch(memory, 'history', { sessionId: 's1' });
150
- expect(left.map((r: any) => r.content)).not.toContain('Target');
151
- });
152
-
153
- // ── stats ─────────────────────────────────────────────────────────────────
154
-
155
- it('stats: returns total, byRole, byTier', async () => {
156
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'f1', role: 'user' });
157
- await dispatch(memory, 'remember', { sessionId: 's1', content: 'f2', role: 'assistant' });
158
- const stats: any = await dispatch(memory, 'stats', { sessionId: 's1' });
159
- expect(stats.total).toBe(2);
160
- expect(stats.byRole.user).toBe(1);
161
- expect(stats.byRole.assistant).toBe(1);
162
- expect(stats.byTier.working).toBe(2);
163
- });
164
-
165
- // ── consolidate ───────────────────────────────────────────────────────────
166
-
167
- it('consolidate: dry run returns summarized count', async () => {
168
- const result: any = await dispatch(memory, 'consolidate', {
169
- sessionId: 's1', dryRun: true
170
- });
171
- expect(typeof result.summarized).toBe('number');
172
- expect(result.created).toBe(0);
173
- });
174
-
175
- it('consolidate: returns zeros for empty session', async () => {
176
- const result: any = await dispatch(memory, 'consolidate', { sessionId: 'empty' });
177
- expect(result.summarized).toBe(0);
178
- expect(result.created).toBe(0);
179
- expect(result.archived).toBe(0);
180
- });
181
-
182
- // ── graph ─────────────────────────────────────────────────────────────────
183
-
184
- it('graph: returns entity + relationships + memories', async () => {
185
- const result: any = await dispatch(memory, 'graph', {
186
- sessionId: 's1', entity: 'govscout'
187
- });
188
- expect(result.entity).toBe('govscout');
189
- expect(Array.isArray(result.relationships)).toBe(true);
190
- expect(Array.isArray(result.relatedMemories)).toBe(true);
191
- });
192
-
193
- // ── remember_user ─────────────────────────────────────────────────────────
194
-
195
- it('remember_user: stores and returns user entry', async () => {
196
- const entry: any = await dispatch(memory, 'remember_user', {
197
- userId: 'u1', content: 'Prefers TypeScript'
198
- });
199
- expect(entry.userId).toBe('u1');
200
- expect(entry.content).toBe('Prefers TypeScript');
201
- expect(entry.tier).toBe('working');
202
- });
203
-
204
- // ── recall_user ───────────────────────────────────────────────────────────
205
-
206
- it('recall_user: retrieves user memories', async () => {
207
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'Loves dark mode' });
208
- const results: any = await dispatch(memory, 'recall_user', { userId: 'u1' });
209
- expect(results.length).toBe(1);
210
- expect(results[0].content).toBe('Loves dark mode');
211
- });
212
-
213
- it('recall_user: isolated between users', async () => {
214
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'U1 fact' });
215
- await dispatch(memory, 'remember_user', { userId: 'u2', content: 'U2 fact' });
216
- const u1: any = await dispatch(memory, 'recall_user', { userId: 'u1' });
217
- expect(u1.map((r: any) => r.content)).not.toContain('U2 fact');
218
- });
219
-
220
- it('recall_user: empty for unknown user', async () => {
221
- const results: any = await dispatch(memory, 'recall_user', { userId: 'nobody' });
222
- expect(results).toEqual([]);
223
- });
224
-
225
- // ── forget_user ───────────────────────────────────────────────────────────
226
-
227
- it('forget_user: deletes all user memories', async () => {
228
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'To delete' });
229
- const deleted: any = await dispatch(memory, 'forget_user', { userId: 'u1' });
230
- expect(deleted).toBe(1);
231
- const left: any = await dispatch(memory, 'recall_user', { userId: 'u1' });
232
- expect(left).toEqual([]);
233
- });
234
-
235
- it('forget_user: deletes by id', async () => {
236
- const e: any = await dispatch(memory, 'remember_user', { userId: 'u1', content: 'Target' });
237
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'Keep' });
238
- await dispatch(memory, 'forget_user', { userId: 'u1', id: e.id });
239
- const left: any = await dispatch(memory, 'recall_user', { userId: 'u1' });
240
- expect(left.map((r: any) => r.content)).not.toContain('Target');
241
- expect(left.map((r: any) => r.content)).toContain('Keep');
242
- });
243
-
244
- // ── consolidate_user ──────────────────────────────────────────────────────
245
-
246
- it('consolidate_user: dry run on empty user', async () => {
247
- const result: any = await dispatch(memory, 'consolidate_user', {
248
- userId: 'nobody', dryRun: true
249
- });
250
- expect(result.summarized).toBe(0);
251
- });
252
-
253
- // ── user_stats ────────────────────────────────────────────────────────────
254
-
255
- it('user_stats: returns counts', async () => {
256
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'f1', role: 'user' });
257
- await dispatch(memory, 'remember_user', { userId: 'u1', content: 'f2', role: 'assistant' });
258
- const stats: any = await dispatch(memory, 'user_stats', { userId: 'u1' });
259
- expect(stats.total).toBe(2);
260
- expect(stats.byTier.working).toBe(2);
261
- expect(stats.byRole.user).toBe(1);
262
- });
263
-
264
- it('user_stats: zeros for unknown user', async () => {
265
- const stats: any = await dispatch(memory, 'user_stats', { userId: 'nobody' });
266
- expect(stats.total).toBe(0);
267
- });
268
-
269
- // ── unknown tool ──────────────────────────────────────────────────────────
270
-
271
- it('unknown tool: throws', async () => {
272
- await expect(dispatch(memory, 'nonexistent', {})).rejects.toThrow('Unknown tool');
273
- });
274
- });
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "CommonJS",
5
- "moduleResolution": "node",
6
- "outDir": "./dist",
7
- "rootDir": ".",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "declaration": true
12
- },
13
- "include": [
14
- "src/**/*",
15
- "engram.ts"
16
- ]
17
- }
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": { "rootDir": "." },
4
- "include": ["src/**/*", "tests/**/*"]
5
- }