@prmichaelsen/remember-mcp 3.15.4 → 3.15.6

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 (132) hide show
  1. package/AGENT.md +363 -5
  2. package/CHANGELOG.md +7 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +50 -0
  5. package/agent/commands/acp.command-create.md +60 -0
  6. package/agent/commands/acp.design-create.md +62 -0
  7. package/agent/commands/acp.design-reference.md +355 -0
  8. package/agent/commands/acp.index.md +423 -0
  9. package/agent/commands/acp.init.md +48 -0
  10. package/agent/commands/acp.package-create.md +1 -0
  11. package/agent/commands/acp.package-info.md +1 -0
  12. package/agent/commands/acp.package-install.md +19 -0
  13. package/agent/commands/acp.package-list.md +1 -0
  14. package/agent/commands/acp.package-publish.md +1 -0
  15. package/agent/commands/acp.package-remove.md +1 -0
  16. package/agent/commands/acp.package-search.md +1 -0
  17. package/agent/commands/acp.package-update.md +1 -0
  18. package/agent/commands/acp.package-validate.md +1 -0
  19. package/agent/commands/acp.pattern-create.md +60 -0
  20. package/agent/commands/acp.plan.md +25 -0
  21. package/agent/commands/acp.proceed.md +621 -75
  22. package/agent/commands/acp.project-create.md +3 -0
  23. package/agent/commands/acp.project-info.md +3 -0
  24. package/agent/commands/acp.project-list.md +3 -1
  25. package/agent/commands/acp.project-set.md +1 -0
  26. package/agent/commands/acp.project-update.md +14 -3
  27. package/agent/commands/acp.projects-restore.md +228 -0
  28. package/agent/commands/acp.projects-sync.md +347 -0
  29. package/agent/commands/acp.report.md +13 -0
  30. package/agent/commands/acp.resume.md +3 -1
  31. package/agent/commands/acp.sessions.md +301 -0
  32. package/agent/commands/acp.status.md +13 -0
  33. package/agent/commands/acp.sync.md +1 -0
  34. package/agent/commands/acp.task-create.md +105 -3
  35. package/agent/commands/acp.update.md +1 -0
  36. package/agent/commands/acp.validate.md +32 -2
  37. package/agent/commands/acp.version-check-for-updates.md +1 -0
  38. package/agent/commands/acp.version-check.md +1 -0
  39. package/agent/commands/acp.version-update.md +1 -0
  40. package/agent/commands/command.template.md +23 -0
  41. package/agent/commands/git.commit.md +1 -0
  42. package/agent/commands/git.init.md +1 -0
  43. package/agent/design/complete-tool-set.md +157 -233
  44. package/agent/design/design.template.md +18 -0
  45. package/agent/design/user-preferences.md +11 -7
  46. package/agent/milestones/milestone-19-new-search-ghost-tools.md +46 -0
  47. package/agent/package.template.yaml +50 -0
  48. package/agent/patterns/pattern.template.md +18 -0
  49. package/agent/progress.yaml +162 -6
  50. package/agent/scripts/acp.common.sh +258 -15
  51. package/agent/scripts/acp.install.sh +91 -4
  52. package/agent/scripts/acp.package-create.sh +0 -1
  53. package/agent/scripts/acp.package-info.sh +19 -1
  54. package/agent/scripts/acp.package-install-optimized.sh +1 -1
  55. package/agent/scripts/acp.package-install.sh +388 -38
  56. package/agent/scripts/acp.package-list.sh +52 -4
  57. package/agent/scripts/acp.package-remove.sh +77 -1
  58. package/agent/scripts/acp.package-search.sh +2 -2
  59. package/agent/scripts/acp.package-update.sh +91 -12
  60. package/agent/scripts/acp.package-validate.sh +136 -1
  61. package/agent/scripts/acp.project-info.sh +34 -11
  62. package/agent/scripts/acp.project-list.sh +4 -0
  63. package/agent/scripts/acp.project-update.sh +66 -19
  64. package/agent/scripts/acp.projects-restore.sh +170 -0
  65. package/agent/scripts/acp.projects-sync.sh +155 -0
  66. package/agent/scripts/acp.sessions.sh +725 -0
  67. package/agent/scripts/acp.version-update.sh +21 -3
  68. package/agent/scripts/acp.yaml-parser.sh +20 -6
  69. package/agent/tasks/milestone-19-new-search-ghost-tools/task-203-create-search-by-tool.md +143 -0
  70. package/agent/tasks/milestone-19-new-search-ghost-tools/task-204-add-new-filters-existing-tools.md +77 -0
  71. package/agent/tasks/milestone-19-new-search-ghost-tools/task-205-add-feel-fields-create-update.md +137 -0
  72. package/agent/tasks/milestone-19-new-search-ghost-tools/task-206-add-byproperty-bysignificance-modes.md +135 -0
  73. package/agent/tasks/milestone-19-new-search-ghost-tools/task-207-add-emotional-composites-search-results.md +88 -0
  74. package/agent/tasks/milestone-19-new-search-ghost-tools/task-208-add-bybroad-byrandom-modes.md +115 -0
  75. package/agent/tasks/milestone-19-new-search-ghost-tools/task-209-create-ghost-memory-tools.md +192 -0
  76. package/agent/tasks/milestone-19-new-search-ghost-tools/task-210-create-get-core-tool.md +203 -0
  77. package/agent/tasks/milestone-19-new-search-ghost-tools/task-211-create-search-space-by-tool.md +182 -0
  78. package/agent/tasks/task-1-{title}.template.md +19 -0
  79. package/agent/tasks/unassigned/bug-report-remember-core-e2e-findings.md +99 -0
  80. package/dist/e2e-helpers.d.ts +26 -0
  81. package/dist/ghost-persona.e2e.d.ts +8 -0
  82. package/dist/memory-crud.e2e.d.ts +8 -0
  83. package/dist/preferences.e2e.d.ts +8 -0
  84. package/dist/relationships.e2e.d.ts +8 -0
  85. package/dist/search-modes.e2e.d.ts +8 -0
  86. package/dist/server-factory.js +1977 -100
  87. package/dist/server.js +1174 -51
  88. package/dist/shared-spaces.e2e.d.ts +8 -0
  89. package/dist/tools/create-ghost-memory.d.ts +70 -0
  90. package/dist/tools/create-memory.d.ts +175 -0
  91. package/dist/tools/get-core.d.ts +28 -0
  92. package/dist/tools/get-core.spec.d.ts +2 -0
  93. package/dist/tools/ghost-tools.spec.d.ts +2 -0
  94. package/dist/tools/query-ghost-memory.d.ts +34 -0
  95. package/dist/tools/query-memory.d.ts +4 -0
  96. package/dist/tools/search-by.d.ts +147 -0
  97. package/dist/tools/search-by.spec.d.ts +2 -0
  98. package/dist/tools/search-ghost-memory-by.d.ts +54 -0
  99. package/dist/tools/search-ghost-memory.d.ts +53 -0
  100. package/dist/tools/search-memory.d.ts +19 -0
  101. package/dist/tools/search-space-by.d.ts +78 -0
  102. package/dist/tools/search-space-by.spec.d.ts +2 -0
  103. package/dist/tools/search-space.d.ts +2 -0
  104. package/dist/tools/update-ghost-memory.d.ts +51 -0
  105. package/dist/tools/update-memory.d.ts +175 -0
  106. package/jest.e2e.config.js +11 -0
  107. package/package.json +2 -2
  108. package/src/e2e-helpers.ts +86 -0
  109. package/src/ghost-persona.e2e.ts +215 -0
  110. package/src/memory-crud.e2e.ts +203 -0
  111. package/src/preferences.e2e.ts +88 -0
  112. package/src/relationships.e2e.ts +156 -0
  113. package/src/search-modes.e2e.ts +184 -0
  114. package/src/server-factory.ts +56 -0
  115. package/src/shared-spaces.e2e.ts +204 -0
  116. package/src/tools/create-ghost-memory.ts +103 -0
  117. package/src/tools/create-memory.ts +45 -1
  118. package/src/tools/get-core.spec.ts +223 -0
  119. package/src/tools/get-core.ts +109 -0
  120. package/src/tools/ghost-tools.spec.ts +361 -0
  121. package/src/tools/query-ghost-memory.ts +63 -0
  122. package/src/tools/query-memory.ts +4 -0
  123. package/src/tools/search-by.spec.ts +325 -0
  124. package/src/tools/search-by.ts +298 -0
  125. package/src/tools/search-ghost-memory-by.ts +80 -0
  126. package/src/tools/search-ghost-memory.ts +73 -0
  127. package/src/tools/search-memory.ts +23 -0
  128. package/src/tools/search-space-by.spec.ts +289 -0
  129. package/src/tools/search-space-by.ts +173 -0
  130. package/src/tools/search-space.ts +20 -1
  131. package/src/tools/update-ghost-memory.ts +86 -0
  132. package/src/tools/update-memory.ts +45 -1
@@ -0,0 +1,109 @@
1
+ /**
2
+ * remember_get_core tool
3
+ * Reads the ghost's core state (mood + perception) from Firestore
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { getDocument } from '../firestore/init.js';
10
+ import { BASE } from '../firestore/paths.js';
11
+
12
+ export const getCoreTool = {
13
+ name: 'remember_get_core',
14
+ description: `Get the ghost's current emotional state and perception model.
15
+
16
+ Returns the ghost's current dimensional state (valence, arousal, confidence,
17
+ social_warmth, coherence, trust), derived emotion labels (dominant_emotion, color),
18
+ directional state (motivation, goal, purpose), and active pressure sources.
19
+
20
+ Optionally includes the ghost's internal model of a specific user (personality,
21
+ communication style, interests, patterns, needs).
22
+
23
+ Use this for introspection — understanding how the ghost feels and why.
24
+ The mood state biases memory retrieval and influences ghost behavior.`,
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {
28
+ include_pressures: {
29
+ type: 'boolean',
30
+ description: "Include active pressure sources with reasons. Default: true",
31
+ },
32
+ include_perception: {
33
+ type: 'string',
34
+ description: "Include the ghost's perception of a specific user (by user_id). Omit to skip. Use owner's user_id for self-perception.",
35
+ },
36
+ },
37
+ },
38
+ };
39
+
40
+ export interface GetCoreArgs {
41
+ include_pressures?: boolean;
42
+ include_perception?: string;
43
+ }
44
+
45
+ export async function handleGetCore(
46
+ args: GetCoreArgs,
47
+ userId: string,
48
+ _authContext?: AuthContext
49
+ ): Promise<string> {
50
+ const debug = createDebugLogger({ tool: 'remember_get_core', userId, operation: 'get core state' });
51
+ try {
52
+ debug.info('Tool invoked');
53
+ debug.trace('Arguments', { args });
54
+
55
+ // Read mood state from Firestore: {BASE}.users/{user_id}/core doc: mood
56
+ const mood = await getDocument(`${BASE}.users/${userId}/core`, 'mood');
57
+
58
+ if (!mood) {
59
+ return JSON.stringify({
60
+ mood: null,
61
+ message: 'No mood state found. Mood is initialized during the first REM cycle.',
62
+ });
63
+ }
64
+
65
+ const result: any = {
66
+ mood: {
67
+ state: mood.state,
68
+ color: mood.color,
69
+ dominant_emotion: mood.dominant_emotion,
70
+ reasoning: mood.reasoning,
71
+ motivation: mood.motivation,
72
+ goal: mood.goal,
73
+ purpose: mood.purpose,
74
+ last_updated: mood.last_updated,
75
+ rem_cycles_since_shift: mood.rem_cycles_since_shift,
76
+ },
77
+ };
78
+
79
+ // Include pressures if requested (default: true)
80
+ if (args.include_pressures !== false && mood.pressures) {
81
+ result.mood.pressures = mood.pressures;
82
+ }
83
+
84
+ // Include threshold flags if any are active
85
+ if (mood.threshold_flags?.length > 0) {
86
+ result.mood.threshold_flags = mood.threshold_flags;
87
+ }
88
+
89
+ // Include perception if requested
90
+ if (args.include_perception) {
91
+ const perception = await getDocument(
92
+ `${BASE}.users/${userId}/core/perceptions`,
93
+ args.include_perception,
94
+ );
95
+ if (perception) {
96
+ result.perception = perception;
97
+ }
98
+ }
99
+
100
+ return JSON.stringify(result, null, 2);
101
+ } catch (error) {
102
+ debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
103
+ handleToolError(error, {
104
+ toolName: 'remember_get_core',
105
+ operation: 'get core state',
106
+ userId,
107
+ });
108
+ }
109
+ }
@@ -0,0 +1,361 @@
1
+ import { createGhostMemoryTool, handleCreateGhostMemory } from './create-ghost-memory.js';
2
+ import { updateGhostMemoryTool, handleUpdateGhostMemory } from './update-ghost-memory.js';
3
+ import { searchGhostMemoryTool, handleSearchGhostMemory } from './search-ghost-memory.js';
4
+ import { queryGhostMemoryTool, handleQueryGhostMemory } from './query-ghost-memory.js';
5
+ import { searchGhostMemoryByTool, handleSearchGhostMemoryBy } from './search-ghost-memory-by.js';
6
+
7
+ // Mock core-services
8
+ const mockCreate = jest.fn();
9
+ const mockUpdate = jest.fn();
10
+ const mockByTime = jest.fn();
11
+
12
+ jest.mock('../core-services.js', () => ({
13
+ createCoreServices: jest.fn(() => ({
14
+ memory: {
15
+ create: mockCreate,
16
+ update: mockUpdate,
17
+ byTime: mockByTime,
18
+ byDensity: jest.fn().mockResolvedValue({ memories: [], total: 0, offset: 0, limit: 10 }),
19
+ byRating: jest.fn().mockResolvedValue({ memories: [], total: 0, offset: 0, limit: 10 }),
20
+ byDiscovery: jest.fn().mockResolvedValue({ memories: [], total: 0, offset: 0, limit: 10 }),
21
+ },
22
+ })),
23
+ }));
24
+
25
+ // Mock search-memory handler
26
+ const mockHandleSearchMemory = jest.fn();
27
+ jest.mock('./search-memory.js', () => ({
28
+ handleSearchMemory: (...args: any[]) => mockHandleSearchMemory(...args),
29
+ }));
30
+
31
+ // Mock query-memory handler
32
+ const mockHandleQueryMemory = jest.fn();
33
+ jest.mock('./query-memory.js', () => ({
34
+ handleQueryMemory: (...args: any[]) => mockHandleQueryMemory(...args),
35
+ }));
36
+
37
+ // Mock weaviate schema for update-ghost-memory
38
+ const mockFetchObjectById = jest.fn();
39
+ jest.mock('../weaviate/schema.js', () => ({
40
+ getMemoryCollection: jest.fn(() => ({
41
+ query: { fetchObjectById: mockFetchObjectById },
42
+ })),
43
+ }));
44
+
45
+ jest.mock('../utils/debug.js', () => ({
46
+ createDebugLogger: () => ({
47
+ info: jest.fn(),
48
+ trace: jest.fn(),
49
+ error: jest.fn(),
50
+ }),
51
+ }));
52
+
53
+ describe('Ghost Memory Tools', () => {
54
+ const userId = 'test-user-1';
55
+
56
+ beforeEach(() => {
57
+ jest.clearAllMocks();
58
+ mockHandleSearchMemory.mockResolvedValue(JSON.stringify({ memories: [], total: 0 }));
59
+ mockHandleQueryMemory.mockResolvedValue(JSON.stringify({ memories: [], total: 0 }));
60
+ mockByTime.mockResolvedValue({ memories: [], total: 0, offset: 0, limit: 10 });
61
+ });
62
+
63
+ // ── create_ghost_memory ──
64
+
65
+ describe('remember_create_ghost_memory', () => {
66
+ it('has correct tool name', () => {
67
+ expect(createGhostMemoryTool.name).toBe('remember_create_ghost_memory');
68
+ });
69
+
70
+ it('requires content', () => {
71
+ expect(createGhostMemoryTool.inputSchema.required).toContain('content');
72
+ });
73
+
74
+ it('hardcodes content_type to ghost', async () => {
75
+ mockCreate.mockResolvedValue({ memory_id: 'mem-1', created_at: '2026-01-01' });
76
+ await handleCreateGhostMemory({ content: 'test ghost' }, userId);
77
+ expect(mockCreate).toHaveBeenCalledWith(
78
+ expect.objectContaining({ type: 'ghost' }),
79
+ );
80
+ });
81
+
82
+ it('adds ghost tag automatically', async () => {
83
+ mockCreate.mockResolvedValue({ memory_id: 'mem-1', created_at: '2026-01-01' });
84
+ await handleCreateGhostMemory({ content: 'test' }, userId);
85
+ expect(mockCreate).toHaveBeenCalledWith(
86
+ expect.objectContaining({
87
+ tags: expect.arrayContaining(['ghost']),
88
+ }),
89
+ );
90
+ });
91
+
92
+ it('adds ghost:{accessor_user_id} tag from auth context', async () => {
93
+ mockCreate.mockResolvedValue({ memory_id: 'mem-1', created_at: '2026-01-01' });
94
+ const authContext = {
95
+ accessToken: 'token',
96
+ credentials: null,
97
+ ghostMode: {
98
+ owner_user_id: 'owner-123',
99
+ accessor_user_id: 'accessor-456',
100
+ accessor_trust_level: 0.7,
101
+ },
102
+ };
103
+ await handleCreateGhostMemory({ content: 'test' }, userId, authContext);
104
+ expect(mockCreate).toHaveBeenCalledWith(
105
+ expect.objectContaining({
106
+ tags: expect.arrayContaining(['ghost', 'ghost:accessor-456']),
107
+ }),
108
+ );
109
+ });
110
+
111
+ it('merges user tags without duplicating ghost', async () => {
112
+ mockCreate.mockResolvedValue({ memory_id: 'mem-1', created_at: '2026-01-01' });
113
+ await handleCreateGhostMemory({ content: 'test', tags: ['ghost', 'custom'] }, userId);
114
+ const callArgs = mockCreate.mock.calls[0][0];
115
+ const ghostCount = callArgs.tags.filter((t: string) => t === 'ghost').length;
116
+ expect(ghostCount).toBe(1);
117
+ expect(callArgs.tags).toContain('custom');
118
+ });
119
+
120
+ it('returns memory_id and content_type in response', async () => {
121
+ mockCreate.mockResolvedValue({ memory_id: 'mem-1', created_at: '2026-01-01' });
122
+ const result = await handleCreateGhostMemory({ content: 'test' }, userId);
123
+ const parsed = JSON.parse(result);
124
+ expect(parsed.memory_id).toBe('mem-1');
125
+ expect(parsed.content_type).toBe('ghost');
126
+ });
127
+
128
+ it('passes feel_* fields through', async () => {
129
+ mockCreate.mockResolvedValue({ memory_id: 'mem-1', created_at: '2026-01-01' });
130
+ await handleCreateGhostMemory(
131
+ { content: 'test', feel_salience: 0.8, feel_social_weight: 0.5 },
132
+ userId,
133
+ );
134
+ expect(mockCreate).toHaveBeenCalledWith(
135
+ expect.objectContaining({ feel_salience: 0.8, feel_social_weight: 0.5 }),
136
+ );
137
+ });
138
+ });
139
+
140
+ // ── update_ghost_memory ──
141
+
142
+ describe('remember_update_ghost_memory', () => {
143
+ it('has correct tool name', () => {
144
+ expect(updateGhostMemoryTool.name).toBe('remember_update_ghost_memory');
145
+ });
146
+
147
+ it('requires memory_id', () => {
148
+ expect(updateGhostMemoryTool.inputSchema.required).toContain('memory_id');
149
+ });
150
+
151
+ it('rejects non-ghost memories', async () => {
152
+ mockFetchObjectById.mockResolvedValue({
153
+ properties: { content_type: 'note' },
154
+ });
155
+ const result = await handleUpdateGhostMemory(
156
+ { memory_id: 'mem-1', content: 'updated' },
157
+ userId,
158
+ );
159
+ const parsed = JSON.parse(result);
160
+ expect(parsed.error).toContain('not a ghost memory');
161
+ });
162
+
163
+ it('returns error for non-existent memory', async () => {
164
+ mockFetchObjectById.mockResolvedValue(null);
165
+ const result = await handleUpdateGhostMemory(
166
+ { memory_id: 'mem-missing' },
167
+ userId,
168
+ );
169
+ const parsed = JSON.parse(result);
170
+ expect(parsed.error).toContain('not found');
171
+ });
172
+
173
+ it('updates ghost memory successfully', async () => {
174
+ mockFetchObjectById.mockResolvedValue({
175
+ properties: { content_type: 'ghost' },
176
+ });
177
+ mockUpdate.mockResolvedValue({
178
+ memory_id: 'mem-1',
179
+ updated_at: '2026-01-01',
180
+ version: 2,
181
+ updated_fields: ['content'],
182
+ });
183
+ const result = await handleUpdateGhostMemory(
184
+ { memory_id: 'mem-1', content: 'updated ghost' },
185
+ userId,
186
+ );
187
+ const parsed = JSON.parse(result);
188
+ expect(parsed.memory_id).toBe('mem-1');
189
+ expect(parsed.updated_fields).toContain('content');
190
+ });
191
+ });
192
+
193
+ // ── search_ghost_memory ──
194
+
195
+ describe('remember_search_ghost_memory', () => {
196
+ it('has correct tool name', () => {
197
+ expect(searchGhostMemoryTool.name).toBe('remember_search_ghost_memory');
198
+ });
199
+
200
+ it('requires query', () => {
201
+ expect(searchGhostMemoryTool.inputSchema.required).toContain('query');
202
+ });
203
+
204
+ it('hardcodes filters.types to ghost', async () => {
205
+ await handleSearchGhostMemory({ query: 'test' }, userId);
206
+ expect(mockHandleSearchMemory).toHaveBeenCalledWith(
207
+ expect.objectContaining({
208
+ filters: expect.objectContaining({ types: ['ghost'] }),
209
+ }),
210
+ userId,
211
+ undefined,
212
+ );
213
+ });
214
+
215
+ it('passes query and alpha through', async () => {
216
+ await handleSearchGhostMemory({ query: 'hello', alpha: 0.5 }, userId);
217
+ expect(mockHandleSearchMemory).toHaveBeenCalledWith(
218
+ expect.objectContaining({ query: 'hello', alpha: 0.5 }),
219
+ userId,
220
+ undefined,
221
+ );
222
+ });
223
+
224
+ it('passes tags through in filters', async () => {
225
+ await handleSearchGhostMemory({ query: 'test', tags: ['important'] }, userId);
226
+ expect(mockHandleSearchMemory).toHaveBeenCalledWith(
227
+ expect.objectContaining({
228
+ filters: expect.objectContaining({ tags: ['important'], types: ['ghost'] }),
229
+ }),
230
+ userId,
231
+ undefined,
232
+ );
233
+ });
234
+
235
+ it('passes limit and offset', async () => {
236
+ await handleSearchGhostMemory({ query: 'test', limit: 20, offset: 5 }, userId);
237
+ expect(mockHandleSearchMemory).toHaveBeenCalledWith(
238
+ expect.objectContaining({ limit: 20, offset: 5 }),
239
+ userId,
240
+ undefined,
241
+ );
242
+ });
243
+
244
+ it('passes deleted_filter', async () => {
245
+ await handleSearchGhostMemory({ query: 'test', deleted_filter: 'only' }, userId);
246
+ expect(mockHandleSearchMemory).toHaveBeenCalledWith(
247
+ expect.objectContaining({ deleted_filter: 'only' }),
248
+ userId,
249
+ undefined,
250
+ );
251
+ });
252
+ });
253
+
254
+ // ── query_ghost_memory ──
255
+
256
+ describe('remember_query_ghost_memory', () => {
257
+ it('has correct tool name', () => {
258
+ expect(queryGhostMemoryTool.name).toBe('remember_query_ghost_memory');
259
+ });
260
+
261
+ it('requires query', () => {
262
+ expect(queryGhostMemoryTool.inputSchema.required).toContain('query');
263
+ });
264
+
265
+ it('hardcodes filters.types to ghost', async () => {
266
+ await handleQueryGhostMemory({ query: 'what happened?' }, userId);
267
+ expect(mockHandleQueryMemory).toHaveBeenCalledWith(
268
+ expect.objectContaining({
269
+ filters: expect.objectContaining({ types: ['ghost'] }),
270
+ }),
271
+ userId,
272
+ undefined,
273
+ );
274
+ });
275
+
276
+ it('passes query through', async () => {
277
+ await handleQueryGhostMemory({ query: 'tell me about interactions' }, userId);
278
+ expect(mockHandleQueryMemory).toHaveBeenCalledWith(
279
+ expect.objectContaining({ query: 'tell me about interactions' }),
280
+ userId,
281
+ undefined,
282
+ );
283
+ });
284
+
285
+ it('passes limit and min_relevance', async () => {
286
+ await handleQueryGhostMemory({ query: 'test', limit: 10, min_relevance: 0.8 }, userId);
287
+ expect(mockHandleQueryMemory).toHaveBeenCalledWith(
288
+ expect.objectContaining({ limit: 10, min_relevance: 0.8 }),
289
+ userId,
290
+ undefined,
291
+ );
292
+ });
293
+
294
+ it('passes authContext through', async () => {
295
+ const authContext = {
296
+ accessToken: 'token',
297
+ credentials: null,
298
+ ghostMode: {
299
+ owner_user_id: 'owner-123',
300
+ accessor_user_id: 'accessor-456',
301
+ accessor_trust_level: 0.7,
302
+ },
303
+ };
304
+ await handleQueryGhostMemory({ query: 'test' }, userId, authContext);
305
+ expect(mockHandleQueryMemory).toHaveBeenCalledWith(
306
+ expect.anything(),
307
+ userId,
308
+ authContext,
309
+ );
310
+ });
311
+ });
312
+
313
+ // ── search_ghost_memory_by ──
314
+
315
+ describe('remember_search_ghost_memory_by', () => {
316
+ it('has correct tool name', () => {
317
+ expect(searchGhostMemoryByTool.name).toBe('remember_search_ghost_memory_by');
318
+ });
319
+
320
+ it('requires mode', () => {
321
+ expect(searchGhostMemoryByTool.inputSchema.required).toContain('mode');
322
+ });
323
+
324
+ it('hardcodes filters.types to ghost for byTime', async () => {
325
+ await handleSearchGhostMemoryBy({ mode: 'byTime' }, userId);
326
+ expect(mockByTime).toHaveBeenCalledWith(
327
+ expect.objectContaining({
328
+ filters: expect.objectContaining({ types: ['ghost'] }),
329
+ }),
330
+ );
331
+ });
332
+
333
+ it('passes sort_order through', async () => {
334
+ await handleSearchGhostMemoryBy({ mode: 'byTime', sort_order: 'asc' }, userId);
335
+ expect(mockByTime).toHaveBeenCalledWith(
336
+ expect.objectContaining({ direction: 'asc' }),
337
+ );
338
+ });
339
+
340
+ it('passes limit and offset', async () => {
341
+ await handleSearchGhostMemoryBy({ mode: 'byTime', limit: 20, offset: 5 }, userId);
342
+ expect(mockByTime).toHaveBeenCalledWith(
343
+ expect.objectContaining({ limit: 20, offset: 5 }),
344
+ );
345
+ });
346
+
347
+ it('passes deleted_filter', async () => {
348
+ await handleSearchGhostMemoryBy({ mode: 'byTime', deleted_filter: 'only' }, userId);
349
+ expect(mockByTime).toHaveBeenCalledWith(
350
+ expect.objectContaining({ deleted_filter: 'only' }),
351
+ );
352
+ });
353
+
354
+ it('has all mode options in schema', () => {
355
+ const modeProp = (searchGhostMemoryByTool.inputSchema.properties as any).mode;
356
+ expect(modeProp.enum).toEqual(
357
+ expect.arrayContaining(['byTime', 'byDensity', 'byRating', 'byDiscovery', 'byProperty', 'bySignificance', 'byRandom', 'byBroad']),
358
+ );
359
+ });
360
+ });
361
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * remember_query_ghost_memory tool
3
+ * Wraps query_memory with hardcoded ghost type filter
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { handleQueryMemory } from './query-memory.js';
10
+
11
+ export const queryGhostMemoryTool = {
12
+ name: 'remember_query_ghost_memory',
13
+ description: `Query ghost memories using natural language (pure semantic search).
14
+ Automatically filters to content_type: ghost.`,
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ query: { type: 'string', description: 'Natural language question' },
19
+ limit: { type: 'number', description: 'Max results. Default: 5' },
20
+ min_relevance: { type: 'number', description: 'Minimum relevance score. Default: 0.6' },
21
+ },
22
+ required: ['query'],
23
+ },
24
+ };
25
+
26
+ export interface QueryGhostMemoryArgs {
27
+ query: string;
28
+ limit?: number;
29
+ min_relevance?: number;
30
+ }
31
+
32
+ export async function handleQueryGhostMemory(
33
+ args: QueryGhostMemoryArgs,
34
+ userId: string,
35
+ authContext?: AuthContext
36
+ ): Promise<string> {
37
+ const debug = createDebugLogger({ tool: 'remember_query_ghost_memory', userId, operation: 'query ghost memories' });
38
+ try {
39
+ debug.info('Tool invoked');
40
+ debug.trace('Arguments', { args });
41
+
42
+ // Delegate to query_memory with ghost type filter hardcoded
43
+ return await handleQueryMemory(
44
+ {
45
+ query: args.query,
46
+ limit: args.limit,
47
+ min_relevance: args.min_relevance,
48
+ filters: {
49
+ types: ['ghost'] as any,
50
+ },
51
+ },
52
+ userId,
53
+ authContext
54
+ );
55
+ } catch (error) {
56
+ debug.error('Tool failed', { error: error instanceof Error ? error.message : String(error) });
57
+ handleToolError(error, {
58
+ toolName: 'remember_query_ghost_memory',
59
+ operation: 'query ghost memories',
60
+ userId,
61
+ });
62
+ }
63
+ }
@@ -102,6 +102,10 @@ export const queryMemoryTool = {
102
102
  type: 'string',
103
103
  description: 'End date (ISO 8601)',
104
104
  },
105
+ rating_min: {
106
+ type: 'number',
107
+ description: 'Minimum Bayesian rating average',
108
+ },
105
109
  },
106
110
  },
107
111
  include_context: {