@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.
- package/.env.example +6 -0
- package/AGENT.md +224 -21
- package/CHANGELOG.md +155 -915
- package/README.md +130 -1
- package/agent/commands/acp.command-create.md +372 -0
- package/agent/commands/acp.design-create.md +224 -0
- package/agent/commands/acp.init.md +39 -5
- package/agent/commands/acp.package-create.md +894 -0
- package/agent/commands/acp.package-info.md +211 -0
- package/agent/commands/acp.package-install.md +206 -33
- package/agent/commands/acp.package-list.md +279 -0
- package/agent/commands/acp.package-publish.md +540 -0
- package/agent/commands/acp.package-remove.md +292 -0
- package/agent/commands/acp.package-search.md +306 -0
- package/agent/commands/acp.package-update.md +360 -0
- package/agent/commands/acp.package-validate.md +539 -0
- package/agent/commands/acp.pattern-create.md +326 -0
- package/agent/commands/acp.plan.md +552 -0
- package/agent/commands/acp.proceed.md +111 -86
- package/agent/commands/acp.project-create.md +672 -0
- package/agent/commands/acp.project-list.md +224 -0
- package/agent/commands/acp.project-set.md +226 -0
- package/agent/commands/acp.report.md +2 -0
- package/agent/commands/acp.resume.md +237 -0
- package/agent/commands/acp.sync.md +55 -15
- package/agent/commands/acp.task-create.md +390 -0
- package/agent/commands/acp.validate.md +61 -10
- package/agent/commands/acp.version-check-for-updates.md +5 -5
- package/agent/commands/acp.version-check.md +6 -6
- package/agent/commands/acp.version-update.md +6 -6
- package/agent/commands/command.template.md +43 -0
- package/agent/commands/git.commit.md +5 -3
- package/agent/design/soft-delete-system.md +291 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/milestones/milestone-13-soft-delete-system.md +306 -0
- package/agent/package.template.yaml +36 -0
- package/agent/progress.template.yaml +3 -0
- package/agent/progress.yaml +238 -6
- package/agent/scripts/acp.common.sh +1536 -0
- package/agent/scripts/{install.sh → acp.install.sh} +82 -26
- package/agent/scripts/acp.package-create.sh +925 -0
- package/agent/scripts/acp.package-info.sh +270 -0
- package/agent/scripts/acp.package-install.sh +596 -0
- package/agent/scripts/acp.package-list.sh +263 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +272 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +438 -0
- package/agent/scripts/acp.package-validate.sh +855 -0
- package/agent/scripts/acp.project-list.sh +121 -0
- package/agent/scripts/acp.project-set.sh +138 -0
- package/agent/scripts/{uninstall.sh → acp.uninstall.sh} +25 -15
- package/agent/scripts/{check-for-updates.sh → acp.version-check-for-updates.sh} +24 -14
- package/agent/scripts/{version.sh → acp.version-check.sh} +20 -8
- package/agent/scripts/{update.sh → acp.version-update.sh} +44 -25
- package/agent/scripts/acp.yaml-parser.sh +853 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/task-68-fix-missing-space-properties.md +192 -0
- package/agent/tasks/task-69-add-comprehensive-tool-debugging.md +454 -0
- package/agent/tasks/task-70-add-soft-delete-schema-fields.md +165 -0
- package/agent/tasks/task-71-implement-delete-confirmation-flow.md +257 -0
- package/agent/tasks/task-72-add-deleted-filter-to-search-tools.md +18 -0
- package/agent/tasks/task-73-update-relationship-handling.md +18 -0
- package/agent/tasks/task-74-add-unit-tests-soft-delete.md +18 -0
- package/agent/tasks/task-75-update-documentation-changelog.md +26 -0
- package/dist/config.d.ts +18 -0
- package/dist/server-factory.js +788 -355
- package/dist/server.js +788 -355
- package/dist/tools/delete-memory.d.ts +5 -30
- package/dist/tools/find-similar.d.ts +8 -1
- package/dist/tools/query-memory.d.ts +8 -1
- package/dist/tools/search-memory.d.ts +6 -0
- package/dist/tools/search-relationship.d.ts +8 -1
- package/dist/types/memory.d.ts +8 -0
- package/dist/types/space-memory.d.ts +3 -0
- package/dist/utils/debug.d.ts +52 -0
- package/dist/utils/debug.spec.d.ts +5 -0
- package/dist/utils/weaviate-filters.d.ts +19 -0
- package/dist/weaviate/client.d.ts +1 -1
- package/package.json +1 -1
- package/src/config.ts +33 -0
- package/src/tools/confirm.ts +113 -8
- package/src/tools/create-relationship.ts +14 -1
- package/src/tools/delete-memory.ts +91 -63
- package/src/tools/find-similar.ts +30 -5
- package/src/tools/publish.ts +19 -1
- package/src/tools/query-memory.ts +18 -5
- package/src/tools/query-space.ts +36 -3
- package/src/tools/search-memory.ts +18 -5
- package/src/tools/search-relationship.ts +19 -5
- package/src/tools/search-space.ts +36 -3
- package/src/tools/update-memory.ts +8 -0
- package/src/types/memory.ts +11 -0
- package/src/types/space-memory.ts +5 -0
- package/src/utils/debug.spec.ts +257 -0
- package/src/utils/debug.ts +138 -0
- package/src/utils/weaviate-filters.ts +28 -1
- package/src/weaviate/client.ts +47 -3
- package/src/weaviate/schema.ts +17 -0
- package/src/weaviate/space-schema.spec.ts +5 -2
- 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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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[] = [];
|
package/src/types/memory.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/weaviate/client.ts
CHANGED
|
@@ -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
|
-
|
|
234
|
-
|
|
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
|
|
288
|
+
return await debug.time('Fetch without property specification', async () => {
|
|
289
|
+
return await collection.query.fetchObjectById(memoryId);
|
|
290
|
+
});
|
|
247
291
|
}
|
|
248
292
|
}
|
|
249
293
|
|
package/src/weaviate/schema.ts
CHANGED
|
@@ -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
|
|