@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.
Files changed (208) hide show
  1. package/AGENT.md +296 -250
  2. package/CHANGELOG.md +358 -0
  3. package/README.md +68 -45
  4. package/agent/commands/acp.clarification-create.md +382 -0
  5. package/agent/commands/acp.project-info.md +309 -0
  6. package/agent/commands/acp.project-remove.md +379 -0
  7. package/agent/commands/acp.project-update.md +296 -0
  8. package/agent/commands/acp.task-create.md +17 -9
  9. package/agent/commands/git.commit.md +13 -1
  10. package/agent/design/comment-memory-type.md +2 -2
  11. package/agent/design/local.collaborative-memory-sync.md +265 -0
  12. package/agent/design/local.content-flags.md +210 -0
  13. package/agent/design/local.ghost-persona-system.md +273 -0
  14. package/agent/design/local.group-acl-integration.md +338 -0
  15. package/agent/design/local.memory-acl-schema.md +352 -0
  16. package/agent/design/local.memory-collection-pattern-v2.md +348 -0
  17. package/agent/design/local.moderation-and-space-config.md +257 -0
  18. package/agent/design/local.v2-api-reference.md +621 -0
  19. package/agent/design/local.v2-migration-guide.md +191 -0
  20. package/agent/design/local.v2-usage-examples.md +265 -0
  21. package/agent/design/permissions-storage-architecture.md +11 -3
  22. package/agent/design/trust-escalation-prevention.md +9 -2
  23. package/agent/design/trust-system-implementation.md +12 -3
  24. package/agent/milestones/milestone-14-memory-collection-v2.md +182 -0
  25. package/agent/milestones/milestone-15-moderation-space-config.md +126 -0
  26. package/agent/progress.yaml +628 -49
  27. package/agent/scripts/acp.common.sh +2 -0
  28. package/agent/scripts/acp.install.sh +11 -1
  29. package/agent/scripts/acp.package-install-optimized.sh +454 -0
  30. package/agent/scripts/acp.package-install.sh +247 -300
  31. package/agent/scripts/acp.project-info.sh +218 -0
  32. package/agent/scripts/acp.project-remove.sh +302 -0
  33. package/agent/scripts/acp.project-update.sh +296 -0
  34. package/agent/scripts/acp.yaml-parser.sh +128 -10
  35. package/agent/tasks/milestone-14-memory-collection-v2/task-165-core-infrastructure-setup.md +171 -0
  36. package/agent/tasks/milestone-14-memory-collection-v2/task-166-update-remember-publish.md +191 -0
  37. package/agent/tasks/milestone-14-memory-collection-v2/task-167-update-remember-retract.md +186 -0
  38. package/agent/tasks/milestone-14-memory-collection-v2/task-168-implement-remember-revise.md +184 -0
  39. package/agent/tasks/milestone-14-memory-collection-v2/task-169-update-remember-search-space.md +179 -0
  40. package/agent/tasks/milestone-14-memory-collection-v2/task-170-update-remember-create-update.md +139 -0
  41. package/agent/tasks/milestone-14-memory-collection-v2/task-172-performance-testing-optimization.md +161 -0
  42. package/agent/tasks/milestone-14-memory-collection-v2/task-173-documentation-examples.md +258 -0
  43. package/agent/tasks/milestone-15-moderation-space-config/task-174-add-moderation-schema-fields.md +57 -0
  44. package/agent/tasks/milestone-15-moderation-space-config/task-175-create-space-config-service.md +64 -0
  45. package/agent/tasks/milestone-15-moderation-space-config/task-176-wire-moderation-publish-flow.md +45 -0
  46. package/agent/tasks/milestone-15-moderation-space-config/task-177-add-moderation-search-filters.md +70 -0
  47. package/agent/tasks/milestone-15-moderation-space-config/task-178-create-remember-moderate-tool.md +69 -0
  48. package/agent/tasks/milestone-15-moderation-space-config/task-179-documentation-integration-tests.md +58 -0
  49. package/agent/tasks/milestone-16-ghost-system/task-187-ghost-config-firestore.md +41 -0
  50. package/agent/tasks/milestone-16-ghost-system/task-188-trust-filter-integration.md +44 -0
  51. package/agent/tasks/milestone-16-ghost-system/task-189-ghost-memory-filtering.md +43 -0
  52. package/agent/tasks/milestone-16-ghost-system/task-190-ghost-config-tools.md +45 -0
  53. package/agent/tasks/milestone-16-ghost-system/task-191-escalation-firestore.md +38 -0
  54. package/agent/tasks/milestone-16-ghost-system/task-192-documentation-verification.md +39 -0
  55. package/agent/tasks/milestone-7-trust-permissions/task-180-access-result-permission-types.md +69 -0
  56. package/agent/tasks/milestone-7-trust-permissions/task-181-firestore-permissions-access-logs.md +56 -0
  57. package/agent/tasks/milestone-7-trust-permissions/task-182-trust-enforcement-service.md +68 -0
  58. package/agent/tasks/milestone-7-trust-permissions/task-183-access-control-service.md +70 -0
  59. package/agent/tasks/milestone-7-trust-permissions/task-184-permission-tools.md +79 -0
  60. package/agent/tasks/milestone-7-trust-permissions/task-185-wire-trust-into-search-query.md +55 -0
  61. package/agent/tasks/milestone-7-trust-permissions/task-186-documentation-verification.md +56 -0
  62. package/agent/tasks/task-76-fix-indexnullstate-schema-bug.md +197 -0
  63. package/dist/collections/composite-ids.d.ts +106 -0
  64. package/dist/collections/core-infrastructure.spec.d.ts +11 -0
  65. package/dist/collections/dot-notation.d.ts +106 -0
  66. package/dist/collections/tracking-arrays.d.ts +176 -0
  67. package/dist/constants/content-types.d.ts +1 -0
  68. package/dist/schema/v2-collections-comments.spec.d.ts +8 -0
  69. package/dist/schema/v2-collections.d.ts +210 -0
  70. package/dist/server-factory.d.ts +15 -0
  71. package/dist/server-factory.js +2798 -1029
  72. package/dist/server.js +2526 -1012
  73. package/dist/services/access-control.d.ts +103 -0
  74. package/dist/services/access-control.spec.d.ts +2 -0
  75. package/dist/services/credentials-provider.d.ts +24 -0
  76. package/dist/services/credentials-provider.spec.d.ts +2 -0
  77. package/dist/services/escalation.service.d.ts +22 -0
  78. package/dist/services/escalation.service.spec.d.ts +2 -0
  79. package/dist/services/ghost-config.service.d.ts +55 -0
  80. package/dist/services/ghost-config.service.spec.d.ts +2 -0
  81. package/dist/services/space-config.service.d.ts +23 -0
  82. package/dist/services/space-config.service.spec.d.ts +2 -0
  83. package/dist/services/trust-enforcement.d.ts +83 -0
  84. package/dist/services/trust-enforcement.spec.d.ts +2 -0
  85. package/dist/services/trust-validator.d.ts +43 -0
  86. package/dist/services/trust-validator.spec.d.ts +2 -0
  87. package/dist/tools/confirm-publish-moderation.spec.d.ts +8 -0
  88. package/dist/tools/confirm.d.ts +8 -1
  89. package/dist/tools/create-memory.d.ts +2 -1
  90. package/dist/tools/create-memory.spec.d.ts +10 -0
  91. package/dist/tools/create-relationship.d.ts +2 -1
  92. package/dist/tools/delete-memory.d.ts +2 -1
  93. package/dist/tools/delete-relationship.d.ts +2 -1
  94. package/dist/tools/deny.d.ts +2 -1
  95. package/dist/tools/find-similar.d.ts +2 -1
  96. package/dist/tools/get-preferences.d.ts +2 -1
  97. package/dist/tools/ghost-config.d.ts +27 -0
  98. package/dist/tools/ghost-config.spec.d.ts +2 -0
  99. package/dist/tools/moderate.d.ts +20 -0
  100. package/dist/tools/moderate.spec.d.ts +5 -0
  101. package/dist/tools/publish.d.ts +11 -3
  102. package/dist/tools/query-memory.d.ts +3 -1
  103. package/dist/tools/query-space.d.ts +4 -1
  104. package/dist/tools/retract.d.ts +29 -0
  105. package/dist/tools/revise.d.ts +45 -0
  106. package/dist/tools/revise.spec.d.ts +8 -0
  107. package/dist/tools/search-memory.d.ts +2 -1
  108. package/dist/tools/search-relationship.d.ts +2 -1
  109. package/dist/tools/search-space.d.ts +25 -5
  110. package/dist/tools/search-space.spec.d.ts +9 -0
  111. package/dist/tools/set-preference.d.ts +2 -1
  112. package/dist/tools/update-memory.d.ts +2 -1
  113. package/dist/tools/update-relationship.d.ts +2 -1
  114. package/dist/types/access-result.d.ts +48 -0
  115. package/dist/types/access-result.spec.d.ts +2 -0
  116. package/dist/types/auth.d.ts +46 -0
  117. package/dist/types/ghost-config.d.ts +36 -0
  118. package/dist/types/memory.d.ts +3 -1
  119. package/dist/types/preferences.d.ts +1 -1
  120. package/dist/utils/auth-helpers.d.ts +14 -0
  121. package/dist/utils/auth-helpers.spec.d.ts +2 -0
  122. package/dist/utils/test-data-generator.d.ts +124 -0
  123. package/dist/utils/test-data-generator.spec.d.ts +12 -0
  124. package/dist/v2-performance.e2e.d.ts +17 -0
  125. package/dist/v2-smoke.e2e.d.ts +14 -0
  126. package/dist/weaviate/client.d.ts +5 -8
  127. package/dist/weaviate/space-schema.d.ts +2 -2
  128. package/docs/performance/v2-benchmarks.md +80 -0
  129. package/jest.e2e.config.js +14 -3
  130. package/package.json +1 -1
  131. package/scripts/.collection-recreation-state.yaml +16 -0
  132. package/scripts/.gitkeep +5 -0
  133. package/scripts/README-collection-recreation.md +224 -0
  134. package/scripts/README.md +51 -0
  135. package/scripts/backup-collections.ts +543 -0
  136. package/scripts/delete-collection.ts +137 -0
  137. package/scripts/migrate-recreate-collections.ts +578 -0
  138. package/scripts/migrate-v1-to-v2.ts +1094 -0
  139. package/scripts/package-lock.json +1113 -0
  140. package/scripts/package.json +27 -0
  141. package/src/collections/composite-ids.ts +193 -0
  142. package/src/collections/core-infrastructure.spec.ts +353 -0
  143. package/src/collections/dot-notation.ts +212 -0
  144. package/src/collections/tracking-arrays.ts +298 -0
  145. package/src/constants/content-types.ts +20 -0
  146. package/src/schema/v2-collections-comments.spec.ts +141 -0
  147. package/src/schema/v2-collections.ts +433 -0
  148. package/src/server-factory.ts +89 -20
  149. package/src/server.ts +45 -17
  150. package/src/services/access-control.spec.ts +383 -0
  151. package/src/services/access-control.ts +291 -0
  152. package/src/services/credentials-provider.spec.ts +22 -0
  153. package/src/services/credentials-provider.ts +34 -0
  154. package/src/services/escalation.service.spec.ts +183 -0
  155. package/src/services/escalation.service.ts +150 -0
  156. package/src/services/ghost-config.service.spec.ts +339 -0
  157. package/src/services/ghost-config.service.ts +219 -0
  158. package/src/services/space-config.service.spec.ts +102 -0
  159. package/src/services/space-config.service.ts +79 -0
  160. package/src/services/trust-enforcement.spec.ts +309 -0
  161. package/src/services/trust-enforcement.ts +197 -0
  162. package/src/services/trust-validator.spec.ts +108 -0
  163. package/src/services/trust-validator.ts +105 -0
  164. package/src/tools/confirm-publish-moderation.spec.ts +240 -0
  165. package/src/tools/confirm.ts +869 -135
  166. package/src/tools/create-memory.spec.ts +126 -0
  167. package/src/tools/create-memory.ts +20 -27
  168. package/src/tools/create-relationship.ts +17 -8
  169. package/src/tools/delete-memory.ts +13 -6
  170. package/src/tools/delete-relationship.ts +15 -6
  171. package/src/tools/deny.ts +8 -1
  172. package/src/tools/find-similar.ts +21 -8
  173. package/src/tools/get-preferences.ts +10 -1
  174. package/src/tools/ghost-config.spec.ts +180 -0
  175. package/src/tools/ghost-config.ts +230 -0
  176. package/src/tools/moderate.spec.ts +277 -0
  177. package/src/tools/moderate.ts +219 -0
  178. package/src/tools/publish.ts +99 -41
  179. package/src/tools/query-memory.ts +28 -6
  180. package/src/tools/query-space.ts +39 -4
  181. package/src/tools/retract.ts +292 -0
  182. package/src/tools/revise.spec.ts +146 -0
  183. package/src/tools/revise.ts +283 -0
  184. package/src/tools/search-memory.ts +30 -7
  185. package/src/tools/search-relationship.ts +11 -2
  186. package/src/tools/search-space.spec.ts +341 -0
  187. package/src/tools/search-space.ts +323 -99
  188. package/src/tools/set-preference.ts +10 -1
  189. package/src/tools/update-memory.ts +16 -5
  190. package/src/tools/update-relationship.ts +10 -1
  191. package/src/types/access-result.spec.ts +193 -0
  192. package/src/types/access-result.ts +62 -0
  193. package/src/types/auth.ts +52 -0
  194. package/src/types/ghost-config.ts +46 -0
  195. package/src/types/memory.ts +9 -1
  196. package/src/types/preferences.ts +2 -2
  197. package/src/utils/auth-helpers.spec.ts +75 -0
  198. package/src/utils/auth-helpers.ts +25 -0
  199. package/src/utils/test-data-generator.spec.ts +317 -0
  200. package/src/utils/test-data-generator.ts +292 -0
  201. package/src/utils/weaviate-filters.ts +4 -4
  202. package/src/v2-performance.e2e.ts +173 -0
  203. package/src/v2-smoke.e2e.ts +401 -0
  204. package/src/weaviate/client.spec.ts +5 -5
  205. package/src/weaviate/client.ts +51 -36
  206. package/src/weaviate/schema.ts +11 -256
  207. package/src/weaviate/space-schema.spec.ts +24 -24
  208. package/src/weaviate/space-schema.ts +18 -6
@@ -1,25 +1,36 @@
1
1
  /**
2
- * remember_search_space tool
3
- *
4
- * Search shared spaces to discover memories from other users.
5
- * Similar to remember_search_memory but searches space collections.
2
+ * remember_search_space tool (Memory Collection Pattern v2)
3
+ *
4
+ * Search shared spaces and/or groups to discover memories from other users.
5
+ * Queries Memory_spaces_public (for space searches) and Memory_groups_{groupId}
6
+ * (for group searches), then merges and deduplicates results.
6
7
  */
7
8
 
8
9
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
9
10
  import { Filters } from 'weaviate-client';
10
11
  import { getWeaviateClient } from '../weaviate/client.js';
11
- import { ensurePublicCollection, isValidSpaceId } from '../weaviate/space-schema.js';
12
+ import { isValidSpaceId } from '../weaviate/space-schema.js';
12
13
  import { SUPPORTED_SPACES } from '../types/space-memory.js';
13
14
  import { handleToolError } from '../utils/error-handler.js';
14
- import type { SearchFilters } from '../types/memory.js';
15
15
  import { createDebugLogger } from '../utils/debug.js';
16
+ import { CollectionType, getCollectionName } from '../collections/dot-notation.js';
17
+ import { logger } from '../utils/logger.js';
18
+ import type { AuthContext } from '../types/auth.js';
19
+ import { canModerate, canModerateAny } from '../utils/auth-helpers.js';
16
20
 
17
21
  /**
18
22
  * Tool definition for remember_search_space
19
23
  */
20
24
  export const searchSpaceTool: Tool = {
21
25
  name: 'remember_search_space',
22
- description: `Search one or more shared spaces to discover thoughts, ideas, and memories. By default, excludes comments to keep discovery clean. Set include_comments: true to include threaded discussions. Can search multiple spaces in a single query.
26
+ description: `Search shared spaces and/or groups to discover memories from other users.
27
+
28
+ Destinations:
29
+ - Spaces: Public shared areas (e.g., "the_void", "dogs") — queries Memory_spaces_public filtered by space_ids
30
+ - Groups: Private group collections — queries Memory_groups_{groupId} for each specified group
31
+ - Neither specified: Searches all public memories across Memory_spaces_public
32
+
33
+ Results from multiple sources are merged and deduplicated by composite ID, sorted by relevance.
23
34
 
24
35
  ⚠️ **CRITICAL - CONTENT TYPE FILTERING**: Do NOT add content_type filter unless the user explicitly requests filtering by type.
25
36
  - ✅ CORRECT: User says "search The Void for hiking" → { spaces: ["the_void"], query: "hiking" }
@@ -32,7 +43,7 @@ Let the search algorithm find ALL relevant memories regardless of type unless ex
32
43
  properties: {
33
44
  query: {
34
45
  type: 'string',
35
- description: 'Search query (semantic + keyword hybrid)',
46
+ description: 'Search query',
36
47
  },
37
48
  spaces: {
38
49
  type: 'array',
@@ -40,9 +51,20 @@ Let the search algorithm find ALL relevant memories regardless of type unless ex
40
51
  type: 'string',
41
52
  enum: SUPPORTED_SPACES,
42
53
  },
43
- description: 'Spaces to search (e.g., ["the_void", "dogs"]). Can search multiple spaces at once.',
54
+ description: 'Spaces to search (e.g., ["the_void", "dogs"]). Omit to search all public spaces.',
55
+ minItems: 1,
56
+ },
57
+ groups: {
58
+ type: 'array',
59
+ items: { type: 'string' },
60
+ description: 'Group IDs to search (e.g., ["group-123"]). Searches Memory_groups_{groupId} for each.',
44
61
  minItems: 1,
45
- default: ['the_void'],
62
+ },
63
+ search_type: {
64
+ type: 'string',
65
+ enum: ['hybrid', 'bm25', 'semantic'],
66
+ description: 'Search algorithm: "hybrid" (default), "bm25" (keyword only), or "semantic" (vector only)',
67
+ default: 'hybrid',
46
68
  },
47
69
  content_type: {
48
70
  type: 'string',
@@ -73,6 +95,12 @@ Let the search algorithm find ALL relevant memories regardless of type unless ex
73
95
  type: 'string',
74
96
  description: 'Filter memories created before this date (ISO 8601)',
75
97
  },
98
+ moderation_filter: {
99
+ type: 'string',
100
+ enum: ['approved', 'pending', 'rejected', 'removed', 'all'],
101
+ description: 'Filter by moderation status. Default: "approved" (only shows approved/unmoderated). Non-approved filters require moderator permissions.',
102
+ default: 'approved',
103
+ },
76
104
  include_comments: {
77
105
  type: 'boolean',
78
106
  description: 'Include comments in search results (default: false)',
@@ -89,30 +117,145 @@ Let the search algorithm find ALL relevant memories regardless of type unless ex
89
117
  description: 'Offset for pagination',
90
118
  },
91
119
  },
92
- required: ['query', 'spaces'],
120
+ required: ['query'],
93
121
  },
94
122
  };
95
123
 
124
+ export type ModerationFilter = 'approved' | 'pending' | 'rejected' | 'removed' | 'all';
125
+
96
126
  interface SearchSpaceArgs {
97
127
  query: string;
98
- spaces: string[];
128
+ spaces?: string[];
129
+ groups?: string[];
130
+ search_type?: 'hybrid' | 'bm25' | 'semantic';
99
131
  content_type?: string;
100
132
  tags?: string[];
101
133
  min_weight?: number;
102
134
  max_weight?: number;
103
135
  date_from?: string;
104
136
  date_to?: string;
137
+ moderation_filter?: ModerationFilter;
105
138
  include_comments?: boolean;
106
139
  limit?: number;
107
140
  offset?: number;
108
141
  }
109
142
 
143
+ /**
144
+ * Build the moderation status filter for a Weaviate collection query.
145
+ *
146
+ * - 'approved' (default): matches approved OR null (backward compat for pre-moderation memories)
147
+ * - 'pending'/'rejected'/'removed': matches that specific status
148
+ * - 'all': no moderation filter applied
149
+ */
150
+ export function buildModerationFilter(collection: any, moderationFilter: ModerationFilter = 'approved'): any | null {
151
+ if (moderationFilter === 'all') {
152
+ return null;
153
+ }
154
+
155
+ if (moderationFilter === 'approved') {
156
+ // Approved OR null (backward compat: existing memories without moderation_status are approved)
157
+ return Filters.or(
158
+ collection.filter.byProperty('moderation_status').equal('approved'),
159
+ collection.filter.byProperty('moderation_status').isNull(true)
160
+ );
161
+ }
162
+
163
+ // Specific non-approved status
164
+ return collection.filter.byProperty('moderation_status').equal(moderationFilter);
165
+ }
166
+
167
+ /**
168
+ * Build base filters applied to all space/group collection queries.
169
+ * Excludes soft-deleted memories and optionally filters by content type, tags, weight, and date.
170
+ * Includes moderation status filter (default: approved/null only).
171
+ */
172
+ export function buildBaseFilters(collection: any, args: SearchSpaceArgs): any[] {
173
+ const filterList: any[] = [];
174
+
175
+ // Exclude soft-deleted memories (requires indexNullState: true on collection)
176
+ filterList.push(collection.filter.byProperty('deleted_at').isNull(true));
177
+
178
+ // Only return memories (not relationships)
179
+ filterList.push(collection.filter.byProperty('doc_type').equal('memory'));
180
+
181
+ // Moderation status filter
182
+ const moderationFilter = buildModerationFilter(collection, args.moderation_filter);
183
+ if (moderationFilter) {
184
+ filterList.push(moderationFilter);
185
+ }
186
+
187
+ // Apply content type filter
188
+ if (args.content_type) {
189
+ filterList.push(collection.filter.byProperty('content_type').equal(args.content_type));
190
+ }
191
+
192
+ // Exclude comments and ghost memories by default (unless content_type is explicitly set)
193
+ if (!args.include_comments && !args.content_type) {
194
+ filterList.push(collection.filter.byProperty('content_type').notEqual('comment'));
195
+ }
196
+ if (!args.content_type) {
197
+ filterList.push(collection.filter.byProperty('content_type').notEqual('ghost'));
198
+ }
199
+
200
+ // Apply tags filter (AND semantics: memory must have ALL specified tags)
201
+ if (args.tags && args.tags.length > 0) {
202
+ args.tags.forEach(tag => {
203
+ filterList.push(collection.filter.byProperty('tags').containsAny([tag]));
204
+ });
205
+ }
206
+
207
+ // Apply weight filters
208
+ if (args.min_weight !== undefined) {
209
+ filterList.push(collection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
210
+ }
211
+ if (args.max_weight !== undefined) {
212
+ filterList.push(collection.filter.byProperty('weight').lessOrEqual(args.max_weight));
213
+ }
214
+
215
+ // Apply date filters (created_at stored as ISO 8601 text, sorts lexicographically)
216
+ if (args.date_from) {
217
+ filterList.push(collection.filter.byProperty('created_at').greaterOrEqual(new Date(args.date_from)));
218
+ }
219
+ if (args.date_to) {
220
+ filterList.push(collection.filter.byProperty('created_at').lessOrEqual(new Date(args.date_to)));
221
+ }
222
+
223
+ return filterList;
224
+ }
225
+
226
+ /**
227
+ * Execute a search against a Weaviate collection using the specified search type.
228
+ */
229
+ async function executeSearch(
230
+ collection: any,
231
+ query: string,
232
+ searchType: 'hybrid' | 'bm25' | 'semantic',
233
+ whereFilter: any,
234
+ limit: number
235
+ ): Promise<any[]> {
236
+ const opts = {
237
+ limit,
238
+ ...(whereFilter && { where: whereFilter }),
239
+ };
240
+
241
+ switch (searchType) {
242
+ case 'bm25':
243
+ return (await collection.query.bm25(query, opts)).objects;
244
+ case 'semantic':
245
+ return (await collection.query.nearText([query], opts)).objects;
246
+ case 'hybrid':
247
+ default:
248
+ return (await collection.query.hybrid(query, opts)).objects;
249
+ }
250
+ }
251
+
110
252
  /**
111
253
  * Handle remember_search_space tool execution
112
254
  */
113
255
  export async function handleSearchSpace(
114
256
  args: SearchSpaceArgs,
115
- userId: string // May be used for private spaces in future
257
+ userId: string,
258
+ authContext?: AuthContext
116
259
  ): Promise<string> {
117
260
  const debug = createDebugLogger({
118
261
  tool: 'remember_search_space',
@@ -123,130 +266,210 @@ export async function handleSearchSpace(
123
266
  try {
124
267
  debug.info('Tool invoked');
125
268
  debug.trace('Arguments', { args });
126
-
127
- // Validate all space IDs
128
- debug.debug('Validating space IDs', { spaces: args.spaces });
129
- const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
130
- if (invalidSpaces.length > 0) {
131
- return JSON.stringify(
132
- {
133
- success: false,
134
- error: 'Invalid space IDs',
135
- message: `Invalid spaces: ${invalidSpaces.join(', ')}. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
136
- context: {
137
- invalid_spaces: invalidSpaces,
138
- provided_spaces: args.spaces,
139
- supported_spaces: SUPPORTED_SPACES,
269
+
270
+ const spaces = args.spaces || [];
271
+ const groups = args.groups || [];
272
+ const searchType = args.search_type || 'hybrid';
273
+ const limit = args.limit || 10;
274
+ const offset = args.offset || 0;
275
+
276
+ // Validate space IDs
277
+ if (spaces.length > 0) {
278
+ const invalidSpaces = spaces.filter(s => !isValidSpaceId(s));
279
+ if (invalidSpaces.length > 0) {
280
+ return JSON.stringify(
281
+ {
282
+ success: false,
283
+ error: 'Invalid space IDs',
284
+ message: `Invalid spaces: ${invalidSpaces.join(', ')}. Supported spaces: ${SUPPORTED_SPACES.join(', ')}`,
285
+ context: {
286
+ invalid_spaces: invalidSpaces,
287
+ provided_spaces: spaces,
288
+ supported_spaces: SUPPORTED_SPACES,
289
+ },
140
290
  },
141
- },
142
- null,
143
- 2
144
- );
291
+ null,
292
+ 2
293
+ );
294
+ }
145
295
  }
146
-
147
- // Validate not empty
148
- if (args.spaces.length === 0) {
149
- return JSON.stringify(
150
- {
151
- success: false,
152
- error: 'Empty spaces array',
153
- message: 'Must specify at least one space to search',
154
- },
155
- null,
156
- 2
157
- );
296
+
297
+ // Validate group IDs
298
+ if (groups.length > 0) {
299
+ const invalidGroups = groups.filter(g => !g || g.includes('.') || g.trim() === '');
300
+ if (invalidGroups.length > 0) {
301
+ return JSON.stringify(
302
+ {
303
+ success: false,
304
+ error: 'Invalid group IDs',
305
+ message: 'Group IDs cannot be empty or contain dots',
306
+ context: { invalid_groups: invalidGroups },
307
+ },
308
+ null,
309
+ 2
310
+ );
311
+ }
312
+ }
313
+
314
+ // Permission check: non-approved moderation filters require can_moderate
315
+ const moderationFilter = args.moderation_filter || 'approved';
316
+ if (moderationFilter !== 'approved') {
317
+ // For group searches: check can_moderate per group
318
+ for (const groupId of groups) {
319
+ if (!canModerate(authContext, groupId)) {
320
+ return JSON.stringify(
321
+ {
322
+ success: false,
323
+ error: 'Permission denied',
324
+ message: `Moderator access required to view ${moderationFilter} memories in group ${groupId}`,
325
+ },
326
+ null,
327
+ 2
328
+ );
329
+ }
330
+ }
331
+ // For space searches: check can_moderate on any group
332
+ if ((spaces.length > 0 || groups.length === 0) && !canModerateAny(authContext)) {
333
+ return JSON.stringify(
334
+ {
335
+ success: false,
336
+ error: 'Permission denied',
337
+ message: `Moderator access required to view ${moderationFilter} memories in spaces`,
338
+ },
339
+ null,
340
+ 2
341
+ );
342
+ }
158
343
  }
159
344
 
160
345
  const weaviateClient = getWeaviateClient();
161
- const publicCollection = await ensurePublicCollection(weaviateClient);
346
+ // Fetch enough results before pagination so we can deduplicate across sources
347
+ const fetchLimit = (limit + offset) * Math.max(1, groups.length + (spaces.length > 0 || groups.length === 0 ? 1 : 0));
348
+ const allObjects: any[] = [];
162
349
 
163
- // Build filters for space search
164
- const filterList: any[] = [];
350
+ logger.info('Starting space/group search', {
351
+ tool: 'remember_search_space',
352
+ userId,
353
+ spaces,
354
+ groups,
355
+ searchType,
356
+ query: args.query,
357
+ });
165
358
 
166
- // Filter by spaces array (memory must be in at least one requested space)
167
- filterList.push(publicCollection.filter.byProperty('spaces').containsAny(args.spaces));
359
+ // --- Space collection search ---
360
+ // Runs when spaces are specified, OR when neither spaces nor groups are specified (all-public)
361
+ if (spaces.length > 0 || groups.length === 0) {
362
+ const spacesCollectionName = getCollectionName(CollectionType.SPACES);
363
+ const spacesCollection = weaviateClient.collections.get(spacesCollectionName);
168
364
 
169
- // Filter by doc_type (memory) - space_memory concept was removed
170
- filterList.push(publicCollection.filter.byProperty('doc_type').equal('memory'));
365
+ const filterList = buildBaseFilters(spacesCollection, args);
171
366
 
172
- // Apply content type filter
173
- if (args.content_type) {
174
- filterList.push(publicCollection.filter.byProperty('type').equal(args.content_type));
175
- }
367
+ // Filter by space_ids array when specific spaces are requested
368
+ if (spaces.length > 0) {
369
+ filterList.push(spacesCollection.filter.byProperty('space_ids').containsAny(spaces));
370
+ }
371
+ // When spaces.length === 0 and groups.length === 0: no space_ids filter → all-public search
176
372
 
177
- // Exclude comments by default (unless explicitly included)
178
- if (!args.include_comments && !args.content_type) {
179
- // Only exclude comments if not filtering by content_type
180
- // (if content_type is set, user has explicit control)
181
- filterList.push(publicCollection.filter.byProperty('type').notEqual('comment'));
182
- }
373
+ const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
183
374
 
184
- // Apply tags filter
185
- if (args.tags && args.tags.length > 0) {
186
- args.tags.forEach(tag => {
187
- filterList.push(publicCollection.filter.byProperty('tags').containsAny([tag]));
375
+ debug.debug('Searching Memory_spaces_public', {
376
+ filterCount: filterList.length,
377
+ spaces,
378
+ allPublic: spaces.length === 0,
379
+ searchType,
188
380
  });
189
- }
190
381
 
191
- // Apply weight filters
192
- if (args.min_weight !== undefined) {
193
- filterList.push(publicCollection.filter.byProperty('weight').greaterOrEqual(args.min_weight));
194
- }
382
+ const spaceObjects = await debug.time('Space collection search', async () => {
383
+ return await executeSearch(spacesCollection, args.query, searchType, whereFilter, fetchLimit);
384
+ });
195
385
 
196
- if (args.max_weight !== undefined) {
197
- filterList.push(publicCollection.filter.byProperty('weight').lessOrEqual(args.max_weight));
198
- }
386
+ allObjects.push(...spaceObjects);
199
387
 
200
- // Apply date filters (convert ISO strings to Date objects)
201
- if (args.date_from) {
202
- filterList.push(publicCollection.filter.byProperty('created_at').greaterOrEqual(new Date(args.date_from)));
388
+ logger.info('Space collection search complete', {
389
+ tool: 'remember_search_space',
390
+ collectionName: spacesCollectionName,
391
+ resultCount: spaceObjects.length,
392
+ });
203
393
  }
204
394
 
205
- if (args.date_to) {
206
- filterList.push(publicCollection.filter.byProperty('created_at').lessOrEqual(new Date(args.date_to)));
207
- }
395
+ // --- Group collection searches ---
396
+ for (const groupId of groups) {
397
+ const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
208
398
 
209
- const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
399
+ // Skip if the group collection doesn't exist yet
400
+ const exists = await weaviateClient.collections.exists(groupCollectionName);
401
+ if (!exists) {
402
+ debug.warn('Group collection not found, skipping', { groupId, groupCollectionName });
403
+ continue;
404
+ }
210
405
 
211
- debug.debug('Executing hybrid search', {
212
- query: args.query,
213
- filterCount: filterList.length,
214
- limit: args.limit || 10,
215
- offset: args.offset || 0,
216
- });
406
+ const groupCollection = weaviateClient.collections.get(groupCollectionName);
407
+ const filterList = buildBaseFilters(groupCollection, args);
408
+ const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
409
+
410
+ debug.debug('Searching group collection', {
411
+ groupId,
412
+ groupCollectionName,
413
+ filterCount: filterList.length,
414
+ searchType,
415
+ });
217
416
 
218
- // Execute hybrid search
219
- const searchResults = await debug.time('Hybrid search query', async () => {
220
- return await publicCollection.query.hybrid(args.query, {
221
- limit: args.limit || 10,
222
- offset: args.offset || 0,
223
- ...(whereFilter && { where: whereFilter }),
417
+ const groupObjects = await debug.time(`Group collection search: ${groupId}`, async () => {
418
+ return await executeSearch(groupCollection, args.query, searchType, whereFilter, fetchLimit);
224
419
  });
420
+
421
+ allObjects.push(...groupObjects);
422
+
423
+ logger.info('Group collection search complete', {
424
+ tool: 'remember_search_space',
425
+ groupId,
426
+ collectionName: groupCollectionName,
427
+ resultCount: groupObjects.length,
428
+ });
429
+ }
430
+
431
+ // --- Deduplicate by UUID (composite ID) ---
432
+ const seen = new Set<string>();
433
+ const deduplicated = allObjects.filter(obj => {
434
+ if (seen.has(obj.uuid)) return false;
435
+ seen.add(obj.uuid);
436
+ return true;
225
437
  });
226
-
227
- debug.debug('Search completed', {
228
- resultCount: searchResults.objects.length,
438
+
439
+ // --- Sort by relevance score descending ---
440
+ deduplicated.sort((a, b) => {
441
+ const scoreA = a.metadata?.score ?? 0;
442
+ const scoreB = b.metadata?.score ?? 0;
443
+ return scoreB - scoreA;
229
444
  });
230
445
 
446
+ // --- Apply pagination ---
447
+ const paginated = deduplicated.slice(offset, offset + limit);
448
+
231
449
  // Format results
232
- const memories = searchResults.objects.map((obj) => ({
450
+ const memories = paginated.map(obj => ({
233
451
  id: obj.uuid,
234
452
  ...obj.properties,
235
453
  _score: obj.metadata?.score,
236
454
  }));
237
455
 
456
+ const isAllPublic = spaces.length === 0 && groups.length === 0;
457
+
238
458
  const result = {
239
- spaces_searched: args.spaces,
459
+ spaces_searched: isAllPublic ? 'all_public' : spaces,
460
+ groups_searched: groups,
240
461
  query: args.query,
462
+ search_type: searchType,
241
463
  memories,
242
464
  total: memories.length,
243
- offset: args.offset || 0,
244
- limit: args.limit || 10,
465
+ offset,
466
+ limit,
245
467
  };
246
468
 
247
469
  debug.info('Tool completed successfully', {
248
470
  resultCount: memories.length,
249
- spaces: args.spaces,
471
+ spaces,
472
+ groups,
250
473
  });
251
474
 
252
475
  return JSON.stringify(result, null, 2);
@@ -259,6 +482,7 @@ export async function handleSearchSpace(
259
482
  toolName: 'remember_search_space',
260
483
  operation: 'search spaces',
261
484
  spaces: args.spaces,
485
+ groups: args.groups,
262
486
  query: args.query,
263
487
  });
264
488
  }
@@ -6,11 +6,13 @@
6
6
  import { PreferencesDatabaseService } from '../services/preferences-database.service.js';
7
7
  import { logger } from '../utils/logger.js';
8
8
  import { handleToolError } from '../utils/error-handler.js';
9
+ import { createDebugLogger } from '../utils/debug.js';
9
10
  import {
10
11
  UserPreferences,
11
12
  getPreferenceDescription,
12
13
  getPreferencesSchema,
13
14
  } from '../types/preferences.js';
15
+ import type { AuthContext } from '../types/auth.js';
14
16
 
15
17
  /**
16
18
  * Tool definition for remember_set_preference
@@ -115,9 +117,15 @@ function formatPreferenceChangeMessage(updates: Partial<UserPreferences>): strin
115
117
  */
116
118
  export async function handleSetPreference(
117
119
  args: SetPreferenceArgs,
118
- userId: string
120
+ userId: string,
121
+ authContext?: AuthContext
119
122
  ): Promise<string> {
123
+ const debug = createDebugLogger({ tool: 'remember_set_preference', userId, operation: 'set preference' });
124
+
120
125
  try {
126
+ debug.info('Tool invoked');
127
+ debug.trace('Arguments', { args });
128
+
121
129
  const { preferences } = args;
122
130
 
123
131
  logger.info('Setting preferences', { userId, updates: Object.keys(preferences) });
@@ -140,6 +148,7 @@ export async function handleSetPreference(
140
148
 
141
149
  return JSON.stringify(result, null, 2);
142
150
  } catch (error) {
151
+ debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
143
152
  handleToolError(error, {
144
153
  toolName: 'remember_set_preference',
145
154
  operation: 'set preference',
@@ -9,6 +9,8 @@ import { fetchMemoryWithAllProperties } from '../weaviate/client.js';
9
9
  import { logger } from '../utils/logger.js';
10
10
  import { handleToolError, withErrorHandling } from '../utils/error-handler.js';
11
11
  import { isValidContentType } from '../constants/content-types.js';
12
+ import { createDebugLogger } from '../utils/debug.js';
13
+ import type { AuthContext } from '../types/auth.js';
12
14
 
13
15
  /**
14
16
  * Tool definition for remember_update_memory
@@ -124,9 +126,13 @@ export interface UpdateMemoryResult {
124
126
  */
125
127
  export async function handleUpdateMemory(
126
128
  args: UpdateMemoryArgs,
127
- userId: string
129
+ userId: string,
130
+ authContext?: AuthContext
128
131
  ): Promise<string> {
132
+ const debug = createDebugLogger({ tool: 'remember_update_memory', userId, operation: 'update memory' });
129
133
  try {
134
+ debug.info('Tool invoked');
135
+ debug.trace('Arguments', { args });
130
136
  logger.info('Updating memory', { userId, memoryId: args.memory_id });
131
137
 
132
138
  const collection = getMemoryCollection(userId);
@@ -191,8 +197,8 @@ export async function handleUpdateMemory(
191
197
  if (!isValidContentType(args.type)) {
192
198
  throw new Error(`Invalid content type: ${args.type}`);
193
199
  }
194
- updates.type = args.type;
195
- updatedFields.push('type');
200
+ updates.content_type = args.type;
201
+ updatedFields.push('content_type');
196
202
  }
197
203
 
198
204
  // Update scoring fields
@@ -210,8 +216,8 @@ export async function handleUpdateMemory(
210
216
  if (args.trust < 0 || args.trust > 1) {
211
217
  throw new Error('Trust must be between 0 and 1');
212
218
  }
213
- updates.trust = args.trust;
214
- updatedFields.push('trust');
219
+ updates.trust_score = args.trust;
220
+ updatedFields.push('trust_score');
215
221
  }
216
222
 
217
223
  // Update organization fields
@@ -231,6 +237,10 @@ export async function handleUpdateMemory(
231
237
  updatedFields.push('structured_content');
232
238
  }
233
239
 
240
+ // NOTE: space_ids and group_ids (publication tracking arrays) are intentionally
241
+ // NOT exposed in UpdateMemoryArgs — they are managed exclusively by
242
+ // remember_publish and remember_retract. The spread below preserves them.
243
+
234
244
  // Update comment/threading fields
235
245
  if (args.parent_id !== undefined) {
236
246
  updates.parent_id = args.parent_id;
@@ -314,6 +324,7 @@ export async function handleUpdateMemory(
314
324
 
315
325
  return JSON.stringify(result, null, 2);
316
326
  } catch (error) {
327
+ debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
317
328
  handleToolError(error, {
318
329
  toolName: 'remember_update_memory',
319
330
  operation: 'update memory',