@prmichaelsen/remember-mcp 0.1.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 (95) hide show
  1. package/.env.example +65 -0
  2. package/AGENT.md +840 -0
  3. package/README.md +72 -0
  4. package/agent/design/.gitkeep +0 -0
  5. package/agent/design/access-control-result-pattern.md +458 -0
  6. package/agent/design/action-audit-memory-types.md +637 -0
  7. package/agent/design/common-template-fields.md +282 -0
  8. package/agent/design/complete-tool-set.md +407 -0
  9. package/agent/design/content-types-expansion.md +521 -0
  10. package/agent/design/cross-database-id-strategy.md +358 -0
  11. package/agent/design/default-template-library.md +423 -0
  12. package/agent/design/firestore-wrapper-analysis.md +606 -0
  13. package/agent/design/llm-provider-abstraction.md +691 -0
  14. package/agent/design/location-handling-architecture.md +523 -0
  15. package/agent/design/memory-templates-design.md +364 -0
  16. package/agent/design/permissions-storage-architecture.md +680 -0
  17. package/agent/design/relationship-storage-strategy.md +361 -0
  18. package/agent/design/remember-mcp-implementation-tasks.md +417 -0
  19. package/agent/design/remember-mcp-progress.yaml +141 -0
  20. package/agent/design/requirements-enhancements.md +468 -0
  21. package/agent/design/requirements.md +56 -0
  22. package/agent/design/template-storage-strategy.md +412 -0
  23. package/agent/design/template-suggestion-system.md +853 -0
  24. package/agent/design/trust-escalation-prevention.md +343 -0
  25. package/agent/design/trust-system-implementation.md +592 -0
  26. package/agent/design/user-preferences.md +683 -0
  27. package/agent/design/weaviate-collection-strategy.md +461 -0
  28. package/agent/milestones/.gitkeep +0 -0
  29. package/agent/milestones/milestone-1-project-foundation.md +121 -0
  30. package/agent/milestones/milestone-2-core-memory-system.md +150 -0
  31. package/agent/milestones/milestone-3-relationships-graph.md +116 -0
  32. package/agent/milestones/milestone-4-user-preferences.md +103 -0
  33. package/agent/milestones/milestone-5-template-system.md +126 -0
  34. package/agent/milestones/milestone-6-auth-multi-tenancy.md +124 -0
  35. package/agent/milestones/milestone-7-trust-permissions.md +133 -0
  36. package/agent/milestones/milestone-8-testing-quality.md +137 -0
  37. package/agent/milestones/milestone-9-deployment-documentation.md +147 -0
  38. package/agent/patterns/.gitkeep +0 -0
  39. package/agent/patterns/bootstrap.md +1271 -0
  40. package/agent/patterns/firebase-admin-sdk-v8-usage.md +950 -0
  41. package/agent/patterns/firestore-users-pattern-best-practices.md +347 -0
  42. package/agent/patterns/library-services.md +454 -0
  43. package/agent/patterns/testing-colocated.md +316 -0
  44. package/agent/progress.yaml +395 -0
  45. package/agent/tasks/.gitkeep +0 -0
  46. package/agent/tasks/task-1-initialize-project-structure.md +266 -0
  47. package/agent/tasks/task-2-install-dependencies.md +199 -0
  48. package/agent/tasks/task-3-setup-weaviate-client.md +330 -0
  49. package/agent/tasks/task-4-setup-firestore-client.md +362 -0
  50. package/agent/tasks/task-5-create-basic-mcp-server.md +114 -0
  51. package/agent/tasks/task-6-create-integration-tests.md +195 -0
  52. package/agent/tasks/task-7-finalize-milestone-1.md +363 -0
  53. package/agent/tasks/task-8-setup-utility-scripts.md +382 -0
  54. package/agent/tasks/task-9-create-server-factory.md +404 -0
  55. package/dist/config.d.ts +26 -0
  56. package/dist/constants/content-types.d.ts +60 -0
  57. package/dist/firestore/init.d.ts +14 -0
  58. package/dist/firestore/paths.d.ts +53 -0
  59. package/dist/firestore/paths.spec.d.ts +2 -0
  60. package/dist/server-factory.d.ts +40 -0
  61. package/dist/server-factory.js +1741 -0
  62. package/dist/server-factory.spec.d.ts +2 -0
  63. package/dist/server.d.ts +3 -0
  64. package/dist/server.js +1690 -0
  65. package/dist/tools/create-memory.d.ts +94 -0
  66. package/dist/tools/delete-memory.d.ts +47 -0
  67. package/dist/tools/search-memory.d.ts +88 -0
  68. package/dist/types/memory.d.ts +183 -0
  69. package/dist/utils/logger.d.ts +7 -0
  70. package/dist/weaviate/client.d.ts +39 -0
  71. package/dist/weaviate/client.spec.d.ts +2 -0
  72. package/dist/weaviate/schema.d.ts +29 -0
  73. package/esbuild.build.js +60 -0
  74. package/esbuild.watch.js +25 -0
  75. package/jest.config.js +31 -0
  76. package/jest.e2e.config.js +17 -0
  77. package/package.json +68 -0
  78. package/src/.gitkeep +0 -0
  79. package/src/config.ts +56 -0
  80. package/src/constants/content-types.ts +454 -0
  81. package/src/firestore/init.ts +68 -0
  82. package/src/firestore/paths.spec.ts +75 -0
  83. package/src/firestore/paths.ts +124 -0
  84. package/src/server-factory.spec.ts +60 -0
  85. package/src/server-factory.ts +215 -0
  86. package/src/server.ts +243 -0
  87. package/src/tools/create-memory.ts +198 -0
  88. package/src/tools/delete-memory.ts +126 -0
  89. package/src/tools/search-memory.ts +216 -0
  90. package/src/types/memory.ts +276 -0
  91. package/src/utils/logger.ts +42 -0
  92. package/src/weaviate/client.spec.ts +58 -0
  93. package/src/weaviate/client.ts +114 -0
  94. package/src/weaviate/schema.ts +288 -0
  95. package/tsconfig.json +26 -0
@@ -0,0 +1,330 @@
1
+ # Task 3: Set Up Weaviate Client
2
+
3
+ **Milestone**: M1 - Project Foundation
4
+ **Estimated Time**: 3 hours
5
+ **Dependencies**: Task 2 ✅
6
+ **Status**: ✅ COMPLETED (2026-02-11)
7
+
8
+ ---
9
+
10
+ ## Objective
11
+
12
+ Create Weaviate client wrapper with connection management and user-scoped collection handling.
13
+
14
+ ---
15
+
16
+ ## Steps
17
+
18
+ ### 1. Create Weaviate Client Wrapper
19
+
20
+ **src/weaviate/client.ts**:
21
+ ```typescript
22
+ import weaviate, { WeaviateClient, ApiKey } from 'weaviate-client';
23
+ import { config } from '../config.js';
24
+
25
+ let client: WeaviateClient | null = null;
26
+
27
+ /**
28
+ * Initialize Weaviate client
29
+ */
30
+ export async function initWeaviateClient(): Promise<WeaviateClient> {
31
+ if (client) {
32
+ return client;
33
+ }
34
+
35
+ const clientConfig: any = {
36
+ scheme: config.weaviate.url.startsWith('https') ? 'https' : 'http',
37
+ host: config.weaviate.url.replace(/^https?:\/\//, ''),
38
+ };
39
+
40
+ if (config.weaviate.apiKey) {
41
+ clientConfig.apiKey = new ApiKey(config.weaviate.apiKey);
42
+ }
43
+
44
+ client = await weaviate.client(clientConfig);
45
+
46
+ console.log('[Weaviate] Client initialized');
47
+ return client;
48
+ }
49
+
50
+ /**
51
+ * Get Weaviate client instance
52
+ */
53
+ export function getWeaviateClient(): WeaviateClient {
54
+ if (!client) {
55
+ throw new Error('Weaviate client not initialized. Call initWeaviateClient() first.');
56
+ }
57
+ return client;
58
+ }
59
+
60
+ /**
61
+ * Test Weaviate connection
62
+ */
63
+ export async function testWeaviateConnection(): Promise<boolean> {
64
+ try {
65
+ const client = getWeaviateClient();
66
+ const meta = await client.misc.metaGetter().do();
67
+ console.log('[Weaviate] Connection successful:', meta.version);
68
+ return true;
69
+ } catch (error) {
70
+ console.error('[Weaviate] Connection failed:', error);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Sanitize user_id for collection name
77
+ * Weaviate collection names must start with uppercase and contain only alphanumeric
78
+ */
79
+ export function sanitizeUserId(userId: string): string {
80
+ // Remove special characters, keep alphanumeric
81
+ const sanitized = userId.replace(/[^a-zA-Z0-9]/g, '_');
82
+ // Ensure starts with uppercase
83
+ return sanitized.charAt(0).toUpperCase() + sanitized.slice(1);
84
+ }
85
+
86
+ /**
87
+ * Get collection name for user's memories
88
+ */
89
+ export function getMemoryCollectionName(userId: string): string {
90
+ return `Memory_${sanitizeUserId(userId)}`;
91
+ }
92
+
93
+ /**
94
+ * Get collection name for user's templates
95
+ */
96
+ export function getTemplateCollectionName(userId: string): string {
97
+ return `Template_${sanitizeUserId(userId)}`;
98
+ }
99
+
100
+ /**
101
+ * Get collection name for user's audit logs
102
+ */
103
+ export function getAuditCollectionName(userId: string): string {
104
+ return `Audit_${sanitizeUserId(userId)}`;
105
+ }
106
+
107
+ /**
108
+ * Check if collection exists
109
+ */
110
+ export async function collectionExists(collectionName: string): Promise<boolean> {
111
+ try {
112
+ const client = getWeaviateClient();
113
+ const schema = await client.schema.getter().do();
114
+ return schema.classes?.some((c: any) => c.class === collectionName) ?? false;
115
+ } catch (error) {
116
+ console.error(`[Weaviate] Error checking collection ${collectionName}:`, error);
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Close Weaviate client connection
123
+ */
124
+ export async function closeWeaviateClient(): Promise<void> {
125
+ if (client) {
126
+ // Weaviate client doesn't have explicit close method
127
+ client = null;
128
+ console.log('[Weaviate] Client closed');
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### 2. Create Configuration Module
134
+
135
+ **src/config.ts**:
136
+ ```typescript
137
+ import dotenv from 'dotenv';
138
+
139
+ dotenv.config();
140
+
141
+ export const config = {
142
+ // Weaviate
143
+ weaviate: {
144
+ url: process.env.WEAVIATE_URL || 'http://localhost:8080',
145
+ apiKey: process.env.WEAVIATE_API_KEY || '',
146
+ },
147
+
148
+ // OpenAI
149
+ openai: {
150
+ apiKey: process.env.OPENAI_APIKEY || '',
151
+ },
152
+
153
+ // Firebase
154
+ firebase: {
155
+ projectId: process.env.FIREBASE_PROJECT_ID || '',
156
+ credentialsPath: process.env.GOOGLE_APPLICATION_CREDENTIALS || './serviceAccount.json',
157
+ },
158
+
159
+ // Server
160
+ server: {
161
+ port: parseInt(process.env.PORT || '3000', 10),
162
+ nodeEnv: process.env.NODE_ENV || 'development',
163
+ logLevel: process.env.LOG_LEVEL || 'info',
164
+ },
165
+
166
+ // MCP
167
+ mcp: {
168
+ transport: process.env.MCP_TRANSPORT || 'sse',
169
+ },
170
+ } as const;
171
+
172
+ /**
173
+ * Validate required configuration
174
+ */
175
+ export function validateConfig(): void {
176
+ const required = [
177
+ { key: 'WEAVIATE_URL', value: config.weaviate.url },
178
+ { key: 'OPENAI_APIKEY', value: config.openai.apiKey },
179
+ { key: 'FIREBASE_PROJECT_ID', value: config.firebase.projectId },
180
+ ];
181
+
182
+ const missing = required.filter((r) => !r.value);
183
+
184
+ if (missing.length > 0) {
185
+ throw new Error(
186
+ `Missing required environment variables: ${missing.map((m) => m.key).join(', ')}`
187
+ );
188
+ }
189
+
190
+ console.log('[Config] Configuration validated');
191
+ }
192
+ ```
193
+
194
+ ### 3. Create Logger Utility
195
+
196
+ **src/utils/logger.ts**:
197
+ ```typescript
198
+ import { config } from '../config.js';
199
+
200
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
201
+
202
+ const LOG_LEVELS: Record<LogLevel, number> = {
203
+ debug: 0,
204
+ info: 1,
205
+ warn: 2,
206
+ error: 3,
207
+ };
208
+
209
+ const currentLevel = LOG_LEVELS[config.server.logLevel as LogLevel] ?? LOG_LEVELS.info;
210
+
211
+ function shouldLog(level: LogLevel): boolean {
212
+ return LOG_LEVELS[level] >= currentLevel;
213
+ }
214
+
215
+ export const logger = {
216
+ debug: (message: string, ...args: any[]) => {
217
+ if (shouldLog('debug')) {
218
+ console.debug(`[DEBUG] ${message}`, ...args);
219
+ }
220
+ },
221
+
222
+ info: (message: string, ...args: any[]) => {
223
+ if (shouldLog('info')) {
224
+ console.info(`[INFO] ${message}`, ...args);
225
+ }
226
+ },
227
+
228
+ warn: (message: string, ...args: any[]) => {
229
+ if (shouldLog('warn')) {
230
+ console.warn(`[WARN] ${message}`, ...args);
231
+ }
232
+ },
233
+
234
+ error: (message: string, ...args: any[]) => {
235
+ if (shouldLog('error')) {
236
+ console.error(`[ERROR] ${message}`, ...args);
237
+ }
238
+ },
239
+ };
240
+ ```
241
+
242
+ ### 4. Create Test File
243
+
244
+ **tests/unit/weaviate-client.test.ts**:
245
+ ```typescript
246
+ import { describe, it, expect, beforeAll } from 'vitest';
247
+ import {
248
+ initWeaviateClient,
249
+ testWeaviateConnection,
250
+ sanitizeUserId,
251
+ getMemoryCollectionName,
252
+ } from '../../src/weaviate/client.js';
253
+
254
+ describe('Weaviate Client', () => {
255
+ beforeAll(async () => {
256
+ await initWeaviateClient();
257
+ });
258
+
259
+ it('should initialize client', async () => {
260
+ const result = await testWeaviateConnection();
261
+ expect(result).toBe(true);
262
+ });
263
+
264
+ it('should sanitize user IDs', () => {
265
+ expect(sanitizeUserId('user@example.com')).toBe('User_example_com');
266
+ expect(sanitizeUserId('user-123')).toBe('User_123');
267
+ expect(sanitizeUserId('123user')).toBe('_23user');
268
+ });
269
+
270
+ it('should generate collection names', () => {
271
+ expect(getMemoryCollectionName('user123')).toBe('Memory_User123');
272
+ expect(getMemoryCollectionName('user@test.com')).toBe('Memory_User_test_com');
273
+ });
274
+ });
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Verification
280
+
281
+ - [x] src/weaviate/client.ts created
282
+ - [x] src/config.ts created
283
+ - [x] src/utils/logger.ts created
284
+ - [x] Tests created (tests/unit/weaviate-client.test.ts)
285
+ - [x] Can initialize Weaviate client (with Weaviate v3 API)
286
+ - [x] Connection test implemented (skipped - requires Weaviate instance)
287
+ - [x] User ID sanitization works (7/7 tests passing)
288
+ - [x] Collection name generation works (all tests passing)
289
+ - [x] Jest configured for ESM support
290
+
291
+ ## Completion Notes
292
+
293
+ **Completed**: 2026-02-11
294
+
295
+ **What Was Created**:
296
+ - ✅ [`src/config.ts`](../../src/config.ts) - Configuration management with environment variables
297
+ - ✅ [`src/utils/logger.ts`](../../src/utils/logger.ts) - Logging utility with log levels
298
+ - ✅ [`src/weaviate/client.ts`](../../src/weaviate/client.ts) - Weaviate client wrapper with:
299
+ - Client initialization using Weaviate v3 API
300
+ - Connection testing
301
+ - User ID sanitization for collection names
302
+ - Collection name generators (Memory, Template, Audit)
303
+ - Collection existence checking
304
+ - ✅ [`tests/unit/weaviate-client.test.ts`](../../tests/unit/weaviate-client.test.ts) - Unit tests (7 passing, 1 skipped)
305
+ - ✅ Updated [`jest.config.js`](../../jest.config.js) for ESM support
306
+
307
+ **Test Results**: 7 passed, 1 skipped (connection test requires Weaviate instance)
308
+
309
+ **What's Next**:
310
+ - Task 4: Set up Firestore client wrapper
311
+
312
+ ---
313
+
314
+ ## Testing
315
+
316
+ ```bash
317
+ # Run tests
318
+ npm test
319
+
320
+ # Test connection manually
321
+ npm run dev
322
+ # Should see: [Weaviate] Client initialized
323
+ # Should see: [Weaviate] Connection successful
324
+ ```
325
+
326
+ ---
327
+
328
+ ## Next Task
329
+
330
+ Task 4: Set Up Firestore Client
@@ -0,0 +1,362 @@
1
+ # Task 4: Set Up Firestore Client
2
+
3
+ **Milestone**: M1 - Project Foundation
4
+ **Estimated Time**: 2 hours
5
+ **Dependencies**: Task 2 ✅
6
+ **Status**: ✅ COMPLETED (2026-02-11)
7
+
8
+ ---
9
+
10
+ ## Objective
11
+
12
+ Create Firestore initialization helper and path utilities using firebase-admin-sdk-v8 (service layer pattern, no wrapper needed per design decision in [`agent/design/firestore-wrapper-analysis.md`](../../agent/design/firestore-wrapper-analysis.md)).
13
+
14
+ ---
15
+
16
+ ## Steps
17
+
18
+ ### 1. Create Firestore Client Wrapper
19
+
20
+ **src/firestore/client.ts**:
21
+ ```typescript
22
+ import admin from 'firebase-admin';
23
+ import { Firestore, Timestamp } from 'firebase-admin/firestore';
24
+ import { config } from '../config.js';
25
+ import { readFileSync } from 'fs';
26
+
27
+ let firestore: Firestore | null = null;
28
+
29
+ /**
30
+ * Initialize Firebase Admin and Firestore
31
+ */
32
+ export async function initFirestore(): Promise<Firestore> {
33
+ if (firestore) {
34
+ return firestore;
35
+ }
36
+
37
+ try {
38
+ // Read service account key
39
+ const serviceAccount = JSON.parse(
40
+ readFileSync(config.firebase.credentialsPath, 'utf8')
41
+ );
42
+
43
+ // Initialize Firebase Admin
44
+ admin.initializeApp({
45
+ credential: admin.credential.cert(serviceAccount),
46
+ projectId: config.firebase.projectId,
47
+ });
48
+
49
+ firestore = admin.firestore();
50
+
51
+ // Configure Firestore settings
52
+ firestore.settings({
53
+ ignoreUndefinedProperties: true,
54
+ });
55
+
56
+ console.log('[Firestore] Client initialized');
57
+ return firestore;
58
+ } catch (error) {
59
+ console.error('[Firestore] Initialization failed:', error);
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get Firestore instance
66
+ */
67
+ export function getFirestore(): Firestore {
68
+ if (!firestore) {
69
+ throw new Error('Firestore not initialized. Call initFirestore() first.');
70
+ }
71
+ return firestore;
72
+ }
73
+
74
+ /**
75
+ * Test Firestore connection
76
+ */
77
+ export async function testFirestoreConnection(): Promise<boolean> {
78
+ try {
79
+ const db = getFirestore();
80
+ // Try to read a document (will fail gracefully if doesn't exist)
81
+ await db.collection('_health_check').doc('test').get();
82
+ console.log('[Firestore] Connection successful');
83
+ return true;
84
+ } catch (error) {
85
+ console.error('[Firestore] Connection failed:', error);
86
+ return false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Get document from Firestore
92
+ */
93
+ export async function getDocument<T = any>(
94
+ collection: string,
95
+ docId: string
96
+ ): Promise<T | null> {
97
+ try {
98
+ const db = getFirestore();
99
+ const doc = await db.collection(collection).doc(docId).get();
100
+
101
+ if (!doc.exists) {
102
+ return null;
103
+ }
104
+
105
+ return doc.data() as T;
106
+ } catch (error) {
107
+ console.error(`[Firestore] Error getting document ${collection}/${docId}:`, error);
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Set document in Firestore
114
+ */
115
+ export async function setDocument<T = any>(
116
+ collection: string,
117
+ docId: string,
118
+ data: T
119
+ ): Promise<void> {
120
+ try {
121
+ const db = getFirestore();
122
+ await db.collection(collection).doc(docId).set(data);
123
+ } catch (error) {
124
+ console.error(`[Firestore] Error setting document ${collection}/${docId}:`, error);
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Update document in Firestore
131
+ */
132
+ export async function updateDocument<T = any>(
133
+ collection: string,
134
+ docId: string,
135
+ data: Partial<T>
136
+ ): Promise<void> {
137
+ try {
138
+ const db = getFirestore();
139
+ await db.collection(collection).doc(docId).update(data as any);
140
+ } catch (error) {
141
+ console.error(`[Firestore] Error updating document ${collection}/${docId}:`, error);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Delete document from Firestore
148
+ */
149
+ export async function deleteDocument(collection: string, docId: string): Promise<void> {
150
+ try {
151
+ const db = getFirestore();
152
+ await db.collection(collection).doc(docId).delete();
153
+ } catch (error) {
154
+ console.error(`[Firestore] Error deleting document ${collection}/${docId}:`, error);
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Query documents from Firestore
161
+ */
162
+ export async function queryDocuments<T = any>(
163
+ collection: string,
164
+ filters?: Array<{ field: string; operator: FirebaseFirestore.WhereFilterOp; value: any }>
165
+ ): Promise<T[]> {
166
+ try {
167
+ const db = getFirestore();
168
+ let query: FirebaseFirestore.Query = db.collection(collection);
169
+
170
+ if (filters) {
171
+ for (const filter of filters) {
172
+ query = query.where(filter.field, filter.operator, filter.value);
173
+ }
174
+ }
175
+
176
+ const snapshot = await query.get();
177
+ return snapshot.docs.map((doc) => doc.data() as T);
178
+ } catch (error) {
179
+ console.error(`[Firestore] Error querying collection ${collection}:`, error);
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Export Timestamp for use in other modules
186
+ */
187
+ export { Timestamp };
188
+ ```
189
+
190
+ ### 2. Create Firestore Path Helpers
191
+
192
+ **src/firestore/paths.ts**:
193
+ ```typescript
194
+ /**
195
+ * Firestore path helpers following users/{user_id}/ pattern
196
+ */
197
+
198
+ /**
199
+ * Get path to user's preferences document
200
+ */
201
+ export function getUserPreferencesPath(userId: string): string {
202
+ return `users/${userId}/preferences`;
203
+ }
204
+
205
+ /**
206
+ * Get path to user's templates collection
207
+ */
208
+ export function getUserTemplatesPath(userId: string): string {
209
+ return `users/${userId}/templates`;
210
+ }
211
+
212
+ /**
213
+ * Get path to specific user template
214
+ */
215
+ export function getUserTemplatePath(userId: string, templateId: string): string {
216
+ return `users/${userId}/templates/${templateId}`;
217
+ }
218
+
219
+ /**
220
+ * Get path to user's access logs collection
221
+ */
222
+ export function getUserAccessLogsPath(userId: string): string {
223
+ return `users/${userId}/access_logs`;
224
+ }
225
+
226
+ /**
227
+ * Get path to user's trust relationships collection
228
+ */
229
+ export function getUserTrustRelationshipsPath(userId: string): string {
230
+ return `users/${userId}/trust_relationships`;
231
+ }
232
+
233
+ /**
234
+ * Get path to default templates collection
235
+ */
236
+ export function getDefaultTemplatesPath(): string {
237
+ return 'templates/default';
238
+ }
239
+
240
+ /**
241
+ * Get path to specific default template
242
+ */
243
+ export function getDefaultTemplatePath(templateId: string): string {
244
+ return `templates/default/${templateId}`;
245
+ }
246
+
247
+ /**
248
+ * Get path to user permissions (cross-user)
249
+ */
250
+ export function getUserPermissionsPath(ownerUserId: string, accessorUserId: string): string {
251
+ return `user_permissions/${ownerUserId}/allowed_accessors/${accessorUserId}`;
252
+ }
253
+ ```
254
+
255
+ ### 3. Create Test File
256
+
257
+ **tests/unit/firestore-client.test.ts**:
258
+ ```typescript
259
+ import { describe, it, expect, beforeAll } from 'vitest';
260
+ import {
261
+ initFirestore,
262
+ testFirestoreConnection,
263
+ setDocument,
264
+ getDocument,
265
+ deleteDocument,
266
+ } from '../../src/firestore/client.js';
267
+
268
+ describe('Firestore Client', () => {
269
+ beforeAll(async () => {
270
+ await initFirestore();
271
+ });
272
+
273
+ it('should initialize client', async () => {
274
+ const result = await testFirestoreConnection();
275
+ expect(result).toBe(true);
276
+ });
277
+
278
+ it('should set and get document', async () => {
279
+ const testData = { test: 'value', timestamp: new Date().toISOString() };
280
+
281
+ await setDocument('_test', 'test-doc', testData);
282
+ const retrieved = await getDocument('_test', 'test-doc');
283
+
284
+ expect(retrieved).toEqual(testData);
285
+
286
+ // Cleanup
287
+ await deleteDocument('_test', 'test-doc');
288
+ });
289
+
290
+ it('should return null for non-existent document', async () => {
291
+ const result = await getDocument('_test', 'non-existent');
292
+ expect(result).toBeNull();
293
+ });
294
+ });
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Verification
300
+
301
+ - [x] src/firestore/init.ts created (minimal initialization helper)
302
+ - [x] src/firestore/paths.ts created (collection path helpers)
303
+ - [x] Tests created (tests/unit/firestore-paths.test.ts)
304
+ - [x] Can initialize Firestore (initFirestore function)
305
+ - [x] Connection test implemented (testFirestoreConnection)
306
+ - [x] Re-exports firebase-admin-sdk-v8 functions (no wrapper needed)
307
+ - [x] Path helpers work correctly (7 tests passing, 100% coverage)
308
+
309
+ ## Completion Notes
310
+
311
+ **Completed**: 2026-02-11
312
+
313
+ **What Was Created**:
314
+ - ✅ [`src/firestore/init.ts`](../../src/firestore/init.ts) - Minimal initialization helper
315
+ - `initFirestore()` - Initialize firebase-admin-sdk-v8 once
316
+ - `isFirestoreInitialized()` - Check initialization state
317
+ - `testFirestoreConnection()` - Test connection
318
+ - Re-exports all firebase-admin-sdk-v8 functions for convenience
319
+
320
+ - ✅ [`src/firestore/paths.ts`](../../src/firestore/paths.ts) - Collection path helpers
321
+ - `getUserPreferencesPath()` - user_preferences/{userId}
322
+ - `getUserTemplatesPath()` - users/{userId}/templates
323
+ - `getUserPermissionsPath()` - user_permissions/{userId}/allowed_accessors
324
+ - `getUserPermissionPath()` - Specific permission document
325
+ - `getTrustHistoryPath()` - trust_history/{userId}/history
326
+ - `getDefaultTemplatesPath()` - templates/default
327
+ - `getDefaultTemplatePath()` - Specific default template
328
+
329
+ - ✅ [`tests/unit/firestore-paths.test.ts`](../../tests/unit/firestore-paths.test.ts) - Unit tests
330
+ - 7 tests passing
331
+ - 100% code coverage for paths.ts
332
+
333
+ **Design Decision**:
334
+ - ✅ Using firebase-admin-sdk-v8 instead of firebase-admin (edge-compatible)
335
+ - ✅ Service layer pattern (no wrapper) per [`agent/design/firestore-wrapper-analysis.md`](../../agent/design/firestore-wrapper-analysis.md)
336
+ - ✅ Minimal init helper + re-exports (not a full wrapper class)
337
+ - ✅ Path helpers for multi-tenant collection organization
338
+
339
+ **Test Results**: 14 passed, 1 skipped (all Firestore path tests passing)
340
+
341
+ **What's Next**:
342
+ - Task 5: Create basic MCP server
343
+
344
+ ---
345
+
346
+ ## Testing
347
+
348
+ ```bash
349
+ # Run tests
350
+ npm test
351
+
352
+ # Test connection manually
353
+ npm run dev
354
+ # Should see: [Firestore] Client initialized
355
+ # Should see: [Firestore] Connection successful
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Next Task
361
+
362
+ Task 5: Create Basic MCP Server