@prmichaelsen/remember-mcp 2.7.10 → 2.8.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 (76) hide show
  1. package/.env.example +6 -0
  2. package/AGENT.md +224 -21
  3. package/CHANGELOG.md +47 -912
  4. package/README.md +35 -0
  5. package/agent/commands/acp.command-create.md +373 -0
  6. package/agent/commands/acp.design-create.md +225 -0
  7. package/agent/commands/acp.init.md +40 -5
  8. package/agent/commands/acp.package-create.md +895 -0
  9. package/agent/commands/acp.package-info.md +212 -0
  10. package/agent/commands/acp.package-install.md +207 -33
  11. package/agent/commands/acp.package-list.md +280 -0
  12. package/agent/commands/acp.package-publish.md +541 -0
  13. package/agent/commands/acp.package-remove.md +293 -0
  14. package/agent/commands/acp.package-search.md +307 -0
  15. package/agent/commands/acp.package-update.md +361 -0
  16. package/agent/commands/acp.package-validate.md +540 -0
  17. package/agent/commands/acp.pattern-create.md +327 -0
  18. package/agent/commands/acp.plan.md +553 -0
  19. package/agent/commands/acp.proceed.md +112 -86
  20. package/agent/commands/acp.project-create.md +673 -0
  21. package/agent/commands/acp.project-list.md +225 -0
  22. package/agent/commands/acp.project-set.md +227 -0
  23. package/agent/commands/acp.report.md +3 -0
  24. package/agent/commands/acp.resume.md +238 -0
  25. package/agent/commands/acp.status.md +1 -0
  26. package/agent/commands/acp.sync.md +56 -15
  27. package/agent/commands/acp.task-create.md +391 -0
  28. package/agent/commands/acp.update.md +1 -0
  29. package/agent/commands/acp.validate.md +62 -10
  30. package/agent/commands/acp.version-check-for-updates.md +6 -5
  31. package/agent/commands/acp.version-check.md +7 -6
  32. package/agent/commands/acp.version-update.md +7 -6
  33. package/agent/commands/command.template.md +48 -0
  34. package/agent/commands/git.commit.md +6 -3
  35. package/agent/commands/git.init.md +1 -0
  36. package/agent/manifest.template.yaml +13 -0
  37. package/agent/package.template.yaml +53 -0
  38. package/agent/progress.template.yaml +3 -0
  39. package/agent/progress.yaml +103 -5
  40. package/agent/scripts/acp.common.sh +1536 -0
  41. package/agent/scripts/acp.install.sh +293 -0
  42. package/agent/scripts/acp.package-create.sh +925 -0
  43. package/agent/scripts/acp.package-info.sh +270 -0
  44. package/agent/scripts/acp.package-install.sh +675 -0
  45. package/agent/scripts/acp.package-list.sh +263 -0
  46. package/agent/scripts/acp.package-publish.sh +420 -0
  47. package/agent/scripts/acp.package-remove.sh +272 -0
  48. package/agent/scripts/acp.package-search.sh +156 -0
  49. package/agent/scripts/acp.package-update.sh +438 -0
  50. package/agent/scripts/acp.package-validate.sh +954 -0
  51. package/agent/scripts/acp.project-list.sh +121 -0
  52. package/agent/scripts/acp.project-set.sh +138 -0
  53. package/agent/scripts/{uninstall.sh → acp.uninstall.sh} +25 -15
  54. package/agent/scripts/{check-for-updates.sh → acp.version-check-for-updates.sh} +24 -14
  55. package/agent/scripts/{version.sh → acp.version-check.sh} +20 -8
  56. package/agent/scripts/{update.sh → acp.version-update.sh} +44 -25
  57. package/agent/scripts/acp.yaml-parser.sh +853 -0
  58. package/agent/scripts/acp.yaml-validate.sh +205 -0
  59. package/agent/tasks/task-68-fix-missing-space-properties.md +192 -0
  60. package/agent/tasks/task-69-add-comprehensive-tool-debugging.md +454 -0
  61. package/dist/config.d.ts +18 -0
  62. package/dist/server-factory.js +296 -19
  63. package/dist/server.js +296 -19
  64. package/dist/utils/debug.d.ts +52 -0
  65. package/dist/utils/debug.spec.d.ts +5 -0
  66. package/dist/weaviate/client.d.ts +1 -1
  67. package/package.json +1 -1
  68. package/src/config.ts +33 -0
  69. package/src/tools/confirm.ts +70 -7
  70. package/src/tools/publish.ts +19 -1
  71. package/src/tools/query-space.ts +36 -3
  72. package/src/tools/search-space.ts +36 -3
  73. package/src/utils/debug.spec.ts +257 -0
  74. package/src/utils/debug.ts +138 -0
  75. package/src/weaviate/client.ts +42 -3
  76. package/agent/scripts/install.sh +0 -157
@@ -11,6 +11,7 @@ import { getWeaviateClient, getMemoryCollectionName, fetchMemoryWithAllPropertie
11
11
  import { ensurePublicCollection } from '../weaviate/space-schema.js';
12
12
  import { handleToolError } from '../utils/error-handler.js';
13
13
  import { logger } from '../utils/logger.js';
14
+ import { createDebugLogger } from '../utils/debug.js';
14
15
 
15
16
  /**
16
17
  * Tool definition for remember_confirm
@@ -63,7 +64,16 @@ export async function handleConfirm(
63
64
  args: ConfirmArgs,
64
65
  userId: string
65
66
  ): Promise<string> {
67
+ const debug = createDebugLogger({
68
+ tool: 'remember_confirm',
69
+ userId,
70
+ operation: 'confirm_action',
71
+ });
72
+
66
73
  try {
74
+ debug.info('Tool invoked');
75
+ debug.trace('Arguments', { token: args.token });
76
+
67
77
  logger.info('Starting confirmation', {
68
78
  tool: 'remember_confirm',
69
79
  userId,
@@ -71,7 +81,10 @@ export async function handleConfirm(
71
81
  });
72
82
 
73
83
  // Validate and confirm token
74
- const request = await confirmationTokenService.confirmRequest(userId, args.token);
84
+ debug.debug('Validating confirmation token');
85
+ const request = await debug.time('Confirm token', async () => {
86
+ return await confirmationTokenService.confirmRequest(userId, args.token);
87
+ });
75
88
 
76
89
  logger.debug('Token validation result', {
77
90
  tool: 'remember_confirm',
@@ -114,6 +127,10 @@ export async function handleConfirm(
114
127
 
115
128
  throw new Error(`Unknown action type: ${request.action}`);
116
129
  } catch (error) {
130
+ debug.error('Tool failed', {
131
+ error: error instanceof Error ? error.message : String(error),
132
+ stack: error instanceof Error ? error.stack : undefined,
133
+ });
117
134
  handleToolError(error, {
118
135
  toolName: 'remember_confirm',
119
136
  userId,
@@ -130,7 +147,18 @@ async function executePublishMemory(
130
147
  request: ConfirmationRequest & { request_id: string },
131
148
  userId: string
132
149
  ): Promise<string> {
150
+ const debug = createDebugLogger({
151
+ tool: 'remember_confirm',
152
+ userId,
153
+ operation: 'execute_publish',
154
+ });
155
+
133
156
  try {
157
+ debug.debug('Executing publish memory action', {
158
+ memoryId: request.payload.memory_id,
159
+ spaces: request.payload.spaces,
160
+ });
161
+
134
162
  logger.info('Executing publish memory action', {
135
163
  function: 'executePublishMemory',
136
164
  userId,
@@ -151,10 +179,12 @@ async function executePublishMemory(
151
179
  memoryId: request.payload.memory_id,
152
180
  });
153
181
 
154
- const originalMemory = await fetchMemoryWithAllProperties(
155
- userCollection,
156
- request.payload.memory_id
157
- );
182
+ const originalMemory = await debug.time('Fetch original memory', async () => {
183
+ return await fetchMemoryWithAllProperties(
184
+ userCollection,
185
+ request.payload.memory_id
186
+ );
187
+ });
158
188
 
159
189
  logger.info('Original memory fetch result', {
160
190
  function: 'executePublishMemory',
@@ -188,6 +218,28 @@ async function executePublishMemory(
188
218
  );
189
219
  }
190
220
 
221
+ // Check if memory has already been published
222
+ if (originalMemory.properties.space_memory_id) {
223
+ const requestedSpaces = request.payload.spaces?.join(', ') || 'unknown';
224
+ logger.warn('Memory already published', {
225
+ function: 'executePublishMemory',
226
+ memoryId: request.payload.memory_id,
227
+ existingSpaceMemoryId: originalMemory.properties.space_memory_id,
228
+ requestedSpaces: request.payload.spaces,
229
+ });
230
+ return JSON.stringify(
231
+ {
232
+ success: false,
233
+ error: 'Already published',
234
+ message: `This memory has already been published to this space. Space memory ID: ${originalMemory.properties.space_memory_id}`,
235
+ space_memory_id: originalMemory.properties.space_memory_id,
236
+ requested_spaces: request.payload.spaces,
237
+ },
238
+ null,
239
+ 2
240
+ );
241
+ }
242
+
191
243
  // Verify ownership again
192
244
  if (originalMemory.properties.user_id !== userId) {
193
245
  logger.warn('Permission denied - wrong owner', {
@@ -264,8 +316,10 @@ async function executePublishMemory(
264
316
 
265
317
  // Insert directly into unified public collection
266
318
  // CRITICAL: Weaviate insert API expects {properties: {...}}, not the properties directly!
267
- const result = await publicCollection.data.insert({
268
- properties: publishedMemory,
319
+ const result = await debug.time('Insert into Memory_public', async () => {
320
+ return await publicCollection.data.insert({
321
+ properties: publishedMemory,
322
+ });
269
323
  });
270
324
 
271
325
  logger.info('Memory published successfully', {
@@ -273,6 +327,11 @@ async function executePublishMemory(
273
327
  spaceMemoryId: result,
274
328
  spaces: request.payload.spaces,
275
329
  });
330
+
331
+ debug.info('Memory published successfully', {
332
+ spaceMemoryId: result,
333
+ spaces: request.payload.spaces,
334
+ });
276
335
 
277
336
  // Update original memory with space_memory_id for bidirectional linking
278
337
  try {
@@ -309,6 +368,10 @@ async function executePublishMemory(
309
368
  2
310
369
  );
311
370
  } catch (error) {
371
+ debug.error('Execute publish failed', {
372
+ error: error instanceof Error ? error.message : String(error),
373
+ stack: error instanceof Error ? error.stack : undefined,
374
+ });
312
375
  handleToolError(error, {
313
376
  toolName: 'remember_confirm',
314
377
  userId,
@@ -12,6 +12,7 @@ import { isValidSpaceId } from '../weaviate/space-schema.js';
12
12
  import { handleToolError } from '../utils/error-handler.js';
13
13
  import { SUPPORTED_SPACES } from '../types/space-memory.js';
14
14
  import { logger } from '../utils/logger.js';
15
+ import { createDebugLogger } from '../utils/debug.js';
15
16
 
16
17
  /**
17
18
  * Tool definition for remember_publish
@@ -62,7 +63,16 @@ export async function handlePublish(
62
63
  args: PublishArgs,
63
64
  userId: string
64
65
  ): Promise<string> {
66
+ const debug = createDebugLogger({
67
+ tool: 'remember_publish',
68
+ userId,
69
+ operation: 'publish_request',
70
+ });
71
+
65
72
  try {
73
+ debug.info('Tool invoked');
74
+ debug.trace('Arguments', { args });
75
+
66
76
  logger.info('Starting publish request', {
67
77
  tool: 'remember_publish',
68
78
  userId,
@@ -73,8 +83,10 @@ export async function handlePublish(
73
83
  });
74
84
 
75
85
  // Validate all space IDs
86
+ debug.debug('Validating space IDs', { spaces: args.spaces });
76
87
  const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
77
88
  if (invalidSpaces.length > 0) {
89
+ debug.warn('Invalid space IDs detected', { invalidSpaces });
78
90
  logger.warn('Invalid space IDs provided', {
79
91
  tool: 'remember_publish',
80
92
  invalidSpaces,
@@ -124,7 +136,9 @@ export async function handlePublish(
124
136
 
125
137
  const userCollection = weaviateClient.collections.get(collectionName);
126
138
 
127
- const memory = await fetchMemoryWithAllProperties(userCollection, args.memory_id);
139
+ const memory = await debug.time('Fetch memory from user collection', async () => {
140
+ return await fetchMemoryWithAllProperties(userCollection, args.memory_id);
141
+ });
128
142
 
129
143
  logger.debug('Memory fetch result', {
130
144
  tool: 'remember_publish',
@@ -232,6 +246,10 @@ export async function handlePublish(
232
246
  2
233
247
  );
234
248
  } catch (error) {
249
+ debug.error('Tool failed', {
250
+ error: error instanceof Error ? error.message : String(error),
251
+ stack: error instanceof Error ? error.stack : undefined,
252
+ });
235
253
  return handleToolError(error, {
236
254
  toolName: 'remember_publish',
237
255
  userId,
@@ -11,6 +11,7 @@ import { getWeaviateClient } from '../weaviate/client.js';
11
11
  import { ensurePublicCollection, isValidSpaceId } from '../weaviate/space-schema.js';
12
12
  import { SUPPORTED_SPACES } from '../types/space-memory.js';
13
13
  import { handleToolError } from '../utils/error-handler.js';
14
+ import { createDebugLogger } from '../utils/debug.js';
14
15
 
15
16
  /**
16
17
  * Tool definition for remember_query_space
@@ -106,8 +107,18 @@ export async function handleQuerySpace(
106
107
  args: QuerySpaceArgs,
107
108
  userId: string // May be used for private spaces in future
108
109
  ): Promise<string> {
110
+ const debug = createDebugLogger({
111
+ tool: 'remember_query_space',
112
+ userId,
113
+ operation: 'query_spaces',
114
+ });
115
+
109
116
  try {
117
+ debug.info('Tool invoked');
118
+ debug.trace('Arguments', { args });
119
+
110
120
  // Validate all space IDs
121
+ debug.debug('Validating space IDs', { spaces: args.spaces });
111
122
  const invalidSpaces = args.spaces.filter(s => !isValidSpaceId(s));
112
123
  if (invalidSpaces.length > 0) {
113
124
  return JSON.stringify(
@@ -181,10 +192,23 @@ export async function handleQuerySpace(
181
192
 
182
193
  const whereFilter = filterList.length > 0 ? Filters.and(...filterList) : undefined;
183
194
 
184
- // Execute semantic search using nearText
185
- const searchResults = await publicCollection.query.nearText(args.question, {
195
+ debug.debug('Executing semantic query', {
196
+ question: args.question,
197
+ filterCount: filterList.length,
186
198
  limit: args.limit || 10,
187
- ...(whereFilter && { where: whereFilter }),
199
+ });
200
+
201
+ // Execute semantic search using nearText
202
+ const searchResults = await debug.time('Semantic query', async () => {
203
+ return await publicCollection.query.nearText(args.question, {
204
+ limit: args.limit || 10,
205
+ ...(whereFilter && { where: whereFilter }),
206
+ });
207
+ });
208
+
209
+ debug.debug('Query completed', {
210
+ resultCount: searchResults.objects.length,
211
+ format: args.format || 'detailed',
188
212
  });
189
213
 
190
214
  // Format results based on requested format
@@ -222,9 +246,18 @@ export async function handleQuerySpace(
222
246
  total: memories.length,
223
247
  };
224
248
 
249
+ debug.info('Tool completed successfully', {
250
+ resultCount: memories.length,
251
+ format: 'detailed',
252
+ });
253
+
225
254
  return JSON.stringify(result, null, 2);
226
255
  }
227
256
  } catch (error) {
257
+ debug.error('Tool failed', {
258
+ error: error instanceof Error ? error.message : String(error),
259
+ stack: error instanceof Error ? error.stack : undefined,
260
+ });
228
261
  return handleToolError(error, {
229
262
  toolName: 'remember_query_space',
230
263
  operation: 'query spaces',
@@ -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',
@@ -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
+ });