@prmichaelsen/remember-mcp 3.0.0 → 3.13.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.
- package/AGENT.md +296 -250
- package/CHANGELOG.md +358 -0
- package/README.md +68 -45
- package/agent/commands/acp.clarification-create.md +382 -0
- package/agent/commands/acp.project-info.md +309 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-update.md +296 -0
- package/agent/commands/acp.task-create.md +17 -9
- package/agent/commands/git.commit.md +13 -1
- package/agent/design/comment-memory-type.md +2 -2
- package/agent/design/local.collaborative-memory-sync.md +265 -0
- package/agent/design/local.content-flags.md +210 -0
- package/agent/design/local.ghost-persona-system.md +273 -0
- package/agent/design/local.group-acl-integration.md +338 -0
- package/agent/design/local.memory-acl-schema.md +352 -0
- package/agent/design/local.memory-collection-pattern-v2.md +348 -0
- package/agent/design/local.moderation-and-space-config.md +257 -0
- package/agent/design/local.v2-api-reference.md +621 -0
- package/agent/design/local.v2-migration-guide.md +191 -0
- package/agent/design/local.v2-usage-examples.md +265 -0
- package/agent/design/permissions-storage-architecture.md +11 -3
- package/agent/design/trust-escalation-prevention.md +9 -2
- package/agent/design/trust-system-implementation.md +12 -3
- package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
- package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
- package/agent/progress.yaml +628 -49
- package/agent/scripts/acp.common.sh +2 -0
- package/agent/scripts/acp.install.sh +11 -1
- package/agent/scripts/acp.package-install-optimized.sh +454 -0
- package/agent/scripts/acp.package-install.sh +247 -300
- package/agent/scripts/acp.project-info.sh +218 -0
- package/agent/scripts/acp.project-remove.sh +302 -0
- package/agent/scripts/acp.project-update.sh +296 -0
- package/agent/scripts/acp.yaml-parser.sh +128 -10
- package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
- package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
- package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
- package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
- package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
- package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
- package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
- package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
- package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
- package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
- package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
- package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
- package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
- package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
- package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
- package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
- package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
- package/dist/collections/composite-ids.d.ts +106 -0
- package/dist/collections/core-infrastructure.spec.d.ts +11 -0
- package/dist/collections/dot-notation.d.ts +106 -0
- package/dist/collections/tracking-arrays.d.ts +176 -0
- package/dist/constants/content-types.d.ts +1 -0
- package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
- package/dist/schema/v2-collections.d.ts +210 -0
- package/dist/server-factory.d.ts +15 -0
- package/dist/server-factory.js +2798 -1029
- package/dist/server.js +2526 -1012
- package/dist/services/access-control.d.ts +103 -0
- package/dist/services/access-control.spec.d.ts +2 -0
- package/dist/services/credentials-provider.d.ts +24 -0
- package/dist/services/credentials-provider.spec.d.ts +2 -0
- package/dist/services/escalation.service.d.ts +22 -0
- package/dist/services/escalation.service.spec.d.ts +2 -0
- package/dist/services/ghost-config.service.d.ts +55 -0
- package/dist/services/ghost-config.service.spec.d.ts +2 -0
- package/dist/services/space-config.service.d.ts +23 -0
- package/dist/services/space-config.service.spec.d.ts +2 -0
- package/dist/services/trust-enforcement.d.ts +83 -0
- package/dist/services/trust-enforcement.spec.d.ts +2 -0
- package/dist/services/trust-validator.d.ts +43 -0
- package/dist/services/trust-validator.spec.d.ts +2 -0
- package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
- package/dist/tools/confirm.d.ts +8 -1
- package/dist/tools/create-memory.d.ts +2 -1
- package/dist/tools/create-memory.spec.d.ts +10 -0
- package/dist/tools/create-relationship.d.ts +2 -1
- package/dist/tools/delete-memory.d.ts +2 -1
- package/dist/tools/delete-relationship.d.ts +2 -1
- package/dist/tools/deny.d.ts +2 -1
- package/dist/tools/find-similar.d.ts +2 -1
- package/dist/tools/get-preferences.d.ts +2 -1
- package/dist/tools/ghost-config.d.ts +27 -0
- package/dist/tools/ghost-config.spec.d.ts +2 -0
- package/dist/tools/moderate.d.ts +20 -0
- package/dist/tools/moderate.spec.d.ts +5 -0
- package/dist/tools/publish.d.ts +11 -3
- package/dist/tools/query-memory.d.ts +3 -1
- package/dist/tools/query-space.d.ts +4 -1
- package/dist/tools/retract.d.ts +29 -0
- package/dist/tools/revise.d.ts +45 -0
- package/dist/tools/revise.spec.d.ts +8 -0
- package/dist/tools/search-memory.d.ts +2 -1
- package/dist/tools/search-relationship.d.ts +2 -1
- package/dist/tools/search-space.d.ts +25 -5
- package/dist/tools/search-space.spec.d.ts +9 -0
- package/dist/tools/set-preference.d.ts +2 -1
- package/dist/tools/update-memory.d.ts +2 -1
- package/dist/tools/update-relationship.d.ts +2 -1
- package/dist/types/access-result.d.ts +48 -0
- package/dist/types/access-result.spec.d.ts +2 -0
- package/dist/types/auth.d.ts +46 -0
- package/dist/types/ghost-config.d.ts +36 -0
- package/dist/types/memory.d.ts +3 -1
- package/dist/types/preferences.d.ts +1 -1
- package/dist/utils/auth-helpers.d.ts +14 -0
- package/dist/utils/auth-helpers.spec.d.ts +2 -0
- package/dist/utils/test-data-generator.d.ts +124 -0
- package/dist/utils/test-data-generator.spec.d.ts +12 -0
- package/dist/v2-performance.e2e.d.ts +17 -0
- package/dist/v2-smoke.e2e.d.ts +14 -0
- package/dist/weaviate/client.d.ts +5 -8
- package/dist/weaviate/space-schema.d.ts +2 -2
- package/docs/performance/v2-benchmarks.md +80 -0
- package/jest.e2e.config.js +14 -3
- package/package.json +1 -1
- package/scripts/.collection-recreation-state.yaml +16 -0
- package/scripts/.gitkeep +5 -0
- package/scripts/README-collection-recreation.md +224 -0
- package/scripts/README.md +51 -0
- package/scripts/backup-collections.ts +543 -0
- package/scripts/delete-collection.ts +137 -0
- package/scripts/migrate-recreate-collections.ts +578 -0
- package/scripts/migrate-v1-to-v2.ts +1094 -0
- package/scripts/package-lock.json +1113 -0
- package/scripts/package.json +27 -0
- package/src/collections/composite-ids.ts +193 -0
- package/src/collections/core-infrastructure.spec.ts +353 -0
- package/src/collections/dot-notation.ts +212 -0
- package/src/collections/tracking-arrays.ts +298 -0
- package/src/constants/content-types.ts +20 -0
- package/src/schema/v2-collections-comments.spec.ts +141 -0
- package/src/schema/v2-collections.ts +433 -0
- package/src/server-factory.ts +89 -20
- package/src/server.ts +45 -17
- package/src/services/access-control.spec.ts +383 -0
- package/src/services/access-control.ts +291 -0
- package/src/services/credentials-provider.spec.ts +22 -0
- package/src/services/credentials-provider.ts +34 -0
- package/src/services/escalation.service.spec.ts +183 -0
- package/src/services/escalation.service.ts +150 -0
- package/src/services/ghost-config.service.spec.ts +339 -0
- package/src/services/ghost-config.service.ts +219 -0
- package/src/services/space-config.service.spec.ts +102 -0
- package/src/services/space-config.service.ts +79 -0
- package/src/services/trust-enforcement.spec.ts +309 -0
- package/src/services/trust-enforcement.ts +197 -0
- package/src/services/trust-validator.spec.ts +108 -0
- package/src/services/trust-validator.ts +105 -0
- package/src/tools/confirm-publish-moderation.spec.ts +240 -0
- package/src/tools/confirm.ts +869 -135
- package/src/tools/create-memory.spec.ts +126 -0
- package/src/tools/create-memory.ts +20 -27
- package/src/tools/create-relationship.ts +17 -8
- package/src/tools/delete-memory.ts +13 -6
- package/src/tools/delete-relationship.ts +15 -6
- package/src/tools/deny.ts +8 -1
- package/src/tools/find-similar.ts +21 -8
- package/src/tools/get-preferences.ts +10 -1
- package/src/tools/ghost-config.spec.ts +180 -0
- package/src/tools/ghost-config.ts +230 -0
- package/src/tools/moderate.spec.ts +277 -0
- package/src/tools/moderate.ts +219 -0
- package/src/tools/publish.ts +99 -41
- package/src/tools/query-memory.ts +28 -6
- package/src/tools/query-space.ts +39 -4
- package/src/tools/retract.ts +292 -0
- package/src/tools/revise.spec.ts +146 -0
- package/src/tools/revise.ts +283 -0
- package/src/tools/search-memory.ts +30 -7
- package/src/tools/search-relationship.ts +11 -2
- package/src/tools/search-space.spec.ts +341 -0
- package/src/tools/search-space.ts +323 -99
- package/src/tools/set-preference.ts +10 -1
- package/src/tools/update-memory.ts +16 -5
- package/src/tools/update-relationship.ts +10 -1
- package/src/types/access-result.spec.ts +193 -0
- package/src/types/access-result.ts +62 -0
- package/src/types/auth.ts +52 -0
- package/src/types/ghost-config.ts +46 -0
- package/src/types/memory.ts +9 -1
- package/src/types/preferences.ts +2 -2
- package/src/utils/auth-helpers.spec.ts +75 -0
- package/src/utils/auth-helpers.ts +25 -0
- package/src/utils/test-data-generator.spec.ts +317 -0
- package/src/utils/test-data-generator.ts +292 -0
- package/src/utils/weaviate-filters.ts +4 -4
- package/src/v2-performance.e2e.ts +173 -0
- package/src/v2-smoke.e2e.ts +401 -0
- package/src/weaviate/client.spec.ts +5 -5
- package/src/weaviate/client.ts +51 -36
- package/src/weaviate/schema.ts +11 -256
- package/src/weaviate/space-schema.spec.ts +24 -24
- package/src/weaviate/space-schema.ts +18 -6
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for test-data-generator utilities
|
|
3
|
+
*
|
|
4
|
+
* Tests cover:
|
|
5
|
+
* - generateMemory: correct shape, determinism, field ranges
|
|
6
|
+
* - generateMemories: count, seeded reproducibility
|
|
7
|
+
* - filterBySpace / filterByGroup: correctness
|
|
8
|
+
* - getDatasetStats: accuracy
|
|
9
|
+
* - measureMs / benchmark: timing utilities
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
generateMemory,
|
|
14
|
+
generateMemories,
|
|
15
|
+
filterBySpace,
|
|
16
|
+
filterByGroup,
|
|
17
|
+
getDatasetStats,
|
|
18
|
+
measureMs,
|
|
19
|
+
benchmark,
|
|
20
|
+
type GenerateOptions,
|
|
21
|
+
} from './test-data-generator.js';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// generateMemory
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function makeRng(seed = 42) {
|
|
28
|
+
let state = seed;
|
|
29
|
+
return {
|
|
30
|
+
next(): number {
|
|
31
|
+
state = (state * 1664525 + 1013904223) & 0xffffffff;
|
|
32
|
+
return (state >>> 0) / 0xffffffff;
|
|
33
|
+
},
|
|
34
|
+
nextInt(min: number, max: number): number {
|
|
35
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
36
|
+
},
|
|
37
|
+
pick<T>(arr: T[]): T {
|
|
38
|
+
return arr[this.nextInt(0, arr.length - 1)];
|
|
39
|
+
},
|
|
40
|
+
shuffle<T>(arr: T[]): T[] {
|
|
41
|
+
const copy = [...arr];
|
|
42
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
43
|
+
const j = this.nextInt(0, i);
|
|
44
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
45
|
+
}
|
|
46
|
+
return copy;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('generateMemory', () => {
|
|
52
|
+
const opts: GenerateOptions = {
|
|
53
|
+
count: 1,
|
|
54
|
+
userId: 'test-user',
|
|
55
|
+
spaces: ['the_void', 'dogs'],
|
|
56
|
+
groups: ['grp-1'],
|
|
57
|
+
publishProbability: 1.0, // ensure fields are set
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
it('returns correct doc_type', () => {
|
|
61
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
62
|
+
expect(mem.doc_type).toBe('memory');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('uses userId from options', () => {
|
|
66
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
67
|
+
expect(mem.user_id).toBe('test-user');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('has non-empty content', () => {
|
|
71
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
72
|
+
expect(mem.content.length).toBeGreaterThan(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('has weight in [0, 1] range', () => {
|
|
76
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
77
|
+
expect(mem.weight).toBeGreaterThanOrEqual(0);
|
|
78
|
+
expect(mem.weight).toBeLessThanOrEqual(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('has trust in [0, 1] range', () => {
|
|
82
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
83
|
+
expect(mem.trust).toBeGreaterThanOrEqual(0);
|
|
84
|
+
expect(mem.trust).toBeLessThanOrEqual(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('always initializes deleted_at to null', () => {
|
|
88
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
89
|
+
expect(mem.deleted_at).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('assigns space_ids from provided spaces when publishProbability is 1', () => {
|
|
93
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
94
|
+
expect(mem.space_ids).toEqual(expect.arrayContaining(['the_void', 'dogs']));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('assigns group_ids from provided groups when publishProbability is 1', () => {
|
|
98
|
+
const mem = generateMemory(0, opts, makeRng());
|
|
99
|
+
expect(mem.group_ids).toContain('grp-1');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('assigns empty space_ids when publishProbability is 0', () => {
|
|
103
|
+
const zeroOpts = { ...opts, publishProbability: 0 };
|
|
104
|
+
const mem = generateMemory(0, zeroOpts, makeRng());
|
|
105
|
+
expect(mem.space_ids).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('assigns empty group_ids when publishProbability is 0', () => {
|
|
109
|
+
const zeroOpts = { ...opts, publishProbability: 0 };
|
|
110
|
+
const mem = generateMemory(0, zeroOpts, makeRng());
|
|
111
|
+
expect(mem.group_ids).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('includes index in id', () => {
|
|
115
|
+
const mem = generateMemory(7, opts, makeRng());
|
|
116
|
+
expect(mem.id).toContain('00000007');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('produces different created_at per index (1 minute apart)', () => {
|
|
120
|
+
const mem0 = generateMemory(0, opts, makeRng());
|
|
121
|
+
const mem1 = generateMemory(1, opts, makeRng());
|
|
122
|
+
const diff = new Date(mem1.created_at).getTime() - new Date(mem0.created_at).getTime();
|
|
123
|
+
expect(diff).toBe(60_000);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// generateMemories
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
describe('generateMemories', () => {
|
|
132
|
+
it('returns exactly count memories', () => {
|
|
133
|
+
const memories = generateMemories({ count: 100 });
|
|
134
|
+
expect(memories).toHaveLength(100);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('returns empty array for count 0', () => {
|
|
138
|
+
const memories = generateMemories({ count: 0 });
|
|
139
|
+
expect(memories).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('is deterministic with same seed', () => {
|
|
143
|
+
const a = generateMemories({ count: 50 }, 99);
|
|
144
|
+
const b = generateMemories({ count: 50 }, 99);
|
|
145
|
+
expect(a[0].id).toBe(b[0].id);
|
|
146
|
+
expect(a[0].content).toBe(b[0].content);
|
|
147
|
+
expect(a[49].weight).toBe(b[49].weight);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('produces different results with different seeds', () => {
|
|
151
|
+
const a = generateMemories({ count: 10 }, 1);
|
|
152
|
+
const b = generateMemories({ count: 10 }, 2);
|
|
153
|
+
// At least one memory should differ
|
|
154
|
+
const differ = a.some((m, i) => m.content !== b[i].content || m.weight !== b[i].weight);
|
|
155
|
+
expect(differ).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('all memories have correct user_id', () => {
|
|
159
|
+
const memories = generateMemories({ count: 20, userId: 'bench-user' });
|
|
160
|
+
expect(memories.every(m => m.user_id === 'bench-user')).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('all memories have doc_type memory', () => {
|
|
164
|
+
const memories = generateMemories({ count: 20 });
|
|
165
|
+
expect(memories.every(m => m.doc_type === 'memory')).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// filterBySpace / filterByGroup
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
describe('filterBySpace', () => {
|
|
174
|
+
const memories = generateMemories({
|
|
175
|
+
count: 200,
|
|
176
|
+
spaces: ['the_void', 'dogs'],
|
|
177
|
+
publishProbability: 0.5,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('returns only memories with the specified space_id', () => {
|
|
181
|
+
const filtered = filterBySpace(memories, 'the_void');
|
|
182
|
+
expect(filtered.every(m => m.space_ids.includes('the_void'))).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('returns empty array for non-existent space', () => {
|
|
186
|
+
const filtered = filterBySpace(memories, 'non-existent-space');
|
|
187
|
+
expect(filtered).toHaveLength(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('returns subset of total memories', () => {
|
|
191
|
+
const filtered = filterBySpace(memories, 'the_void');
|
|
192
|
+
expect(filtered.length).toBeLessThanOrEqual(memories.length);
|
|
193
|
+
expect(filtered.length).toBeGreaterThan(0);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('filterByGroup', () => {
|
|
198
|
+
const memories = generateMemories({
|
|
199
|
+
count: 200,
|
|
200
|
+
groups: ['grp-a', 'grp-b'],
|
|
201
|
+
publishProbability: 0.5,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('returns only memories with the specified group_id', () => {
|
|
205
|
+
const filtered = filterByGroup(memories, 'grp-a');
|
|
206
|
+
expect(filtered.every(m => m.group_ids.includes('grp-a'))).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('returns empty array for non-existent group', () => {
|
|
210
|
+
const filtered = filterByGroup(memories, 'grp-z');
|
|
211
|
+
expect(filtered).toHaveLength(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// getDatasetStats
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
describe('getDatasetStats', () => {
|
|
220
|
+
it('returns total equal to memories.length', () => {
|
|
221
|
+
const memories = generateMemories({ count: 100 });
|
|
222
|
+
const stats = getDatasetStats(memories);
|
|
223
|
+
expect(stats.total).toBe(100);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('unpublished + publishedToAnySpace/Group accounting is internally consistent', () => {
|
|
227
|
+
const memories = generateMemories({
|
|
228
|
+
count: 200,
|
|
229
|
+
spaces: ['the_void'],
|
|
230
|
+
groups: ['grp-1'],
|
|
231
|
+
publishProbability: 0.5,
|
|
232
|
+
});
|
|
233
|
+
const stats = getDatasetStats(memories);
|
|
234
|
+
expect(stats.publishedToAnySpace + stats.publishedToAnyGroup + stats.unpublished).toBeGreaterThanOrEqual(stats.total);
|
|
235
|
+
expect(stats.total).toBe(200);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('bySpace counts match filterBySpace counts', () => {
|
|
239
|
+
const memories = generateMemories({
|
|
240
|
+
count: 100,
|
|
241
|
+
spaces: ['the_void', 'dogs'],
|
|
242
|
+
publishProbability: 0.5,
|
|
243
|
+
});
|
|
244
|
+
const stats = getDatasetStats(memories);
|
|
245
|
+
const filteredCount = filterBySpace(memories, 'the_void').length;
|
|
246
|
+
expect(stats.bySpace['the_void'] ?? 0).toBe(filteredCount);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('avgTagsPerMemory is a reasonable number between 0 and 3', () => {
|
|
250
|
+
const memories = generateMemories({ count: 100 });
|
|
251
|
+
const stats = getDatasetStats(memories);
|
|
252
|
+
expect(stats.avgTagsPerMemory).toBeGreaterThanOrEqual(0);
|
|
253
|
+
expect(stats.avgTagsPerMemory).toBeLessThanOrEqual(3);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('returns all zeros for empty dataset', () => {
|
|
257
|
+
const stats = getDatasetStats([]);
|
|
258
|
+
expect(stats.total).toBe(0);
|
|
259
|
+
expect(stats.publishedToAnySpace).toBe(0);
|
|
260
|
+
expect(stats.unpublished).toBe(0);
|
|
261
|
+
expect(stats.avgTagsPerMemory).toBe(0);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
// measureMs
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
describe('measureMs', () => {
|
|
270
|
+
it('returns result from the async function', async () => {
|
|
271
|
+
const { result } = await measureMs(async () => 42);
|
|
272
|
+
expect(result).toBe(42);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('returns elapsedMs as a non-negative number', async () => {
|
|
276
|
+
const { elapsedMs } = await measureMs(async () => {});
|
|
277
|
+
expect(elapsedMs).toBeGreaterThanOrEqual(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('captures time for delayed operations', async () => {
|
|
281
|
+
const { elapsedMs } = await measureMs(async () => {
|
|
282
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
283
|
+
});
|
|
284
|
+
expect(elapsedMs).toBeGreaterThanOrEqual(15);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// benchmark
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
describe('benchmark', () => {
|
|
293
|
+
it('runs exactly iterations times', async () => {
|
|
294
|
+
let count = 0;
|
|
295
|
+
await benchmark(async () => { count++; }, 5);
|
|
296
|
+
expect(count).toBe(5);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('returns results array of correct length', async () => {
|
|
300
|
+
const { results } = await benchmark(async () => 'x', 3);
|
|
301
|
+
expect(results).toHaveLength(3);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('min <= avg <= max', async () => {
|
|
305
|
+
const { min, avg, max } = await benchmark(async () => {
|
|
306
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
307
|
+
}, 4);
|
|
308
|
+
expect(min).toBeLessThanOrEqual(avg);
|
|
309
|
+
expect(avg).toBeLessThanOrEqual(max);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('p95 is between min and max', async () => {
|
|
313
|
+
const { min, max, p95 } = await benchmark(async () => {}, 10);
|
|
314
|
+
expect(p95).toBeGreaterThanOrEqual(min);
|
|
315
|
+
expect(p95).toBeLessThanOrEqual(max);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Data Generator for Memory Collection Pattern v2 Performance Tests
|
|
3
|
+
*
|
|
4
|
+
* Generates synthetic memory objects at scale for benchmarking.
|
|
5
|
+
* All functions are pure (no Weaviate dependency) — usable in unit tests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SUPPORTED_SPACES } from '../types/space-memory.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimal memory object shape used for performance test generation.
|
|
12
|
+
* Mirrors the fields written to Weaviate during create/publish.
|
|
13
|
+
*/
|
|
14
|
+
export interface SyntheticMemory {
|
|
15
|
+
id: string;
|
|
16
|
+
user_id: string;
|
|
17
|
+
doc_type: 'memory';
|
|
18
|
+
content: string;
|
|
19
|
+
title: string;
|
|
20
|
+
type: string;
|
|
21
|
+
weight: number;
|
|
22
|
+
trust: number;
|
|
23
|
+
confidence: number;
|
|
24
|
+
tags: string[];
|
|
25
|
+
space_ids: string[];
|
|
26
|
+
group_ids: string[];
|
|
27
|
+
created_at: string;
|
|
28
|
+
updated_at: string;
|
|
29
|
+
version: number;
|
|
30
|
+
access_count: number;
|
|
31
|
+
deleted_at: null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options for generating synthetic memories
|
|
36
|
+
*/
|
|
37
|
+
export interface GenerateOptions {
|
|
38
|
+
/** Number of memories to generate */
|
|
39
|
+
count: number;
|
|
40
|
+
/** User ID to assign (default: 'perf-test-user') */
|
|
41
|
+
userId?: string;
|
|
42
|
+
/** Spaces to randomly assign (default: subset of SUPPORTED_SPACES) */
|
|
43
|
+
spaces?: string[];
|
|
44
|
+
/** Group IDs to randomly assign */
|
|
45
|
+
groups?: string[];
|
|
46
|
+
/** Probability (0-1) that a memory has been published to a space */
|
|
47
|
+
publishProbability?: number;
|
|
48
|
+
/** Base ISO date string for created_at (default: current time) */
|
|
49
|
+
baseDate?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A fast, deterministic pseudo-random number generator (LCG).
|
|
54
|
+
* Avoids Math.random() so tests are reproducible.
|
|
55
|
+
*/
|
|
56
|
+
function createRng(seed: number) {
|
|
57
|
+
let state = seed;
|
|
58
|
+
return {
|
|
59
|
+
next(): number {
|
|
60
|
+
state = (state * 1664525 + 1013904223) & 0xffffffff;
|
|
61
|
+
return (state >>> 0) / 0xffffffff;
|
|
62
|
+
},
|
|
63
|
+
nextInt(min: number, max: number): number {
|
|
64
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
65
|
+
},
|
|
66
|
+
pick<T>(arr: T[]): T {
|
|
67
|
+
return arr[this.nextInt(0, arr.length - 1)];
|
|
68
|
+
},
|
|
69
|
+
shuffle<T>(arr: T[]): T[] {
|
|
70
|
+
const copy = [...arr];
|
|
71
|
+
for (let i = copy.length - 1; i > 0; i--) {
|
|
72
|
+
const j = this.nextInt(0, i);
|
|
73
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
74
|
+
}
|
|
75
|
+
return copy;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const CONTENT_TEMPLATES = [
|
|
81
|
+
'Remember that {} is important for the project.',
|
|
82
|
+
'Note: {} should be completed by next week.',
|
|
83
|
+
'Meeting with {} to discuss the roadmap.',
|
|
84
|
+
'Recipe for {}: combine ingredients and bake.',
|
|
85
|
+
'Bookmark: {} — useful reference for later.',
|
|
86
|
+
'Idea: {} could improve performance significantly.',
|
|
87
|
+
'Quote from {}: "Knowledge is power."',
|
|
88
|
+
'Event: {} on the calendar for this month.',
|
|
89
|
+
'Todo: {} needs to be done before launch.',
|
|
90
|
+
'Research notes on {}: several key findings.',
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const WORDS = [
|
|
94
|
+
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta',
|
|
95
|
+
'project', 'memory', 'design', 'system', 'module', 'feature', 'release',
|
|
96
|
+
'task', 'goal', 'metric', 'api', 'schema', 'service', 'client', 'server',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const CONTENT_TYPES = ['note', 'todo', 'bookmark', 'idea', 'recipe', 'event', 'reference'];
|
|
100
|
+
|
|
101
|
+
const TAG_POOL = [
|
|
102
|
+
'important', 'work', 'personal', 'cooking', 'tech', 'health',
|
|
103
|
+
'books', 'travel', 'family', 'finance', 'learning', 'creative',
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate a single synthetic memory
|
|
108
|
+
*/
|
|
109
|
+
export function generateMemory(index: number, options: GenerateOptions, rng: ReturnType<typeof createRng>): SyntheticMemory {
|
|
110
|
+
const userId = options.userId ?? 'perf-test-user';
|
|
111
|
+
const spaces = options.spaces ?? SUPPORTED_SPACES.slice(0, 3);
|
|
112
|
+
const groups = options.groups ?? [];
|
|
113
|
+
const publishProb = options.publishProbability ?? 0.3;
|
|
114
|
+
const baseDate = options.baseDate ?? '2026-01-01T00:00:00Z';
|
|
115
|
+
|
|
116
|
+
const baseTime = new Date(baseDate).getTime();
|
|
117
|
+
const createdAt = new Date(baseTime + index * 60_000).toISOString(); // 1 minute apart
|
|
118
|
+
|
|
119
|
+
const word = rng.pick(WORDS);
|
|
120
|
+
const template = rng.pick(CONTENT_TEMPLATES);
|
|
121
|
+
const content = template.replace('{}', word);
|
|
122
|
+
|
|
123
|
+
const tagCount = rng.nextInt(0, 3);
|
|
124
|
+
const shuffledTags = rng.shuffle(TAG_POOL);
|
|
125
|
+
const tags = shuffledTags.slice(0, tagCount);
|
|
126
|
+
|
|
127
|
+
// Randomly assign to spaces based on publishProbability
|
|
128
|
+
const assignedSpaceIds: string[] = [];
|
|
129
|
+
for (const spaceId of spaces) {
|
|
130
|
+
if (rng.next() < publishProb) {
|
|
131
|
+
assignedSpaceIds.push(spaceId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Randomly assign to groups
|
|
136
|
+
const assignedGroupIds: string[] = [];
|
|
137
|
+
for (const groupId of groups) {
|
|
138
|
+
if (rng.next() < publishProb) {
|
|
139
|
+
assignedGroupIds.push(groupId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
id: `${userId}-mem-${String(index).padStart(8, '0')}`,
|
|
145
|
+
user_id: userId,
|
|
146
|
+
doc_type: 'memory',
|
|
147
|
+
content,
|
|
148
|
+
title: `Memory ${index}: ${word}`,
|
|
149
|
+
type: rng.pick(CONTENT_TYPES),
|
|
150
|
+
weight: Math.round(rng.next() * 100) / 100,
|
|
151
|
+
trust: Math.round(rng.next() * 100) / 100,
|
|
152
|
+
confidence: 1.0,
|
|
153
|
+
tags,
|
|
154
|
+
space_ids: assignedSpaceIds,
|
|
155
|
+
group_ids: assignedGroupIds,
|
|
156
|
+
created_at: createdAt,
|
|
157
|
+
updated_at: createdAt,
|
|
158
|
+
version: 1,
|
|
159
|
+
access_count: 0,
|
|
160
|
+
deleted_at: null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate a batch of synthetic memories.
|
|
166
|
+
*
|
|
167
|
+
* Uses a seeded RNG for reproducible results across test runs.
|
|
168
|
+
*
|
|
169
|
+
* @param options - Generation options
|
|
170
|
+
* @param seed - RNG seed (default: 42)
|
|
171
|
+
* @returns Array of synthetic memory objects
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* // Generate 1K memories for a single user
|
|
175
|
+
* const memories = generateMemories({ count: 1000 });
|
|
176
|
+
*
|
|
177
|
+
* // Generate 10K with custom spaces
|
|
178
|
+
* const memories = generateMemories({
|
|
179
|
+
* count: 10_000,
|
|
180
|
+
* userId: 'user-123',
|
|
181
|
+
* spaces: ['the_void', 'dogs'],
|
|
182
|
+
* publishProbability: 0.5,
|
|
183
|
+
* });
|
|
184
|
+
*/
|
|
185
|
+
export function generateMemories(options: GenerateOptions, seed = 42): SyntheticMemory[] {
|
|
186
|
+
const rng = createRng(seed);
|
|
187
|
+
const memories: SyntheticMemory[] = [];
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < options.count; i++) {
|
|
190
|
+
memories.push(generateMemory(i, options, rng));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return memories;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Filter generated memories to only those published to a given space.
|
|
198
|
+
* Mirrors the Weaviate containsAny filter used in search-space.
|
|
199
|
+
*/
|
|
200
|
+
export function filterBySpace(memories: SyntheticMemory[], spaceId: string): SyntheticMemory[] {
|
|
201
|
+
return memories.filter(m => m.space_ids.includes(spaceId));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Filter generated memories to only those published to a given group.
|
|
206
|
+
*/
|
|
207
|
+
export function filterByGroup(memories: SyntheticMemory[], groupId: string): SyntheticMemory[] {
|
|
208
|
+
return memories.filter(m => m.group_ids.includes(groupId));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get publication statistics for a generated dataset.
|
|
213
|
+
* Useful for understanding expected query result counts before running benchmarks.
|
|
214
|
+
*/
|
|
215
|
+
export function getDatasetStats(memories: SyntheticMemory[]): {
|
|
216
|
+
total: number;
|
|
217
|
+
publishedToAnySpace: number;
|
|
218
|
+
publishedToAnyGroup: number;
|
|
219
|
+
unpublished: number;
|
|
220
|
+
bySpace: Record<string, number>;
|
|
221
|
+
byGroup: Record<string, number>;
|
|
222
|
+
avgTagsPerMemory: number;
|
|
223
|
+
} {
|
|
224
|
+
const bySpace: Record<string, number> = {};
|
|
225
|
+
const byGroup: Record<string, number> = {};
|
|
226
|
+
let publishedToAnySpace = 0;
|
|
227
|
+
let publishedToAnyGroup = 0;
|
|
228
|
+
let totalTags = 0;
|
|
229
|
+
|
|
230
|
+
for (const mem of memories) {
|
|
231
|
+
if (mem.space_ids.length > 0) publishedToAnySpace++;
|
|
232
|
+
if (mem.group_ids.length > 0) publishedToAnyGroup++;
|
|
233
|
+
totalTags += mem.tags.length;
|
|
234
|
+
|
|
235
|
+
for (const spaceId of mem.space_ids) {
|
|
236
|
+
bySpace[spaceId] = (bySpace[spaceId] ?? 0) + 1;
|
|
237
|
+
}
|
|
238
|
+
for (const groupId of mem.group_ids) {
|
|
239
|
+
byGroup[groupId] = (byGroup[groupId] ?? 0) + 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
total: memories.length,
|
|
245
|
+
publishedToAnySpace,
|
|
246
|
+
publishedToAnyGroup,
|
|
247
|
+
unpublished: memories.filter(m => m.space_ids.length === 0 && m.group_ids.length === 0).length,
|
|
248
|
+
bySpace,
|
|
249
|
+
byGroup,
|
|
250
|
+
avgTagsPerMemory: memories.length > 0 ? Math.round((totalTags / memories.length) * 100) / 100 : 0,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Timing utility for measuring operation duration.
|
|
256
|
+
* Returns elapsed milliseconds.
|
|
257
|
+
*/
|
|
258
|
+
export async function measureMs<T>(fn: () => Promise<T>): Promise<{ result: T; elapsedMs: number }> {
|
|
259
|
+
const start = performance.now();
|
|
260
|
+
const result = await fn();
|
|
261
|
+
const elapsedMs = Math.round((performance.now() - start) * 100) / 100;
|
|
262
|
+
return { result, elapsedMs };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Run a function N times and return timing statistics.
|
|
267
|
+
*/
|
|
268
|
+
export async function benchmark<T>(
|
|
269
|
+
fn: () => Promise<T>,
|
|
270
|
+
iterations: number
|
|
271
|
+
): Promise<{ min: number; max: number; avg: number; p95: number; results: T[] }> {
|
|
272
|
+
const times: number[] = [];
|
|
273
|
+
const results: T[] = [];
|
|
274
|
+
|
|
275
|
+
for (let i = 0; i < iterations; i++) {
|
|
276
|
+
const { result, elapsedMs } = await measureMs(fn);
|
|
277
|
+
times.push(elapsedMs);
|
|
278
|
+
results.push(result);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
times.sort((a, b) => a - b);
|
|
282
|
+
const avg = times.reduce((s, t) => s + t, 0) / times.length;
|
|
283
|
+
const p95Index = Math.floor(times.length * 0.95);
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
min: times[0],
|
|
287
|
+
max: times[times.length - 1],
|
|
288
|
+
avg: Math.round(avg * 100) / 100,
|
|
289
|
+
p95: times[p95Index],
|
|
290
|
+
results,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -68,11 +68,11 @@ function buildDocTypeFilters(
|
|
|
68
68
|
if (docType === 'memory' && filters?.types && filters.types.length > 0) {
|
|
69
69
|
if (filters.types.length === 1) {
|
|
70
70
|
filterList.push(
|
|
71
|
-
collection.filter.byProperty('
|
|
71
|
+
collection.filter.byProperty('content_type').equal(filters.types[0])
|
|
72
72
|
);
|
|
73
73
|
} else {
|
|
74
74
|
filterList.push(
|
|
75
|
-
collection.filter.byProperty('
|
|
75
|
+
collection.filter.byProperty('content_type').containsAny(filters.types)
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -94,14 +94,14 @@ function buildDocTypeFilters(
|
|
|
94
94
|
// Trust filter (minimum) - applies to both
|
|
95
95
|
if (filters?.trust_min !== undefined) {
|
|
96
96
|
filterList.push(
|
|
97
|
-
collection.filter.byProperty('
|
|
97
|
+
collection.filter.byProperty('trust_score').greaterThanOrEqual(filters.trust_min)
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Trust filter (maximum)
|
|
102
102
|
if (filters?.trust_max !== undefined) {
|
|
103
103
|
filterList.push(
|
|
104
|
-
collection.filter.byProperty('
|
|
104
|
+
collection.filter.byProperty('trust_score').lessThanOrEqual(filters.trust_max)
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
|