polydev-ai 1.2.3 → 1.2.5

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.
@@ -0,0 +1,607 @@
1
+ "use strict";
2
+ /**
3
+ * Universal Memory Extraction System
4
+ * Extracts memory from ALL CLI tools with zero-knowledge encryption
5
+ * Preserves existing functionality while adding comprehensive memory support
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.universalMemoryExtractor = exports.UniversalMemoryExtractor = void 0;
12
+ const promises_1 = __importDefault(require("fs/promises"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const server_1 = require("@/app/utils/supabase/server");
16
+ const zeroKnowledgeEncryption_1 = require("./zeroKnowledgeEncryption");
17
+ class UniversalMemoryExtractor {
18
+ constructor() {
19
+ // Memory location patterns for all supported CLI tools
20
+ this.MEMORY_PATTERNS = {
21
+ claude_code: {
22
+ global: [
23
+ '~/.claude/CLAUDE.md',
24
+ '~/.claude/global_memory.md',
25
+ '~/.claude/config.json'
26
+ ],
27
+ project: [
28
+ 'CLAUDE.md',
29
+ '.claude/CLAUDE.md',
30
+ '.claude/project_memory.md'
31
+ ],
32
+ conversations: [
33
+ '~/.claude/projects/*/conversations/*.jsonl',
34
+ '~/.claude/conversations/*.jsonl'
35
+ ],
36
+ config: ['~/.claude/config.json']
37
+ },
38
+ cline: {
39
+ global: [
40
+ '~/.cline/global_memory.md',
41
+ '~/.cline/settings.json',
42
+ '~/.cline/config.json'
43
+ ],
44
+ project: [
45
+ '.cline/project_memory.md',
46
+ '.cline/cline_project.json',
47
+ '.cline/memory.md'
48
+ ],
49
+ conversations: [
50
+ '.cline/conversations/*.json',
51
+ '.cline/chat_history.json'
52
+ ],
53
+ config: ['.cline/cline_config.json', '.cline/settings.json']
54
+ },
55
+ codex_cli: {
56
+ global: [
57
+ '~/.codex/memory.json',
58
+ '~/.codex/config.yaml',
59
+ '~/.codex/global_context.md'
60
+ ],
61
+ project: [
62
+ '.codex/project_context.json',
63
+ '.codex/memory.md',
64
+ '.codex/context.json'
65
+ ],
66
+ conversations: [
67
+ '~/.codex/conversations/*.json',
68
+ '.codex/history.json'
69
+ ],
70
+ config: ['~/.codex/config.json', '~/.codex/config.yaml']
71
+ },
72
+ cursor: {
73
+ global: [
74
+ '~/.cursor/memory.json',
75
+ '~/.cursor/settings.json',
76
+ '~/.cursor/global_context.md'
77
+ ],
78
+ project: [
79
+ '.cursor/project.json',
80
+ '.cursor/workspace_context.json',
81
+ '.cursor/memory.md'
82
+ ],
83
+ conversations: [
84
+ '.cursor/chat_history.json',
85
+ '.cursor/conversations/*.json'
86
+ ],
87
+ config: ['.cursor/settings.json']
88
+ },
89
+ continue: {
90
+ global: [
91
+ '~/.continue/config.json',
92
+ '~/.continue/memory.json',
93
+ '~/.continue/global_context.md'
94
+ ],
95
+ project: [
96
+ '.continue/config.json',
97
+ '.continue/memory.md',
98
+ '.continue/project_context.json'
99
+ ],
100
+ conversations: [
101
+ '.continue/sessions/*.json',
102
+ '.continue/chat_history.json'
103
+ ],
104
+ config: ['.continue/config.json']
105
+ },
106
+ aider: {
107
+ global: [
108
+ '~/.aider.conf.yml',
109
+ '~/.aider/memory.json',
110
+ '~/.aider/global_context.md'
111
+ ],
112
+ project: [
113
+ '.aider.conf.yml',
114
+ '.aider/project_memory.md',
115
+ '.aider/context.md'
116
+ ],
117
+ conversations: [
118
+ '.aider/chat_history.json',
119
+ '.aider/sessions/*.json'
120
+ ],
121
+ config: ['.aider.conf.yml']
122
+ },
123
+ generic: {
124
+ global: [
125
+ '~/.ai/global_memory.md',
126
+ '~/.ai/config.json'
127
+ ],
128
+ project: [
129
+ 'ai_memory.md',
130
+ 'project_context.md',
131
+ '.ai/memory.json',
132
+ '.ai/context.md'
133
+ ],
134
+ conversations: [
135
+ 'ai_conversations.json',
136
+ '.ai/history.json',
137
+ 'chat_history.json'
138
+ ],
139
+ config: ['~/.ai/config.json']
140
+ }
141
+ };
142
+ this.homeDir = os_1.default.homedir();
143
+ this.encryption = new zeroKnowledgeEncryption_1.ZeroKnowledgeEncryption();
144
+ }
145
+ /**
146
+ * Initialize the memory extractor
147
+ */
148
+ async initialize() {
149
+ await this.encryption.initialize();
150
+ }
151
+ /**
152
+ * Detect all available memory sources for specified CLI tools
153
+ */
154
+ async detectMemorySources(projectPath = process.cwd(), cliTools = ['all']) {
155
+ const sources = [];
156
+ const toolsToCheck = cliTools.includes('all')
157
+ ? Object.keys(this.MEMORY_PATTERNS)
158
+ : cliTools;
159
+ for (const tool of toolsToCheck) {
160
+ const patterns = this.MEMORY_PATTERNS[tool];
161
+ if (!patterns)
162
+ continue;
163
+ // Check global memory sources
164
+ if (patterns.global) {
165
+ for (const pattern of patterns.global) {
166
+ const resolved = this.resolvePath(pattern);
167
+ const exists = await this.fileExists(resolved);
168
+ sources.push({
169
+ cli_tool: tool,
170
+ memory_type: 'global',
171
+ file_path: resolved,
172
+ exists,
173
+ ...(exists && await this.getFileStats(resolved))
174
+ });
175
+ }
176
+ }
177
+ // Check project memory sources
178
+ if (patterns.project) {
179
+ for (const pattern of patterns.project) {
180
+ const resolved = path_1.default.resolve(projectPath, pattern);
181
+ const exists = await this.fileExists(resolved);
182
+ sources.push({
183
+ cli_tool: tool,
184
+ memory_type: 'project',
185
+ file_path: resolved,
186
+ exists,
187
+ ...(exists && await this.getFileStats(resolved))
188
+ });
189
+ }
190
+ }
191
+ // Check conversation sources (simplified glob pattern matching)
192
+ if (patterns.conversations) {
193
+ for (const pattern of patterns.conversations) {
194
+ const conversationSources = await this.findConversationFiles(pattern, projectPath);
195
+ sources.push(...conversationSources.map(filePath => ({
196
+ cli_tool: tool,
197
+ memory_type: 'conversation',
198
+ file_path: filePath,
199
+ exists: true
200
+ })));
201
+ }
202
+ }
203
+ }
204
+ return sources.filter(source => source.exists);
205
+ }
206
+ /**
207
+ * Extract memory from detected sources
208
+ */
209
+ async extractMemory(sources, options = {}) {
210
+ const { encryptContent = true, maxFileSizeKB = 500, relevanceThreshold = 0.3 } = options;
211
+ const extractedMemories = [];
212
+ for (const source of sources) {
213
+ if (!source.exists)
214
+ continue;
215
+ try {
216
+ // Skip large files
217
+ if (source.size_bytes && source.size_bytes > maxFileSizeKB * 1024) {
218
+ console.warn(`[Memory Extractor] Skipping large file: ${source.file_path} (${source.size_bytes} bytes)`);
219
+ continue;
220
+ }
221
+ const content = await promises_1.default.readFile(source.file_path, 'utf-8');
222
+ const relevanceScore = this.calculateRelevanceScore(content, source);
223
+ // Skip low-relevance content
224
+ if (relevanceScore < relevanceThreshold) {
225
+ continue;
226
+ }
227
+ const extractedMemory = {
228
+ source,
229
+ content,
230
+ relevance_score: relevanceScore,
231
+ extraction_method: this.getExtractionMethod(source),
232
+ project_identifier: this.extractProjectIdentifier(source.file_path)
233
+ };
234
+ extractedMemories.push(extractedMemory);
235
+ }
236
+ catch (error) {
237
+ console.warn(`[Memory Extractor] Failed to read ${source.file_path}:`, error);
238
+ continue;
239
+ }
240
+ }
241
+ return extractedMemories.sort((a, b) => b.relevance_score - a.relevance_score);
242
+ }
243
+ /**
244
+ * Extract recent conversations from CLI tools
245
+ */
246
+ async extractRecentConversations(sources, options = {}) {
247
+ const { limit = 10, timeRangeHours = 24, queryContext = '' } = options;
248
+ const conversations = [];
249
+ const cutoffTime = new Date(Date.now() - timeRangeHours * 60 * 60 * 1000);
250
+ const conversationSources = sources.filter(s => s.memory_type === 'conversation');
251
+ for (const source of conversationSources) {
252
+ try {
253
+ const content = await promises_1.default.readFile(source.file_path, 'utf-8');
254
+ const extracted = this.parseConversations(content, source.cli_tool, cutoffTime);
255
+ conversations.push(...extracted);
256
+ }
257
+ catch (error) {
258
+ console.warn(`[Memory Extractor] Failed to parse conversations from ${source.file_path}:`, error);
259
+ }
260
+ }
261
+ // Sort by relevance if query context provided, otherwise by timestamp
262
+ if (queryContext) {
263
+ conversations.forEach(conv => {
264
+ conv.relevance_score = this.calculateConversationRelevance(conv, queryContext);
265
+ });
266
+ conversations.sort((a, b) => (b.relevance_score || 0) - (a.relevance_score || 0));
267
+ }
268
+ else {
269
+ conversations.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
270
+ }
271
+ return conversations.slice(0, limit);
272
+ }
273
+ /**
274
+ * Store extracted memory in database with encryption
275
+ */
276
+ async storeMemory(userId, extractedMemories, useEncryption = true) {
277
+ const supabase = await (0, server_1.createClient)();
278
+ for (const memory of extractedMemories) {
279
+ try {
280
+ let storedContent = memory.content;
281
+ let encryptedContent = null;
282
+ let contentHash;
283
+ if (useEncryption) {
284
+ const encrypted = await this.encryption.encrypt(memory.content);
285
+ encryptedContent = JSON.stringify(encrypted);
286
+ contentHash = await this.encryption.createContentHash(memory.content);
287
+ }
288
+ else {
289
+ contentHash = await this.encryption.createContentHash(memory.content);
290
+ }
291
+ const sourcePathHash = await this.encryption.createContentHash(memory.source.file_path);
292
+ // Insert into user_cli_memory_sources
293
+ await supabase
294
+ .from('user_cli_memory_sources')
295
+ .upsert({
296
+ user_id: userId,
297
+ cli_tool: memory.source.cli_tool,
298
+ memory_type: memory.source.memory_type,
299
+ source_path_hash: sourcePathHash,
300
+ encrypted_content: encryptedContent,
301
+ content_hash: contentHash,
302
+ file_count: 1,
303
+ last_modified: memory.source.last_modified?.toISOString(),
304
+ extraction_method: memory.extraction_method,
305
+ relevance_score: memory.relevance_score,
306
+ project_identifier: memory.project_identifier,
307
+ encryption_version: useEncryption ? 1 : 0
308
+ }, {
309
+ onConflict: 'user_id,cli_tool,memory_type,source_path_hash'
310
+ });
311
+ // Log the extraction for audit
312
+ await this.logMemoryAccess(userId, {
313
+ action_type: 'extract',
314
+ cli_tool: memory.source.cli_tool,
315
+ memory_type: memory.source.memory_type,
316
+ sources_processed: 1,
317
+ bytes_processed: memory.content.length,
318
+ privacy_mode: useEncryption
319
+ });
320
+ }
321
+ catch (error) {
322
+ console.error(`[Memory Extractor] Failed to store memory for ${memory.source.file_path}:`, error);
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * Store conversations with encryption
328
+ */
329
+ async storeConversations(userId, conversations, useEncryption = true) {
330
+ const supabase = await (0, server_1.createClient)();
331
+ for (const conv of conversations) {
332
+ try {
333
+ let encryptedUserMessage = null;
334
+ let encryptedAssistantResponse = null;
335
+ let conversationHash;
336
+ const conversationText = `${conv.user_message}\n\n${conv.assistant_response}`;
337
+ if (useEncryption) {
338
+ const encryptedUser = await this.encryption.encrypt(conv.user_message);
339
+ const encryptedAssistant = await this.encryption.encrypt(conv.assistant_response);
340
+ encryptedUserMessage = JSON.stringify(encryptedUser);
341
+ encryptedAssistantResponse = JSON.stringify(encryptedAssistant);
342
+ conversationHash = await this.encryption.createContentHash(conversationText);
343
+ }
344
+ else {
345
+ conversationHash = await this.encryption.createContentHash(conversationText);
346
+ }
347
+ // Insert into mcp_conversation_memory (extending existing table)
348
+ await supabase
349
+ .from('mcp_conversation_memory')
350
+ .upsert({
351
+ user_id: userId,
352
+ user_message: useEncryption ? '' : conv.user_message,
353
+ assistant_response: useEncryption ? '' : conv.assistant_response,
354
+ encrypted_user_message: encryptedUserMessage,
355
+ encrypted_assistant_response: encryptedAssistantResponse,
356
+ model_used: conv.model_used || 'unknown',
357
+ tokens_used: conv.tokens_used || 0,
358
+ conversation_hash: conversationHash,
359
+ encryption_version: useEncryption ? 1 : 0,
360
+ privacy_mode: useEncryption,
361
+ created_at: conv.timestamp.toISOString()
362
+ }, {
363
+ onConflict: 'conversation_hash'
364
+ });
365
+ }
366
+ catch (error) {
367
+ console.error('[Memory Extractor] Failed to store conversation:', error);
368
+ }
369
+ }
370
+ }
371
+ /**
372
+ * Get comprehensive memory context for injection
373
+ */
374
+ async getContextForInjection(userId, queryContext = '', options = {}) {
375
+ const supabase = await (0, server_1.createClient)();
376
+ const { maxMemoryKB = 50, maxConversations = 6 } = options;
377
+ let memory = '';
378
+ let conversations = '';
379
+ let totalSize = 0;
380
+ const sourcesUsed = [];
381
+ try {
382
+ // Get relevant memory sources
383
+ const { data: memorySources } = await supabase
384
+ .from('user_cli_memory_sources')
385
+ .select('*')
386
+ .eq('user_id', userId)
387
+ .gte('relevance_score', 0.5)
388
+ .order('relevance_score', { ascending: false })
389
+ .limit(20);
390
+ if (memorySources) {
391
+ for (const source of memorySources) {
392
+ if (totalSize > maxMemoryKB * 1024)
393
+ break;
394
+ let content = '';
395
+ if (source.encrypted_content) {
396
+ try {
397
+ const encryptedData = JSON.parse(source.encrypted_content);
398
+ content = await this.encryption.decrypt(encryptedData);
399
+ }
400
+ catch (error) {
401
+ console.warn('[Memory Context] Failed to decrypt memory source:', error);
402
+ continue;
403
+ }
404
+ }
405
+ if (content) {
406
+ const contextEntry = `[${source.cli_tool}:${source.memory_type}]\n${content}\n\n`;
407
+ if (totalSize + contextEntry.length <= maxMemoryKB * 1024) {
408
+ memory += contextEntry;
409
+ totalSize += contextEntry.length;
410
+ sourcesUsed.push(`${source.cli_tool}:${source.memory_type}`);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ // Get relevant conversations
416
+ const { data: recentConversations } = await supabase
417
+ .from('mcp_conversation_memory')
418
+ .select('*')
419
+ .eq('user_id', userId)
420
+ .order('created_at', { ascending: false })
421
+ .limit(maxConversations);
422
+ if (recentConversations) {
423
+ for (const conv of recentConversations.slice(0, maxConversations)) {
424
+ let userMsg = conv.user_message;
425
+ let assistantResp = conv.assistant_response;
426
+ if (conv.encrypted_user_message && conv.encrypted_assistant_response) {
427
+ try {
428
+ const encryptedUser = JSON.parse(conv.encrypted_user_message);
429
+ const encryptedAssistant = JSON.parse(conv.encrypted_assistant_response);
430
+ userMsg = await this.encryption.decrypt(encryptedUser);
431
+ assistantResp = await this.encryption.decrypt(encryptedAssistant);
432
+ }
433
+ catch (error) {
434
+ console.warn('[Memory Context] Failed to decrypt conversation:', error);
435
+ continue;
436
+ }
437
+ }
438
+ const convEntry = `[Conversation ${conv.created_at}]\nUser: ${userMsg}\nAssistant: ${assistantResp}\n\n`;
439
+ conversations += convEntry;
440
+ }
441
+ }
442
+ }
443
+ catch (error) {
444
+ console.error('[Memory Context] Failed to get context:', error);
445
+ }
446
+ return {
447
+ memory: memory.trim(),
448
+ conversations: conversations.trim(),
449
+ totalSizeKB: Math.round(totalSize / 1024),
450
+ sourcesUsed
451
+ };
452
+ }
453
+ // Private helper methods
454
+ resolvePath(pathPattern) {
455
+ return pathPattern.replace('~', this.homeDir);
456
+ }
457
+ async fileExists(filePath) {
458
+ try {
459
+ await promises_1.default.access(filePath);
460
+ return true;
461
+ }
462
+ catch {
463
+ return false;
464
+ }
465
+ }
466
+ async getFileStats(filePath) {
467
+ const stats = await promises_1.default.stat(filePath);
468
+ return {
469
+ size_bytes: stats.size,
470
+ last_modified: stats.mtime
471
+ };
472
+ }
473
+ async findConversationFiles(pattern, projectPath) {
474
+ const files = [];
475
+ try {
476
+ const resolved = pattern.includes('~') ? this.resolvePath(pattern) : path_1.default.resolve(projectPath, pattern);
477
+ const dir = path_1.default.dirname(resolved.replace('*', ''));
478
+ if (await this.fileExists(dir)) {
479
+ const entries = await promises_1.default.readdir(dir);
480
+ for (const entry of entries) {
481
+ const fullPath = path_1.default.join(dir, entry);
482
+ const stats = await promises_1.default.stat(fullPath);
483
+ if (stats.isFile() && (entry.endsWith('.json') || entry.endsWith('.jsonl'))) {
484
+ files.push(fullPath);
485
+ }
486
+ }
487
+ }
488
+ }
489
+ catch (error) {
490
+ // Silently handle directory access errors
491
+ }
492
+ return files;
493
+ }
494
+ calculateRelevanceScore(content, source) {
495
+ let score = 0.5; // Base score
496
+ // Boost for project-level memory
497
+ if (source.memory_type === 'project')
498
+ score += 0.2;
499
+ // Boost for recent conversations
500
+ if (source.memory_type === 'conversation')
501
+ score += 0.1;
502
+ // Boost for certain CLI tools
503
+ if (['claude_code', 'cursor', 'cline'].includes(source.cli_tool))
504
+ score += 0.1;
505
+ // Content quality indicators
506
+ if (content.includes('TODO') || content.includes('FIXME'))
507
+ score += 0.1;
508
+ if (content.includes('pattern') || content.includes('decision'))
509
+ score += 0.1;
510
+ if (content.length > 1000 && content.length < 10000)
511
+ score += 0.1;
512
+ return Math.min(score, 1.0);
513
+ }
514
+ getExtractionMethod(source) {
515
+ const ext = path_1.default.extname(source.file_path).toLowerCase();
516
+ switch (ext) {
517
+ case '.md': return 'markdown_parse';
518
+ case '.json': return 'json_parse';
519
+ case '.jsonl': return 'jsonl_parse';
520
+ case '.yml':
521
+ case '.yaml': return 'yaml_parse';
522
+ default: return 'text_parse';
523
+ }
524
+ }
525
+ extractProjectIdentifier(filePath) {
526
+ const parts = filePath.split(path_1.default.sep);
527
+ const projectIndex = parts.findIndex(part => !part.startsWith('.') && part !== 'home' && part !== 'Users');
528
+ return projectIndex >= 0 ? parts.slice(projectIndex, projectIndex + 2).join('/') : 'unknown';
529
+ }
530
+ parseConversations(content, cliTool, cutoffTime) {
531
+ const conversations = [];
532
+ try {
533
+ if (cliTool === 'claude_code' && content.includes('"type":"message"')) {
534
+ // Parse Claude Code JSONL format
535
+ const lines = content.split('\n').filter(line => line.trim());
536
+ for (const line of lines) {
537
+ try {
538
+ const entry = JSON.parse(line);
539
+ if (entry.type === 'message' && entry.timestamp) {
540
+ const timestamp = new Date(entry.timestamp);
541
+ if (timestamp > cutoffTime) {
542
+ conversations.push({
543
+ timestamp,
544
+ user_message: entry.content || '',
545
+ assistant_response: entry.response || '',
546
+ model_used: entry.model,
547
+ tokens_used: entry.tokens
548
+ });
549
+ }
550
+ }
551
+ }
552
+ catch { }
553
+ }
554
+ }
555
+ else if (content.startsWith('[') || content.startsWith('{')) {
556
+ // Parse JSON format conversations
557
+ const data = JSON.parse(content);
558
+ const messages = Array.isArray(data) ? data : data.messages || [];
559
+ for (let i = 0; i < messages.length - 1; i += 2) {
560
+ const userMsg = messages[i];
561
+ const assistantMsg = messages[i + 1];
562
+ if (userMsg && assistantMsg) {
563
+ const timestamp = new Date(userMsg.timestamp || assistantMsg.timestamp || Date.now());
564
+ if (timestamp > cutoffTime) {
565
+ conversations.push({
566
+ timestamp,
567
+ user_message: userMsg.content || userMsg.text || '',
568
+ assistant_response: assistantMsg.content || assistantMsg.text || '',
569
+ model_used: assistantMsg.model,
570
+ tokens_used: assistantMsg.tokens
571
+ });
572
+ }
573
+ }
574
+ }
575
+ }
576
+ }
577
+ catch (error) {
578
+ console.warn(`[Memory Extractor] Failed to parse conversations for ${cliTool}:`, error);
579
+ }
580
+ return conversations;
581
+ }
582
+ calculateConversationRelevance(conversation, queryContext) {
583
+ if (!queryContext)
584
+ return 0.5;
585
+ const text = `${conversation.user_message} ${conversation.assistant_response}`.toLowerCase();
586
+ const queryWords = queryContext.toLowerCase().split(/\s+/);
587
+ const matches = queryWords.filter(word => text.includes(word)).length;
588
+ return Math.min(matches / queryWords.length, 1.0);
589
+ }
590
+ async logMemoryAccess(userId, logData) {
591
+ const supabase = await (0, server_1.createClient)();
592
+ try {
593
+ await supabase
594
+ .from('user_memory_audit_log')
595
+ .insert({
596
+ user_id: userId,
597
+ ...logData
598
+ });
599
+ }
600
+ catch (error) {
601
+ console.error('[Memory Extractor] Failed to log memory access:', error);
602
+ }
603
+ }
604
+ }
605
+ exports.UniversalMemoryExtractor = UniversalMemoryExtractor;
606
+ // Export singleton instance
607
+ exports.universalMemoryExtractor = new UniversalMemoryExtractor();