@prmichaelsen/remember-mcp 2.7.11 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.env.example +6 -0
  2. package/AGENT.md +224 -21
  3. package/CHANGELOG.md +155 -915
  4. package/README.md +130 -1
  5. package/agent/commands/acp.command-create.md +372 -0
  6. package/agent/commands/acp.design-create.md +224 -0
  7. package/agent/commands/acp.init.md +39 -5
  8. package/agent/commands/acp.package-create.md +894 -0
  9. package/agent/commands/acp.package-info.md +211 -0
  10. package/agent/commands/acp.package-install.md +206 -33
  11. package/agent/commands/acp.package-list.md +279 -0
  12. package/agent/commands/acp.package-publish.md +540 -0
  13. package/agent/commands/acp.package-remove.md +292 -0
  14. package/agent/commands/acp.package-search.md +306 -0
  15. package/agent/commands/acp.package-update.md +360 -0
  16. package/agent/commands/acp.package-validate.md +539 -0
  17. package/agent/commands/acp.pattern-create.md +326 -0
  18. package/agent/commands/acp.plan.md +552 -0
  19. package/agent/commands/acp.proceed.md +111 -86
  20. package/agent/commands/acp.project-create.md +672 -0
  21. package/agent/commands/acp.project-list.md +224 -0
  22. package/agent/commands/acp.project-set.md +226 -0
  23. package/agent/commands/acp.report.md +2 -0
  24. package/agent/commands/acp.resume.md +237 -0
  25. package/agent/commands/acp.sync.md +55 -15
  26. package/agent/commands/acp.task-create.md +390 -0
  27. package/agent/commands/acp.validate.md +61 -10
  28. package/agent/commands/acp.version-check-for-updates.md +5 -5
  29. package/agent/commands/acp.version-check.md +6 -6
  30. package/agent/commands/acp.version-update.md +6 -6
  31. package/agent/commands/command.template.md +43 -0
  32. package/agent/commands/git.commit.md +5 -3
  33. package/agent/design/soft-delete-system.md +291 -0
  34. package/agent/manifest.template.yaml +13 -0
  35. package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
  36. package/agent/package.template.yaml +36 -0
  37. package/agent/progress.template.yaml +3 -0
  38. package/agent/progress.yaml +238 -6
  39. package/agent/scripts/acp.common.sh +1536 -0
  40. package/agent/scripts/{install.sh → acp.install.sh} +82 -26
  41. package/agent/scripts/acp.package-create.sh +925 -0
  42. package/agent/scripts/acp.package-info.sh +270 -0
  43. package/agent/scripts/acp.package-install.sh +596 -0
  44. package/agent/scripts/acp.package-list.sh +263 -0
  45. package/agent/scripts/acp.package-publish.sh +420 -0
  46. package/agent/scripts/acp.package-remove.sh +272 -0
  47. package/agent/scripts/acp.package-search.sh +156 -0
  48. package/agent/scripts/acp.package-update.sh +438 -0
  49. package/agent/scripts/acp.package-validate.sh +855 -0
  50. package/agent/scripts/acp.project-list.sh +121 -0
  51. package/agent/scripts/acp.project-set.sh +138 -0
  52. package/agent/scripts/{uninstall.sh → acp.uninstall.sh} +25 -15
  53. package/agent/scripts/{check-for-updates.sh → acp.version-check-for-updates.sh} +24 -14
  54. package/agent/scripts/{version.sh → acp.version-check.sh} +20 -8
  55. package/agent/scripts/{update.sh → acp.version-update.sh} +44 -25
  56. package/agent/scripts/acp.yaml-parser.sh +853 -0
  57. package/agent/scripts/acp.yaml-validate.sh +205 -0
  58. package/agent/tasks/task-68-fix-missing-space-properties.md +192 -0
  59. package/agent/tasks/task-69-add-comprehensive-tool-debugging.md +454 -0
  60. package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
  61. package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
  62. package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
  63. package/agent/tasks/task-73-update-relationship-handling.md +18 -0
  64. package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
  65. package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
  66. package/dist/config.d.ts +18 -0
  67. package/dist/server-factory.js +788 -355
  68. package/dist/server.js +788 -355
  69. package/dist/tools/delete-memory.d.ts +5 -30
  70. package/dist/tools/find-similar.d.ts +8 -1
  71. package/dist/tools/query-memory.d.ts +8 -1
  72. package/dist/tools/search-memory.d.ts +6 -0
  73. package/dist/tools/search-relationship.d.ts +8 -1
  74. package/dist/types/memory.d.ts +8 -0
  75. package/dist/types/space-memory.d.ts +3 -0
  76. package/dist/utils/debug.d.ts +52 -0
  77. package/dist/utils/debug.spec.d.ts +5 -0
  78. package/dist/utils/weaviate-filters.d.ts +19 -0
  79. package/dist/weaviate/client.d.ts +1 -1
  80. package/package.json +1 -1
  81. package/src/config.ts +33 -0
  82. package/src/tools/confirm.ts +113 -8
  83. package/src/tools/create-relationship.ts +14 -1
  84. package/src/tools/delete-memory.ts +91 -63
  85. package/src/tools/find-similar.ts +30 -5
  86. package/src/tools/publish.ts +19 -1
  87. package/src/tools/query-memory.ts +18 -5
  88. package/src/tools/query-space.ts +36 -3
  89. package/src/tools/search-memory.ts +18 -5
  90. package/src/tools/search-relationship.ts +19 -5
  91. package/src/tools/search-space.ts +36 -3
  92. package/src/tools/update-memory.ts +8 -0
  93. package/src/types/memory.ts +11 -0
  94. package/src/types/space-memory.ts +5 -0
  95. package/src/utils/debug.spec.ts +257 -0
  96. package/src/utils/debug.ts +138 -0
  97. package/src/utils/weaviate-filters.ts +28 -1
  98. package/src/weaviate/client.ts +47 -3
  99. package/src/weaviate/schema.ts +17 -0
  100. package/src/weaviate/space-schema.spec.ts +5 -2
  101. package/src/weaviate/space-schema.ts +17 -5
@@ -12,6 +12,7 @@ import { ensurePublicCollection, isValidSpaceId } from '../weaviate/space-schema
12
12
  import { SUPPORTED_SPACES } from '../types/space-memory.js';
13
13
  import { handleToolError } from '../utils/error-handler.js';
14
14
  import type { SearchFilters } from '../types/memory.js';
15
+ import { createDebugLogger } from '../utils/debug.js';
15
16
 
16
17
  /**
17
18
  * Tool definition for remember_search_space
@@ -113,8 +114,18 @@ export async function handleSearchSpace(
113
114
  args: SearchSpaceArgs,
114
115
  userId: string // May be used for private spaces in future
115
116
  ): Promise<string> {
117
+ const debug = createDebugLogger({
118
+ tool: 'remember_search_space',
119
+ userId,
120
+ operation: 'search_spaces',
121
+ });
122
+
116
123
  try {
124
+ debug.info('Tool invoked');
125
+ debug.trace('Arguments', { args });
126
+
117
127
  // Validate all space IDs
128
+ debug.debug('Validating space IDs', { spaces: args.spaces });
118
129
  const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
119
130
  if (invalidSpaces.length > 0) {
120
131
  return JSON.stringify(
@@ -197,11 +208,24 @@ export async function handleSearchSpace(
197
208
 
198
209
  const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
199
210
 
200
- // Execute hybrid search
201
- const searchResults = await publicCollection.query.hybrid(args.query, {
211
+ debug.debug('Executing hybrid search', {
212
+ query: args.query,
213
+ filterCount: filterList.length,
202
214
  limit: args.limit || 10,
203
215
  offset: args.offset || 0,
204
- ...(whereFilter && { where: whereFilter }),
216
+ });
217
+
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 }),
224
+ });
225
+ });
226
+
227
+ debug.debug('Search completed', {
228
+ resultCount: searchResults.objects.length,
205
229
  });
206
230
 
207
231
  // Format results
@@ -220,8 +244,17 @@ export async function handleSearchSpace(
220
244
  limit: args.limit || 10,
221
245
  };
222
246
 
247
+ debug.info('Tool completed successfully', {
248
+ resultCount: memories.length,
249
+ spaces: args.spaces,
250
+ });
251
+
223
252
  return JSON.stringify(result, null, 2);
224
253
  } catch (error) {
254
+ debug.error('Tool failed', {
255
+ error: error instanceof Error ? error.message : String(error),
256
+ stack: error instanceof Error ? error.stack : undefined,
257
+ });
225
258
  return handleToolError(error, {
226
259
  toolName: 'remember_search_space',
227
260
  operation: 'search spaces',
@@ -163,6 +163,14 @@ export async function handleUpdateMemory(
163
163
  throw new Error('Cannot update relationships using this tool. Use remember_update_relationship instead.');
164
164
  }
165
165
 
166
+ // Check if memory is deleted
167
+ if (existingMemory.properties.deleted_at) {
168
+ const deletedAt = typeof existingMemory.properties.deleted_at === 'string'
169
+ ? existingMemory.properties.deleted_at
170
+ : new Date(existingMemory.properties.deleted_at as any).toISOString();
171
+ throw new Error(`Cannot update deleted memory: ${args.memory_id}. Memory was deleted on ${deletedAt}.`);
172
+ }
173
+
166
174
  // Build update object with only provided fields
167
175
  const updates: Record<string, any> = {};
168
176
  const updatedFields: string[] = [];
@@ -196,6 +196,11 @@ export interface Memory {
196
196
  parent_id?: string | null; // ID of parent memory or comment (null for top-level)
197
197
  thread_root_id?: string | null; // Root memory ID for fetching entire thread (null for top-level)
198
198
  moderation_flags?: string[]; // Per-space moderation flags (format: "{space_id}:{flag_type}")
199
+
200
+ // Soft Delete Fields
201
+ deleted_at?: Date | null; // Timestamp when memory was soft-deleted (null = not deleted)
202
+ deleted_by?: string; // User ID who deleted the memory
203
+ deletion_reason?: string; // Optional reason for deletion
199
204
  }
200
205
 
201
206
  /**
@@ -259,6 +264,11 @@ export interface SearchFilters {
259
264
  has_relationships?: boolean;
260
265
  }
261
266
 
267
+ /**
268
+ * Deleted filter type
269
+ */
270
+ export type DeletedFilter = 'exclude' | 'include' | 'only';
271
+
262
272
  /**
263
273
  * Search options
264
274
  */
@@ -267,6 +277,7 @@ export interface SearchOptions {
267
277
  alpha?: number; // 0-1, balance between semantic (1.0) and keyword (0.0)
268
278
  filters?: SearchFilters;
269
279
  include_relationships?: boolean;
280
+ deleted_filter?: DeletedFilter;
270
281
  limit?: number;
271
282
  offset?: number;
272
283
  }
@@ -55,6 +55,11 @@ export interface SpaceMemory extends Omit<Memory, 'user_id' | 'doc_type'> {
55
55
  * Always 'space_memory' for space memories
56
56
  */
57
57
  doc_type: 'space_memory';
58
+
59
+ // Soft Delete Fields (inherited from Memory, but explicitly typed here)
60
+ deleted_at?: Date | null;
61
+ deleted_by?: string;
62
+ deletion_reason?: string;
58
63
  }
59
64
 
60
65
  /**
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Unit tests for debug utility
3
+ */
4
+
5
+ import { createDebugLogger, DebugLogger } from './debug';
6
+ import { DebugLevel } from '../config';
7
+ import { logger } from './logger';
8
+
9
+ // Mock logger
10
+ jest.mock('./logger', () => ({
11
+ logger: {
12
+ debug: jest.fn(),
13
+ info: jest.fn(),
14
+ warn: jest.fn(),
15
+ error: jest.fn(),
16
+ },
17
+ }));
18
+
19
+ // Mock config
20
+ jest.mock('../config', () => {
21
+ let mockLevel = 0; // NONE by default
22
+
23
+ return {
24
+ DebugLevel: {
25
+ NONE: 0,
26
+ ERROR: 1,
27
+ WARN: 2,
28
+ INFO: 3,
29
+ DEBUG: 4,
30
+ TRACE: 5,
31
+ },
32
+ debugConfig: {
33
+ get level() {
34
+ return mockLevel;
35
+ },
36
+ set level(value: number) {
37
+ mockLevel = value;
38
+ },
39
+ enabled: (level: number) => mockLevel >= level,
40
+ },
41
+ // Re-export setMockDebugLevel for tests
42
+ __setMockDebugLevel: (level: number) => {
43
+ mockLevel = level;
44
+ },
45
+ };
46
+ });
47
+
48
+ const { __setMockDebugLevel } = require('../config');
49
+
50
+ describe('DebugLogger', () => {
51
+ let debug: DebugLogger;
52
+
53
+ beforeEach(() => {
54
+ jest.clearAllMocks();
55
+ __setMockDebugLevel(DebugLevel.NONE);
56
+ debug = createDebugLogger({
57
+ tool: 'test_tool',
58
+ userId: 'user123',
59
+ operation: 'test_operation',
60
+ });
61
+ });
62
+
63
+ describe('Debug Level Filtering', () => {
64
+ it('should not log anything when level is NONE', () => {
65
+ __setMockDebugLevel(DebugLevel.NONE);
66
+
67
+ debug.trace('trace message');
68
+ debug.debug('debug message');
69
+ debug.info('info message');
70
+ debug.warn('warn message');
71
+ debug.error('error message');
72
+
73
+ expect(logger.debug).not.toHaveBeenCalled();
74
+ expect(logger.info).not.toHaveBeenCalled();
75
+ expect(logger.warn).not.toHaveBeenCalled();
76
+ expect(logger.error).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it('should only log errors when level is ERROR', () => {
80
+ __setMockDebugLevel(DebugLevel.ERROR);
81
+
82
+ debug.trace('trace message');
83
+ debug.debug('debug message');
84
+ debug.info('info message');
85
+ debug.warn('warn message');
86
+ debug.error('error message');
87
+
88
+ expect(logger.debug).not.toHaveBeenCalled();
89
+ expect(logger.info).not.toHaveBeenCalled();
90
+ expect(logger.warn).not.toHaveBeenCalled();
91
+ expect(logger.error).toHaveBeenCalledTimes(1);
92
+ });
93
+
94
+ it('should log warnings and errors when level is WARN', () => {
95
+ __setMockDebugLevel(DebugLevel.WARN);
96
+
97
+ debug.trace('trace message');
98
+ debug.debug('debug message');
99
+ debug.info('info message');
100
+ debug.warn('warn message');
101
+ debug.error('error message');
102
+
103
+ expect(logger.debug).not.toHaveBeenCalled();
104
+ expect(logger.info).not.toHaveBeenCalled();
105
+ expect(logger.warn).toHaveBeenCalledTimes(1);
106
+ expect(logger.error).toHaveBeenCalledTimes(1);
107
+ });
108
+
109
+ it('should log info, warnings, and errors when level is INFO', () => {
110
+ __setMockDebugLevel(DebugLevel.INFO);
111
+
112
+ debug.trace('trace message');
113
+ debug.debug('debug message');
114
+ debug.info('info message');
115
+ debug.warn('warn message');
116
+ debug.error('error message');
117
+
118
+ expect(logger.debug).not.toHaveBeenCalled();
119
+ expect(logger.info).toHaveBeenCalledTimes(1);
120
+ expect(logger.warn).toHaveBeenCalledTimes(1);
121
+ expect(logger.error).toHaveBeenCalledTimes(1);
122
+ });
123
+
124
+ it('should log debug, info, warnings, and errors when level is DEBUG', () => {
125
+ __setMockDebugLevel(DebugLevel.DEBUG);
126
+
127
+ debug.trace('trace message');
128
+ debug.debug('debug message');
129
+ debug.info('info message');
130
+ debug.warn('warn message');
131
+ debug.error('error message');
132
+
133
+ expect(logger.debug).toHaveBeenCalledTimes(1); // debug only
134
+ expect(logger.info).toHaveBeenCalledTimes(1);
135
+ expect(logger.warn).toHaveBeenCalledTimes(1);
136
+ expect(logger.error).toHaveBeenCalledTimes(1);
137
+ });
138
+
139
+ it('should log everything including trace when level is TRACE', () => {
140
+ __setMockDebugLevel(DebugLevel.TRACE);
141
+
142
+ debug.trace('trace message');
143
+ debug.debug('debug message');
144
+ debug.info('info message');
145
+ debug.warn('warn message');
146
+ debug.error('error message');
147
+
148
+ expect(logger.debug).toHaveBeenCalledTimes(2); // trace + debug
149
+ expect(logger.info).toHaveBeenCalledTimes(1);
150
+ expect(logger.warn).toHaveBeenCalledTimes(1);
151
+ expect(logger.error).toHaveBeenCalledTimes(1);
152
+ });
153
+ });
154
+
155
+ describe('Context Propagation', () => {
156
+ it('should include context in all log calls', () => {
157
+ __setMockDebugLevel(DebugLevel.DEBUG);
158
+
159
+ debug.debug('test message', { extra: 'data' });
160
+
161
+ expect(logger.debug).toHaveBeenCalledWith(
162
+ '[DEBUG] test message',
163
+ expect.objectContaining({
164
+ tool: 'test_tool',
165
+ userId: 'user123',
166
+ operation: 'test_operation',
167
+ extra: 'data',
168
+ debugLevel: 'DEBUG',
169
+ })
170
+ );
171
+ });
172
+ });
173
+
174
+ describe('dump()', () => {
175
+ it('should only dump objects at TRACE level', () => {
176
+ __setMockDebugLevel(DebugLevel.DEBUG);
177
+
178
+ debug.dump('test object', { foo: 'bar' });
179
+ expect(logger.debug).not.toHaveBeenCalled();
180
+
181
+ __setMockDebugLevel(DebugLevel.TRACE);
182
+ debug.dump('test object', { foo: 'bar' });
183
+
184
+ expect(logger.debug).toHaveBeenCalledWith(
185
+ '[DUMP] test object',
186
+ expect.objectContaining({
187
+ dump: expect.stringContaining('"foo": "bar"'),
188
+ })
189
+ );
190
+ });
191
+ });
192
+
193
+ describe('time()', () => {
194
+ it('should not add timing overhead when debug is disabled', async () => {
195
+ __setMockDebugLevel(DebugLevel.NONE);
196
+
197
+ const result = await debug.time('test operation', async () => {
198
+ return 'result';
199
+ });
200
+
201
+ expect(result).toBe('result');
202
+ expect(logger.debug).not.toHaveBeenCalled();
203
+ });
204
+
205
+ it('should log timing when DEBUG level is enabled', async () => {
206
+ __setMockDebugLevel(DebugLevel.DEBUG);
207
+
208
+ const result = await debug.time('test operation', async () => {
209
+ await new Promise(resolve => setTimeout(resolve, 10));
210
+ return 'result';
211
+ });
212
+
213
+ expect(result).toBe('result');
214
+ expect(logger.debug).toHaveBeenCalledWith(
215
+ '[DEBUG] test operation - Starting',
216
+ expect.any(Object)
217
+ );
218
+ expect(logger.debug).toHaveBeenCalledWith(
219
+ '[DEBUG] test operation - Completed',
220
+ expect.objectContaining({
221
+ durationMs: expect.any(Number),
222
+ })
223
+ );
224
+ });
225
+
226
+ it('should log errors with timing on failure', async () => {
227
+ __setMockDebugLevel(DebugLevel.DEBUG);
228
+
229
+ const error = new Error('test error');
230
+
231
+ await expect(
232
+ debug.time('test operation', async () => {
233
+ throw error;
234
+ })
235
+ ).rejects.toThrow('test error');
236
+
237
+ expect(logger.error).toHaveBeenCalledWith(
238
+ '[ERROR] test operation - Failed',
239
+ expect.objectContaining({
240
+ durationMs: expect.any(Number),
241
+ error: 'test error',
242
+ })
243
+ );
244
+ });
245
+ });
246
+
247
+ describe('createDebugLogger()', () => {
248
+ it('should create a DebugLogger instance with context', () => {
249
+ const logger = createDebugLogger({
250
+ tool: 'test_tool',
251
+ userId: 'user123',
252
+ });
253
+
254
+ expect(logger).toBeInstanceOf(DebugLogger);
255
+ });
256
+ });
257
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Debug logging utility for remember-mcp
3
+ *
4
+ * Provides configurable debug logging via REMEMBER_MCP_DEBUG_LEVEL environment variable.
5
+ * Levels: NONE (default), ERROR, WARN, INFO, DEBUG, TRACE
6
+ */
7
+
8
+ import { debugConfig, DebugLevel } from '../config.js';
9
+ import { logger } from './logger.js';
10
+
11
+ export interface DebugContext {
12
+ tool: string;
13
+ userId?: string;
14
+ operation?: string;
15
+ [key: string]: any;
16
+ }
17
+
18
+ export class DebugLogger {
19
+ private context: DebugContext;
20
+
21
+ constructor(context: DebugContext) {
22
+ this.context = context;
23
+ }
24
+
25
+ trace(message: string, data?: any): void {
26
+ if (debugConfig.enabled(DebugLevel.TRACE)) {
27
+ logger.debug(`[TRACE] ${message}`, {
28
+ ...this.context,
29
+ ...data,
30
+ debugLevel: 'TRACE',
31
+ });
32
+ }
33
+ }
34
+
35
+ debug(message: string, data?: any): void {
36
+ if (debugConfig.enabled(DebugLevel.DEBUG)) {
37
+ logger.debug(`[DEBUG] ${message}`, {
38
+ ...this.context,
39
+ ...data,
40
+ debugLevel: 'DEBUG',
41
+ });
42
+ }
43
+ }
44
+
45
+ info(message: string, data?: any): void {
46
+ if (debugConfig.enabled(DebugLevel.INFO)) {
47
+ logger.info(`[INFO] ${message}`, {
48
+ ...this.context,
49
+ ...data,
50
+ debugLevel: 'INFO',
51
+ });
52
+ }
53
+ }
54
+
55
+ warn(message: string, data?: any): void {
56
+ if (debugConfig.enabled(DebugLevel.WARN)) {
57
+ logger.warn(`[WARN] ${message}`, {
58
+ ...this.context,
59
+ ...data,
60
+ debugLevel: 'WARN',
61
+ });
62
+ }
63
+ }
64
+
65
+ error(message: string, data?: any): void {
66
+ if (debugConfig.enabled(DebugLevel.ERROR)) {
67
+ logger.error(`[ERROR] ${message}`, {
68
+ ...this.context,
69
+ ...data,
70
+ debugLevel: 'ERROR',
71
+ });
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Dump full object (TRACE only)
77
+ * Use with caution - may expose sensitive data
78
+ */
79
+ dump(label: string, obj: any): void {
80
+ if (debugConfig.enabled(DebugLevel.TRACE)) {
81
+ logger.debug(`[DUMP] ${label}`, {
82
+ ...this.context,
83
+ dump: JSON.stringify(obj, null, 2),
84
+ debugLevel: 'TRACE',
85
+ });
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Time an async operation (DEBUG and above)
91
+ * Logs start, completion, and duration
92
+ */
93
+ async time<T>(label: string, fn: () => Promise<T>): Promise<T> {
94
+ if (!debugConfig.enabled(DebugLevel.DEBUG)) {
95
+ return fn();
96
+ }
97
+
98
+ const start = Date.now();
99
+ this.debug(`${label} - Starting`);
100
+
101
+ try {
102
+ const result = await fn();
103
+ const duration = Date.now() - start;
104
+ this.debug(`${label} - Completed`, { durationMs: duration });
105
+ return result;
106
+ } catch (error) {
107
+ const duration = Date.now() - start;
108
+ this.error(`${label} - Failed`, {
109
+ durationMs: duration,
110
+ error: error instanceof Error ? error.message : String(error),
111
+ });
112
+ throw error;
113
+ }
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Create a debug logger with context
119
+ *
120
+ * @param context - Context information (tool name, userId, operation, etc.)
121
+ * @returns DebugLogger instance
122
+ *
123
+ * @example
124
+ * const debug = createDebugLogger({
125
+ * tool: 'remember_create_memory',
126
+ * userId: 'user123',
127
+ * operation: 'create',
128
+ * });
129
+ *
130
+ * debug.info('Tool invoked');
131
+ * debug.trace('Arguments', { args });
132
+ * const result = await debug.time('Database operation', async () => {
133
+ * return await collection.insert(data);
134
+ * });
135
+ */
136
+ export function createDebugLogger(context: DebugContext): DebugLogger {
137
+ return new DebugLogger(context);
138
+ }
@@ -8,6 +8,11 @@
8
8
  import { Filters } from 'weaviate-client';
9
9
  import type { SearchFilters } from '../types/memory.js';
10
10
 
11
+ /**
12
+ * Deleted filter options
13
+ */
14
+ export type DeletedFilter = 'exclude' | 'include' | 'only';
15
+
11
16
  /**
12
17
  * Build filters for searching both memories and relationships
13
18
  * Uses OR logic: (doc_type=memory AND memory_filters) OR (doc_type=relationship AND relationship_filters)
@@ -165,7 +170,7 @@ export function buildRelationshipOnlyFilters(
165
170
  * @param filters - Array of filter objects
166
171
  * @returns Combined filter or undefined
167
172
  */
168
- function combineFiltersWithAnd(filters: any[]): any {
173
+ export function combineFiltersWithAnd(filters: any[]): any {
169
174
  // Filter out any undefined/null values
170
175
  const validFilters = filters.filter(f => f !== undefined && f !== null);
171
176
 
@@ -207,3 +212,25 @@ function combineFiltersWithOr(filters: any[]): any {
207
212
  export function hasFilters(filter: any): boolean {
208
213
  return filter !== undefined && filter !== null;
209
214
  }
215
+
216
+ /**
217
+ * Build filter for deleted_at field based on deleted_filter parameter
218
+ *
219
+ * @param collection - Weaviate collection instance
220
+ * @param deletedFilter - Filter mode: 'exclude' (default), 'include', or 'only'
221
+ * @returns Filter for deleted_at field, or null if no filter needed
222
+ */
223
+ export function buildDeletedFilter(
224
+ collection: any,
225
+ deletedFilter: DeletedFilter = 'exclude'
226
+ ): any | null {
227
+ if (deletedFilter === 'exclude') {
228
+ // Exclude deleted: deleted_at is null or missing
229
+ return collection.filter.byProperty('deleted_at').isNull(true);
230
+ } else if (deletedFilter === 'only') {
231
+ // Only deleted: deleted_at is not null
232
+ return collection.filter.byProperty('deleted_at').isNull(false);
233
+ }
234
+ // 'include': no filter (show all)
235
+ return null;
236
+ }
@@ -1,6 +1,7 @@
1
1
  import weaviate, { WeaviateClient } from 'weaviate-client';
2
2
  import { config } from '../config.js';
3
3
  import { logger } from '../utils/logger.js';
4
+ import { createDebugLogger } from '../utils/debug.js';
4
5
 
5
6
  let client: WeaviateClient | null = null;
6
7
 
@@ -207,6 +208,21 @@ export const ALL_MEMORY_PROPERTIES = [
207
208
  'parent_id',
208
209
  'thread_root_id',
209
210
  'moderation_flags',
211
+
212
+ // Space/publishing fields (for Memory_public collection)
213
+ 'spaces',
214
+ 'space_id',
215
+ 'author_id',
216
+ 'ghost_id',
217
+ 'attribution',
218
+ 'published_at',
219
+ 'discovery_count',
220
+ 'space_memory_id',
221
+
222
+ // Soft delete fields
223
+ 'deleted_at',
224
+ 'deleted_by',
225
+ 'deletion_reason',
210
226
  ] as const;
211
227
 
212
228
  /**
@@ -228,22 +244,50 @@ export async function fetchMemoryWithAllProperties(
228
244
  collection: any,
229
245
  memoryId: string
230
246
  ) {
247
+ const debug = createDebugLogger({
248
+ tool: 'weaviate-client',
249
+ operation: 'fetchMemoryWithAllProperties',
250
+ });
251
+
252
+ debug.debug('Fetching memory', {
253
+ memoryId,
254
+ collectionName: collection.name,
255
+ propertyCount: ALL_MEMORY_PROPERTIES.length,
256
+ });
257
+
231
258
  try {
232
259
  // Try to fetch with all properties specified
233
- return await collection.query.fetchObjectById(memoryId, {
234
- returnProperties: ALL_MEMORY_PROPERTIES,
260
+ const result = await debug.time('Fetch with all properties', async () => {
261
+ return await collection.query.fetchObjectById(memoryId, {
262
+ returnProperties: ALL_MEMORY_PROPERTIES,
263
+ });
235
264
  });
265
+
266
+ debug.trace('Fetch result', {
267
+ found: !!result,
268
+ propertyCount: result?.properties ? Object.keys(result.properties).length : 0,
269
+ hasSpaces: !!result?.properties?.spaces,
270
+ hasAuthorId: !!result?.properties?.author_id,
271
+ });
272
+
273
+ return result;
236
274
  } catch (error) {
237
275
  // If that fails (e.g., property doesn't exist on this record),
238
276
  // fetch without specifying properties - Weaviate will return
239
277
  // all properties that actually exist on the record
278
+ debug.warn('Fetch with all properties failed, falling back', {
279
+ error: error instanceof Error ? error.message : String(error),
280
+ });
281
+
240
282
  logger.warn('Failed to fetch with all properties, falling back to unspecified fetch', {
241
283
  module: 'weaviate-client',
242
284
  memoryId,
243
285
  error: error instanceof Error ? error.message : String(error),
244
286
  });
245
287
 
246
- return await collection.query.fetchObjectById(memoryId);
288
+ return await debug.time('Fetch without property specification', async () => {
289
+ return await collection.query.fetchObjectById(memoryId);
290
+ });
247
291
  }
248
292
  }
249
293
 
@@ -268,6 +268,23 @@ export async function createMemoryCollection(userId: string): Promise<void> {
268
268
  dataType: 'text[]' as any,
269
269
  description: 'Per-space moderation flags (format: "{space_id}:{flag_type}")',
270
270
  },
271
+
272
+ // Soft delete fields
273
+ {
274
+ name: 'deleted_at',
275
+ dataType: 'date' as any,
276
+ description: 'Timestamp when memory was soft-deleted (null = not deleted)',
277
+ },
278
+ {
279
+ name: 'deleted_by',
280
+ dataType: 'text' as any,
281
+ description: 'User ID who deleted the memory',
282
+ },
283
+ {
284
+ name: 'deletion_reason',
285
+ dataType: 'text' as any,
286
+ description: 'Optional reason for deletion',
287
+ },
271
288
  ],
272
289
  });
273
290