@prmichaelsen/remember-mcp 3.15.4 → 3.15.6
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.
- package/AGENT.md +363 -5
- package/CHANGELOG.md +7 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +50 -0
- package/agent/commands/acp.command-create.md +60 -0
- package/agent/commands/acp.design-create.md +62 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +48 -0
- package/agent/commands/acp.package-create.md +1 -0
- package/agent/commands/acp.package-info.md +1 -0
- package/agent/commands/acp.package-install.md +19 -0
- package/agent/commands/acp.package-list.md +1 -0
- package/agent/commands/acp.package-publish.md +1 -0
- package/agent/commands/acp.package-remove.md +1 -0
- package/agent/commands/acp.package-search.md +1 -0
- package/agent/commands/acp.package-update.md +1 -0
- package/agent/commands/acp.package-validate.md +1 -0
- package/agent/commands/acp.pattern-create.md +60 -0
- package/agent/commands/acp.plan.md +25 -0
- package/agent/commands/acp.proceed.md +621 -75
- package/agent/commands/acp.project-create.md +3 -0
- package/agent/commands/acp.project-info.md +3 -0
- package/agent/commands/acp.project-list.md +3 -1
- package/agent/commands/acp.project-set.md +1 -0
- package/agent/commands/acp.project-update.md +14 -3
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +13 -0
- package/agent/commands/acp.resume.md +3 -1
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +13 -0
- package/agent/commands/acp.sync.md +1 -0
- package/agent/commands/acp.task-create.md +105 -3
- package/agent/commands/acp.update.md +1 -0
- package/agent/commands/acp.validate.md +32 -2
- package/agent/commands/acp.version-check-for-updates.md +1 -0
- package/agent/commands/acp.version-check.md +1 -0
- package/agent/commands/acp.version-update.md +1 -0
- package/agent/commands/command.template.md +23 -0
- package/agent/commands/git.commit.md +1 -0
- package/agent/commands/git.init.md +1 -0
- package/agent/design/complete-tool-set.md +157 -233
- package/agent/design/design.template.md +18 -0
- package/agent/design/user-preferences.md +11 -7
- package/agent/milestones/milestone-19-new-search-ghost-tools.md +46 -0
- package/agent/package.template.yaml +50 -0
- package/agent/patterns/pattern.template.md +18 -0
- package/agent/progress.yaml +162 -6
- package/agent/scripts/acp.common.sh +258 -15
- package/agent/scripts/acp.install.sh +91 -4
- package/agent/scripts/acp.package-create.sh +0 -1
- package/agent/scripts/acp.package-info.sh +19 -1
- package/agent/scripts/acp.package-install-optimized.sh +1 -1
- package/agent/scripts/acp.package-install.sh +388 -38
- package/agent/scripts/acp.package-list.sh +52 -4
- package/agent/scripts/acp.package-remove.sh +77 -1
- package/agent/scripts/acp.package-search.sh +2 -2
- package/agent/scripts/acp.package-update.sh +91 -12
- package/agent/scripts/acp.package-validate.sh +136 -1
- package/agent/scripts/acp.project-info.sh +34 -11
- package/agent/scripts/acp.project-list.sh +4 -0
- package/agent/scripts/acp.project-update.sh +66 -19
- package/agent/scripts/acp.projects-restore.sh +170 -0
- package/agent/scripts/acp.projects-sync.sh +155 -0
- package/agent/scripts/acp.sessions.sh +725 -0
- package/agent/scripts/acp.version-update.sh +21 -3
- package/agent/scripts/acp.yaml-parser.sh +20 -6
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-203-create-search-by-tool.md +143 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-204-add-new-filters-existing-tools.md +77 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-205-add-feel-fields-create-update.md +137 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-206-add-byproperty-bysignificance-modes.md +135 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-207-add-emotional-composites-search-results.md +88 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-208-add-bybroad-byrandom-modes.md +115 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-209-create-ghost-memory-tools.md +192 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-210-create-get-core-tool.md +203 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-211-create-search-space-by-tool.md +182 -0
- package/agent/tasks/task-1-{title}.template.md +19 -0
- package/agent/tasks/unassigned/bug-report-remember-core-e2e-findings.md +99 -0
- package/dist/e2e-helpers.d.ts +26 -0
- package/dist/ghost-persona.e2e.d.ts +8 -0
- package/dist/memory-crud.e2e.d.ts +8 -0
- package/dist/preferences.e2e.d.ts +8 -0
- package/dist/relationships.e2e.d.ts +8 -0
- package/dist/search-modes.e2e.d.ts +8 -0
- package/dist/server-factory.js +1977 -100
- package/dist/server.js +1174 -51
- package/dist/shared-spaces.e2e.d.ts +8 -0
- package/dist/tools/create-ghost-memory.d.ts +70 -0
- package/dist/tools/create-memory.d.ts +175 -0
- package/dist/tools/get-core.d.ts +28 -0
- package/dist/tools/get-core.spec.d.ts +2 -0
- package/dist/tools/ghost-tools.spec.d.ts +2 -0
- package/dist/tools/query-ghost-memory.d.ts +34 -0
- package/dist/tools/query-memory.d.ts +4 -0
- package/dist/tools/search-by.d.ts +147 -0
- package/dist/tools/search-by.spec.d.ts +2 -0
- package/dist/tools/search-ghost-memory-by.d.ts +54 -0
- package/dist/tools/search-ghost-memory.d.ts +53 -0
- package/dist/tools/search-memory.d.ts +19 -0
- package/dist/tools/search-space-by.d.ts +78 -0
- package/dist/tools/search-space-by.spec.d.ts +2 -0
- package/dist/tools/search-space.d.ts +2 -0
- package/dist/tools/update-ghost-memory.d.ts +51 -0
- package/dist/tools/update-memory.d.ts +175 -0
- package/jest.e2e.config.js +11 -0
- package/package.json +2 -2
- package/src/e2e-helpers.ts +86 -0
- package/src/ghost-persona.e2e.ts +215 -0
- package/src/memory-crud.e2e.ts +203 -0
- package/src/preferences.e2e.ts +88 -0
- package/src/relationships.e2e.ts +156 -0
- package/src/search-modes.e2e.ts +184 -0
- package/src/server-factory.ts +56 -0
- package/src/shared-spaces.e2e.ts +204 -0
- package/src/tools/create-ghost-memory.ts +103 -0
- package/src/tools/create-memory.ts +45 -1
- package/src/tools/get-core.spec.ts +223 -0
- package/src/tools/get-core.ts +109 -0
- package/src/tools/ghost-tools.spec.ts +361 -0
- package/src/tools/query-ghost-memory.ts +63 -0
- package/src/tools/query-memory.ts +4 -0
- package/src/tools/search-by.spec.ts +325 -0
- package/src/tools/search-by.ts +298 -0
- package/src/tools/search-ghost-memory-by.ts +80 -0
- package/src/tools/search-ghost-memory.ts +73 -0
- package/src/tools/search-memory.ts +23 -0
- package/src/tools/search-space-by.spec.ts +289 -0
- package/src/tools/search-space-by.ts +173 -0
- package/src/tools/search-space.ts +20 -1
- package/src/tools/update-ghost-memory.ts +86 -0
- package/src/tools/update-memory.ts +45 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E: Core Memory CRUD — remember_create/search/update/delete/find_similar/query
|
|
3
|
+
*
|
|
4
|
+
* Run with:
|
|
5
|
+
* DOTENV_CONFIG_PATH=.env.e1.local npm run test:e2e -- --testPathPattern=memory-crud
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
e2eInit, e2eUserId, e2eAuthContext, e2eEnsureCollection,
|
|
10
|
+
e2eCleanup, parseResult, waitForIndex,
|
|
11
|
+
} from './e2e-helpers.js';
|
|
12
|
+
import { handleCreateMemory } from './tools/create-memory.js';
|
|
13
|
+
import { handleSearchMemory } from './tools/search-memory.js';
|
|
14
|
+
import { handleUpdateMemory } from './tools/update-memory.js';
|
|
15
|
+
import { handleDeleteMemory } from './tools/delete-memory.js';
|
|
16
|
+
import { handleConfirm } from './tools/confirm.js';
|
|
17
|
+
import { handleFindSimilar } from './tools/find-similar.js';
|
|
18
|
+
import { handleQueryMemory } from './tools/query-memory.js';
|
|
19
|
+
|
|
20
|
+
const userId = e2eUserId('crud');
|
|
21
|
+
const auth = e2eAuthContext();
|
|
22
|
+
|
|
23
|
+
describe('E2E: Memory CRUD', () => {
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
await e2eInit();
|
|
26
|
+
await e2eEnsureCollection(userId);
|
|
27
|
+
}, 30_000);
|
|
28
|
+
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
await e2eCleanup(userId);
|
|
31
|
+
}, 30_000);
|
|
32
|
+
|
|
33
|
+
// ---- State shared across ordered tests ----
|
|
34
|
+
let memoryId: string;
|
|
35
|
+
let secondMemoryId: string;
|
|
36
|
+
let thirdMemoryId: string;
|
|
37
|
+
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
// CREATE
|
|
40
|
+
// -------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
it('creates a memory', async () => {
|
|
43
|
+
const res = parseResult(await handleCreateMemory(
|
|
44
|
+
{ content: 'I went camping at Yosemite last weekend. The Half Dome trail was spectacular.', weight: 0.8, trust: 0.5, tags: ['camping', 'yosemite'] },
|
|
45
|
+
userId, auth,
|
|
46
|
+
));
|
|
47
|
+
|
|
48
|
+
expect(res.memory_id).toBeTruthy();
|
|
49
|
+
memoryId = res.memory_id;
|
|
50
|
+
console.log(` created memory ${memoryId}`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('creates a second related memory', async () => {
|
|
54
|
+
const res = parseResult(await handleCreateMemory(
|
|
55
|
+
{ content: 'My favorite camping gear: MSR tent, Jetboil stove, and Osprey backpack.', weight: 0.6, tags: ['camping', 'gear'] },
|
|
56
|
+
userId, auth,
|
|
57
|
+
));
|
|
58
|
+
secondMemoryId = res.memory_id;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('creates a third unrelated memory', async () => {
|
|
62
|
+
const res = parseResult(await handleCreateMemory(
|
|
63
|
+
{ content: 'Grandma\'s chocolate chip cookie recipe: 2 cups flour, 1 cup butter, chocolate chips.', weight: 0.7, tags: ['recipe', 'cookies'] },
|
|
64
|
+
userId, auth,
|
|
65
|
+
));
|
|
66
|
+
thirdMemoryId = res.memory_id;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// -------------------------------------------------------------------------
|
|
70
|
+
// SEARCH (hybrid)
|
|
71
|
+
// -------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
it('searches and finds the camping memory via hybrid search', async () => {
|
|
74
|
+
await waitForIndex();
|
|
75
|
+
|
|
76
|
+
const res = parseResult(await handleSearchMemory(
|
|
77
|
+
{ query: 'camping yosemite half dome', limit: 10 },
|
|
78
|
+
userId, auth,
|
|
79
|
+
));
|
|
80
|
+
|
|
81
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
82
|
+
const ids = res.memories.map((m: any) => m.id);
|
|
83
|
+
expect(ids).toContain(memoryId);
|
|
84
|
+
console.log(` search returned ${res.memories.length} memories`);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not return unrelated memories first', async () => {
|
|
88
|
+
const res = parseResult(await handleSearchMemory(
|
|
89
|
+
{ query: 'camping trip outdoors', limit: 5 },
|
|
90
|
+
userId, auth,
|
|
91
|
+
));
|
|
92
|
+
|
|
93
|
+
// Cookie recipe should not be the top result
|
|
94
|
+
if (res.memories.length > 0) {
|
|
95
|
+
expect(res.memories[0].id).not.toBe(thirdMemoryId);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// -------------------------------------------------------------------------
|
|
100
|
+
// UPDATE
|
|
101
|
+
// -------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
it('updates the memory content', async () => {
|
|
104
|
+
const res = parseResult(await handleUpdateMemory(
|
|
105
|
+
{ memory_id: memoryId, content: 'I went camping at Yosemite last weekend. The Half Dome trail was spectacular. We saw a bear!', tags: ['camping', 'yosemite', 'wildlife'] },
|
|
106
|
+
userId, auth,
|
|
107
|
+
));
|
|
108
|
+
|
|
109
|
+
expect(res.memory_id).toBe(memoryId);
|
|
110
|
+
expect(res.version).toBeGreaterThan(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('search reflects updated content', async () => {
|
|
114
|
+
await waitForIndex();
|
|
115
|
+
|
|
116
|
+
const res = parseResult(await handleSearchMemory(
|
|
117
|
+
{ query: 'bear wildlife camping', limit: 5 },
|
|
118
|
+
userId, auth,
|
|
119
|
+
));
|
|
120
|
+
|
|
121
|
+
const found = res.memories.find((m: any) => m.id === memoryId);
|
|
122
|
+
expect(found).toBeTruthy();
|
|
123
|
+
expect(found.content).toContain('bear');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
// FIND SIMILAR
|
|
128
|
+
// -------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
it('find_similar returns related camping memory', async () => {
|
|
131
|
+
const res = parseResult(await handleFindSimilar(
|
|
132
|
+
{ memory_id: memoryId, limit: 5, min_similarity: 0.3 },
|
|
133
|
+
userId, auth,
|
|
134
|
+
));
|
|
135
|
+
|
|
136
|
+
expect(res.similar_memories).toBeDefined();
|
|
137
|
+
expect(res.similar_memories.length).toBeGreaterThan(0);
|
|
138
|
+
// The gear memory should be more similar than the cookie recipe
|
|
139
|
+
const ids = res.similar_memories.map((m: any) => m.id);
|
|
140
|
+
expect(ids).toContain(secondMemoryId);
|
|
141
|
+
console.log(` find_similar returned ${res.similar_memories.length} results`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// -------------------------------------------------------------------------
|
|
145
|
+
// QUERY (RAG / semantic)
|
|
146
|
+
// -------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
it('query_memory returns relevant results for natural language question', async () => {
|
|
149
|
+
const res = parseResult(await handleQueryMemory(
|
|
150
|
+
{ query: 'What outdoor activities have I done recently?', limit: 5, min_relevance: 0.3 },
|
|
151
|
+
userId, auth,
|
|
152
|
+
));
|
|
153
|
+
|
|
154
|
+
expect(res.memories).toBeDefined();
|
|
155
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
156
|
+
console.log(` query returned ${res.memories.length} memories`);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
// DELETE (soft delete + confirm)
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
it('requests deletion and gets a token', async () => {
|
|
164
|
+
const res = parseResult(await handleDeleteMemory(
|
|
165
|
+
{ memory_id: thirdMemoryId, reason: 'e2e cleanup' },
|
|
166
|
+
userId, auth,
|
|
167
|
+
));
|
|
168
|
+
|
|
169
|
+
expect(res.success).toBe(true);
|
|
170
|
+
expect(res.token).toBeTruthy();
|
|
171
|
+
|
|
172
|
+
// Confirm the deletion
|
|
173
|
+
const confirmRes = parseResult(await handleConfirm(
|
|
174
|
+
{ token: res.token },
|
|
175
|
+
userId, auth,
|
|
176
|
+
));
|
|
177
|
+
expect(confirmRes.success).toBe(true);
|
|
178
|
+
expect(confirmRes.memory_id).toBe(thirdMemoryId);
|
|
179
|
+
console.log(` deleted memory ${thirdMemoryId}`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('deleted memory is excluded from default search', async () => {
|
|
183
|
+
await waitForIndex();
|
|
184
|
+
|
|
185
|
+
const res = parseResult(await handleSearchMemory(
|
|
186
|
+
{ query: 'chocolate chip cookie recipe', limit: 10 },
|
|
187
|
+
userId, auth,
|
|
188
|
+
));
|
|
189
|
+
|
|
190
|
+
const ids = res.memories.map((m: any) => m.id);
|
|
191
|
+
expect(ids).not.toContain(thirdMemoryId);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('deleted memory is visible with deleted_filter=only', async () => {
|
|
195
|
+
const res = parseResult(await handleSearchMemory(
|
|
196
|
+
{ query: 'chocolate chip cookie recipe', limit: 10, deleted_filter: 'only' } as any,
|
|
197
|
+
userId, auth,
|
|
198
|
+
));
|
|
199
|
+
|
|
200
|
+
const ids = res.memories.map((m: any) => m.id);
|
|
201
|
+
expect(ids).toContain(thirdMemoryId);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E: Preferences — get/set preferences
|
|
3
|
+
*
|
|
4
|
+
* Run with:
|
|
5
|
+
* DOTENV_CONFIG_PATH=.env.e1.local npm run test:e2e -- --testPathPattern=preferences
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
e2eInit, e2eUserId, e2eAuthContext, e2eCleanup, parseResult,
|
|
10
|
+
} from './e2e-helpers.js';
|
|
11
|
+
import { handleSetPreference } from './tools/set-preference.js';
|
|
12
|
+
import { handleGetPreferences } from './tools/get-preferences.js';
|
|
13
|
+
|
|
14
|
+
const userId = e2eUserId('pref');
|
|
15
|
+
const auth = e2eAuthContext();
|
|
16
|
+
|
|
17
|
+
// BUG: remember-core getUserPreferencesPath returns 3-segment Firestore path
|
|
18
|
+
// ({BASE}.users/{userId}/preferences) which fails validation in firebase-admin-sdk-v8.
|
|
19
|
+
// Fix requires remember-core path update to use 4-segment subcollection paths.
|
|
20
|
+
// These tests WILL FAIL until the core bug is fixed.
|
|
21
|
+
describe('E2E: Preferences', () => {
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
await e2eInit();
|
|
24
|
+
}, 30_000);
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await e2eCleanup(userId);
|
|
28
|
+
}, 30_000);
|
|
29
|
+
|
|
30
|
+
it('gets default preferences', async () => {
|
|
31
|
+
const res = parseResult(await handleGetPreferences(
|
|
32
|
+
{},
|
|
33
|
+
userId, auth,
|
|
34
|
+
));
|
|
35
|
+
|
|
36
|
+
expect(res.preferences).toBeDefined();
|
|
37
|
+
expect(res.is_default).toBe(true);
|
|
38
|
+
console.log(' got default preferences');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('sets a search preference', async () => {
|
|
42
|
+
const res = parseResult(await handleSetPreference(
|
|
43
|
+
{ preferences: { search: { default_limit: 25 } } } as any,
|
|
44
|
+
userId, auth,
|
|
45
|
+
));
|
|
46
|
+
|
|
47
|
+
expect(res.success).toBe(true);
|
|
48
|
+
console.log(' set search.default_limit = 25');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('gets search preferences and sees updated value', async () => {
|
|
52
|
+
const res = parseResult(await handleGetPreferences(
|
|
53
|
+
{ category: 'search' },
|
|
54
|
+
userId, auth,
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
expect(res.preferences).toBeDefined();
|
|
58
|
+
expect(res.preferences.search.default_limit).toBe(25);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('updates a preference to a new value', async () => {
|
|
62
|
+
const res = parseResult(await handleSetPreference(
|
|
63
|
+
{ preferences: { search: { default_limit: 50 } } } as any,
|
|
64
|
+
userId, auth,
|
|
65
|
+
));
|
|
66
|
+
expect(res.success).toBe(true);
|
|
67
|
+
|
|
68
|
+
const getRes = parseResult(await handleGetPreferences(
|
|
69
|
+
{ category: 'search' },
|
|
70
|
+
userId, auth,
|
|
71
|
+
));
|
|
72
|
+
expect(getRes.preferences.search.default_limit).toBe(50);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('sets a display preference', async () => {
|
|
76
|
+
const res = parseResult(await handleSetPreference(
|
|
77
|
+
{ preferences: { privacy: { default_trust_level: 0.5 } } } as any,
|
|
78
|
+
userId, auth,
|
|
79
|
+
));
|
|
80
|
+
expect(res.success).toBe(true);
|
|
81
|
+
|
|
82
|
+
const getRes = parseResult(await handleGetPreferences(
|
|
83
|
+
{ category: 'privacy' },
|
|
84
|
+
userId, auth,
|
|
85
|
+
));
|
|
86
|
+
expect(getRes.preferences.privacy.default_trust_level).toBe(0.5);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E: Relationships — create/search/update/delete relationships
|
|
3
|
+
*
|
|
4
|
+
* Run with:
|
|
5
|
+
* DOTENV_CONFIG_PATH=.env.e1.local npm run test:e2e -- --testPathPattern=relationships
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
e2eInit, e2eUserId, e2eAuthContext, e2eEnsureCollection,
|
|
10
|
+
e2eCleanup, parseResult, waitForIndex,
|
|
11
|
+
} from './e2e-helpers.js';
|
|
12
|
+
import { handleCreateMemory } from './tools/create-memory.js';
|
|
13
|
+
import { handleCreateRelationship } from './tools/create-relationship.js';
|
|
14
|
+
import { handleSearchRelationship } from './tools/search-relationship.js';
|
|
15
|
+
import { handleUpdateRelationship } from './tools/update-relationship.js';
|
|
16
|
+
import { handleDeleteRelationship } from './tools/delete-relationship.js';
|
|
17
|
+
import { handleSearchMemory } from './tools/search-memory.js';
|
|
18
|
+
|
|
19
|
+
const userId = e2eUserId('rel');
|
|
20
|
+
const auth = e2eAuthContext();
|
|
21
|
+
|
|
22
|
+
describe('E2E: Relationships', () => {
|
|
23
|
+
let memA: string;
|
|
24
|
+
let memB: string;
|
|
25
|
+
let relId: string;
|
|
26
|
+
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
await e2eInit();
|
|
29
|
+
await e2eEnsureCollection(userId);
|
|
30
|
+
|
|
31
|
+
// Create two memories to link
|
|
32
|
+
const a = parseResult(await handleCreateMemory(
|
|
33
|
+
{ content: 'Learned TypeScript generics at work today. Mapped types are powerful.', tags: ['typescript', 'learning'] },
|
|
34
|
+
userId, auth,
|
|
35
|
+
));
|
|
36
|
+
memA = a.memory_id;
|
|
37
|
+
|
|
38
|
+
const b = parseResult(await handleCreateMemory(
|
|
39
|
+
{ content: 'Built a type-safe API client using TypeScript generics and Zod validation.', tags: ['typescript', 'api'] },
|
|
40
|
+
userId, auth,
|
|
41
|
+
));
|
|
42
|
+
memB = b.memory_id;
|
|
43
|
+
|
|
44
|
+
await waitForIndex();
|
|
45
|
+
}, 30_000);
|
|
46
|
+
|
|
47
|
+
afterAll(async () => {
|
|
48
|
+
await e2eCleanup(userId);
|
|
49
|
+
}, 30_000);
|
|
50
|
+
|
|
51
|
+
// -------------------------------------------------------------------------
|
|
52
|
+
// CREATE RELATIONSHIP
|
|
53
|
+
// -------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
it('creates a relationship linking two memories', async () => {
|
|
56
|
+
const res = parseResult(await handleCreateRelationship(
|
|
57
|
+
{
|
|
58
|
+
memory_ids: [memA, memB],
|
|
59
|
+
relationship_type: 'applied_learning',
|
|
60
|
+
observation: 'The TypeScript generics knowledge was directly applied to build the API client.',
|
|
61
|
+
strength: 0.9,
|
|
62
|
+
},
|
|
63
|
+
userId, auth,
|
|
64
|
+
));
|
|
65
|
+
|
|
66
|
+
expect(res.relationship_id).toBeTruthy();
|
|
67
|
+
expect(res.memory_ids).toContain(memA);
|
|
68
|
+
expect(res.memory_ids).toContain(memB);
|
|
69
|
+
relId = res.relationship_id;
|
|
70
|
+
console.log(` created relationship ${relId}`);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
// SEARCH RELATIONSHIP
|
|
75
|
+
// -------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
it('finds relationship via semantic search on observation', async () => {
|
|
78
|
+
await waitForIndex();
|
|
79
|
+
|
|
80
|
+
const res = parseResult(await handleSearchRelationship(
|
|
81
|
+
{ query: 'applied generics knowledge to API', limit: 5 },
|
|
82
|
+
userId, auth,
|
|
83
|
+
));
|
|
84
|
+
|
|
85
|
+
expect(res.relationships).toBeDefined();
|
|
86
|
+
expect(res.relationships.length).toBeGreaterThan(0);
|
|
87
|
+
const ids = res.relationships.map((r: any) => r.id);
|
|
88
|
+
expect(ids).toContain(relId);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('search_memory with include_relationships returns both memories and relationships', async () => {
|
|
92
|
+
const res = parseResult(await handleSearchMemory(
|
|
93
|
+
{ query: 'TypeScript generics', limit: 10, include_relationships: true },
|
|
94
|
+
userId, auth,
|
|
95
|
+
));
|
|
96
|
+
|
|
97
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
98
|
+
expect(res.relationships).toBeDefined();
|
|
99
|
+
expect(res.relationships.length).toBeGreaterThan(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// -------------------------------------------------------------------------
|
|
103
|
+
// UPDATE RELATIONSHIP
|
|
104
|
+
// -------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
it('updates the relationship observation', async () => {
|
|
107
|
+
const res = parseResult(await handleUpdateRelationship(
|
|
108
|
+
{
|
|
109
|
+
relationship_id: relId,
|
|
110
|
+
observation: 'The TypeScript generics knowledge was applied to build a production API client with full type safety.',
|
|
111
|
+
strength: 1.0,
|
|
112
|
+
},
|
|
113
|
+
userId, auth,
|
|
114
|
+
));
|
|
115
|
+
|
|
116
|
+
expect(res.relationship_id).toBe(relId);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('search reflects updated observation', async () => {
|
|
120
|
+
await waitForIndex();
|
|
121
|
+
|
|
122
|
+
const res = parseResult(await handleSearchRelationship(
|
|
123
|
+
{ query: 'production API client full type safety', limit: 5 },
|
|
124
|
+
userId, auth,
|
|
125
|
+
));
|
|
126
|
+
|
|
127
|
+
const found = res.relationships.find((r: any) => r.id === relId);
|
|
128
|
+
expect(found).toBeTruthy();
|
|
129
|
+
expect(found.observation).toContain('production');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
// DELETE RELATIONSHIP
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
it('deletes the relationship', async () => {
|
|
137
|
+
const res = parseResult(await handleDeleteRelationship(
|
|
138
|
+
{ relationship_id: relId },
|
|
139
|
+
userId, auth,
|
|
140
|
+
));
|
|
141
|
+
|
|
142
|
+
expect(res.deleted).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('deleted relationship no longer appears in search', async () => {
|
|
146
|
+
await waitForIndex();
|
|
147
|
+
|
|
148
|
+
const res = parseResult(await handleSearchRelationship(
|
|
149
|
+
{ query: 'TypeScript generics applied learning', limit: 5 },
|
|
150
|
+
userId, auth,
|
|
151
|
+
));
|
|
152
|
+
|
|
153
|
+
const ids = (res.relationships || []).map((r: any) => r.id);
|
|
154
|
+
expect(ids).not.toContain(relId);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E: Advanced Search Modes — search_by and search_space_by
|
|
3
|
+
*
|
|
4
|
+
* Run with:
|
|
5
|
+
* DOTENV_CONFIG_PATH=.env.e1.local npm run test:e2e -- --testPathPattern=search-modes
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
e2eInit, e2eUserId, e2eAuthContext, e2eEnsureCollection,
|
|
10
|
+
e2eCleanup, parseResult, waitForIndex,
|
|
11
|
+
} from './e2e-helpers.js';
|
|
12
|
+
import { handleCreateMemory } from './tools/create-memory.js';
|
|
13
|
+
import { handleSearchBy } from './tools/search-by.js';
|
|
14
|
+
import { handlePublish } from './tools/publish.js';
|
|
15
|
+
import { handleConfirm } from './tools/confirm.js';
|
|
16
|
+
import { handleSearchSpaceBy } from './tools/search-space-by.js';
|
|
17
|
+
|
|
18
|
+
const userId = e2eUserId('modes');
|
|
19
|
+
const auth = e2eAuthContext();
|
|
20
|
+
const SPACE = 'the_void';
|
|
21
|
+
|
|
22
|
+
describe('E2E: Search Modes', () => {
|
|
23
|
+
const memoryIds: string[] = [];
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
await e2eInit();
|
|
27
|
+
await e2eEnsureCollection(userId);
|
|
28
|
+
|
|
29
|
+
// Create several memories with varying weights and tags
|
|
30
|
+
const memories = [
|
|
31
|
+
{ content: 'Morning run along the river trail, 5 miles in 42 minutes.', weight: 0.5, tags: ['fitness', 'running'] },
|
|
32
|
+
{ content: 'Read "Thinking Fast and Slow" by Kahneman. Chapter on anchoring bias was eye-opening.', weight: 0.9, tags: ['reading', 'psychology'] },
|
|
33
|
+
{ content: 'Cooked pad thai for dinner. Used tamarind paste instead of ketchup this time.', weight: 0.3, tags: ['cooking', 'thai'] },
|
|
34
|
+
{ content: 'Team standup: discussed migration plan for database sharding.', weight: 0.7, tags: ['work', 'database'] },
|
|
35
|
+
{ content: 'Guitar practice: learned the intro to Stairway to Heaven.', weight: 0.6, tags: ['music', 'guitar'] },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
for (const mem of memories) {
|
|
39
|
+
const res = parseResult(await handleCreateMemory(mem, userId, auth));
|
|
40
|
+
memoryIds.push(res.memory_id);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Publish one to space for search_space_by tests
|
|
44
|
+
const pubRes = parseResult(await handlePublish(
|
|
45
|
+
{ memory_id: memoryIds[1], spaces: [SPACE] },
|
|
46
|
+
userId, auth,
|
|
47
|
+
));
|
|
48
|
+
const confirmRes = parseResult(await handleConfirm(
|
|
49
|
+
{ token: pubRes.token },
|
|
50
|
+
userId, auth,
|
|
51
|
+
));
|
|
52
|
+
|
|
53
|
+
await waitForIndex(2000);
|
|
54
|
+
}, 60_000);
|
|
55
|
+
|
|
56
|
+
afterAll(async () => {
|
|
57
|
+
await e2eCleanup(userId);
|
|
58
|
+
}, 30_000);
|
|
59
|
+
|
|
60
|
+
// -------------------------------------------------------------------------
|
|
61
|
+
// search_by modes
|
|
62
|
+
// -------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
describe('search_by', () => {
|
|
65
|
+
it('byTime returns memories in chronological order', async () => {
|
|
66
|
+
const res = parseResult(await handleSearchBy(
|
|
67
|
+
{ mode: 'byTime', sort_order: 'desc', limit: 5 },
|
|
68
|
+
userId, auth,
|
|
69
|
+
));
|
|
70
|
+
|
|
71
|
+
expect(res.memories).toBeDefined();
|
|
72
|
+
expect(res.memories.length).toBe(5);
|
|
73
|
+
// Should be newest first
|
|
74
|
+
const dates = res.memories.map((m: any) => new Date(m.created_at).getTime());
|
|
75
|
+
for (let i = 0; i < dates.length - 1; i++) {
|
|
76
|
+
expect(dates[i]).toBeGreaterThanOrEqual(dates[i + 1]);
|
|
77
|
+
}
|
|
78
|
+
console.log(` byTime returned ${res.memories.length} memories`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('byTime ascending returns oldest first', async () => {
|
|
82
|
+
const res = parseResult(await handleSearchBy(
|
|
83
|
+
{ mode: 'byTime', sort_order: 'asc', limit: 5 },
|
|
84
|
+
userId, auth,
|
|
85
|
+
));
|
|
86
|
+
|
|
87
|
+
const dates = res.memories.map((m: any) => new Date(m.created_at).getTime());
|
|
88
|
+
for (let i = 0; i < dates.length - 1; i++) {
|
|
89
|
+
expect(dates[i]).toBeLessThanOrEqual(dates[i + 1]);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('byBroad returns truncated content for scanning', async () => {
|
|
94
|
+
const res = parseResult(await handleSearchBy(
|
|
95
|
+
{ mode: 'byBroad', limit: 10 },
|
|
96
|
+
userId, auth,
|
|
97
|
+
));
|
|
98
|
+
|
|
99
|
+
// byBroad returns { results: [...] } not { memories: [...] }
|
|
100
|
+
expect(res.results).toBeDefined();
|
|
101
|
+
expect(res.results.length).toBeGreaterThan(0);
|
|
102
|
+
// Verify truncated content fields exist
|
|
103
|
+
const first = res.results[0];
|
|
104
|
+
expect(first.content_head).toBeDefined();
|
|
105
|
+
console.log(` byBroad returned ${res.results.length} results`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('byRandom returns results', async () => {
|
|
109
|
+
const res = parseResult(await handleSearchBy(
|
|
110
|
+
{ mode: 'byRandom', limit: 3 },
|
|
111
|
+
userId, auth,
|
|
112
|
+
));
|
|
113
|
+
|
|
114
|
+
// byRandom returns { results: [...], total_pool_size }
|
|
115
|
+
expect(res.results).toBeDefined();
|
|
116
|
+
expect(res.results.length).toBeGreaterThan(0);
|
|
117
|
+
expect(res.results.length).toBeLessThanOrEqual(3);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('byProperty sorts by weight', async () => {
|
|
121
|
+
const res = parseResult(await handleSearchBy(
|
|
122
|
+
{ mode: 'byProperty', sort_field: 'weight', sort_order: 'desc', limit: 5 },
|
|
123
|
+
userId, auth,
|
|
124
|
+
));
|
|
125
|
+
|
|
126
|
+
expect(res.memories).toBeDefined();
|
|
127
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
128
|
+
// Highest weight memory (0.9 — the reading one) should be first
|
|
129
|
+
const weights = res.memories.map((m: any) => m.weight);
|
|
130
|
+
for (let i = 0; i < weights.length - 1; i++) {
|
|
131
|
+
expect(weights[i]).toBeGreaterThanOrEqual(weights[i + 1]);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('byDensity returns results (may be 0 if no relationships)', async () => {
|
|
136
|
+
const res = parseResult(await handleSearchBy(
|
|
137
|
+
{ mode: 'byDensity', limit: 5 },
|
|
138
|
+
userId, auth,
|
|
139
|
+
));
|
|
140
|
+
|
|
141
|
+
expect(res.memories).toBeDefined();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
// search_space_by modes
|
|
147
|
+
// -------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
// BUG: Most search_space_by modes are unimplemented in remember-core's SpaceService.
|
|
150
|
+
// byTime, byRating, byProperty, byBroad, byRandom all return hardcoded error strings.
|
|
151
|
+
// Fix: implement these modes in SpaceService, mirroring MemoryService equivalents.
|
|
152
|
+
// These tests WILL FAIL until the core methods are implemented.
|
|
153
|
+
describe('search_space_by', () => {
|
|
154
|
+
it('byTime returns space memories chronologically', async () => {
|
|
155
|
+
const res = parseResult(await handleSearchSpaceBy(
|
|
156
|
+
{ mode: 'byTime', spaces: [SPACE], sort_order: 'desc', limit: 5 },
|
|
157
|
+
userId, auth,
|
|
158
|
+
));
|
|
159
|
+
|
|
160
|
+
expect(res.memories).toBeDefined();
|
|
161
|
+
expect(res.memories.length).toBeGreaterThan(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('byBroad returns space memories with truncated content', async () => {
|
|
165
|
+
const res = parseResult(await handleSearchSpaceBy(
|
|
166
|
+
{ mode: 'byBroad', spaces: [SPACE], limit: 10 },
|
|
167
|
+
userId, auth,
|
|
168
|
+
));
|
|
169
|
+
|
|
170
|
+
expect(res.results).toBeDefined();
|
|
171
|
+
expect(res.results.length).toBeGreaterThan(0);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('byRandom returns space memories', async () => {
|
|
175
|
+
const res = parseResult(await handleSearchSpaceBy(
|
|
176
|
+
{ mode: 'byRandom', spaces: [SPACE], limit: 3 },
|
|
177
|
+
userId, auth,
|
|
178
|
+
));
|
|
179
|
+
|
|
180
|
+
expect(res.results).toBeDefined();
|
|
181
|
+
expect(res.results.length).toBeGreaterThan(0);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|