claude-self-reflect 1.3.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude/agents/README.md +138 -0
  2. package/.claude/agents/docker-orchestrator.md +264 -0
  3. package/.claude/agents/documentation-writer.md +262 -0
  4. package/.claude/agents/import-debugger.md +203 -0
  5. package/.claude/agents/mcp-integration.md +286 -0
  6. package/.claude/agents/open-source-maintainer.md +150 -0
  7. package/.claude/agents/performance-tuner.md +276 -0
  8. package/.claude/agents/qdrant-specialist.md +138 -0
  9. package/.claude/agents/reflection-specialist.md +361 -0
  10. package/.claude/agents/search-optimizer.md +307 -0
  11. package/LICENSE +21 -0
  12. package/README.md +363 -0
  13. package/installer/cli.js +122 -0
  14. package/installer/postinstall.js +13 -0
  15. package/installer/setup-wizard.js +204 -0
  16. package/mcp-server/pyproject.toml +27 -0
  17. package/mcp-server/run-mcp.sh +21 -0
  18. package/mcp-server/src/__init__.py +1 -0
  19. package/mcp-server/src/__main__.py +23 -0
  20. package/mcp-server/src/server.py +316 -0
  21. package/mcp-server/src/server_v2.py +240 -0
  22. package/package.json +12 -36
  23. package/scripts/import-conversations-isolated.py +311 -0
  24. package/scripts/import-conversations-voyage-streaming.py +377 -0
  25. package/scripts/import-conversations-voyage.py +428 -0
  26. package/scripts/import-conversations.py +240 -0
  27. package/scripts/import-current-conversation.py +38 -0
  28. package/scripts/import-live-conversation.py +152 -0
  29. package/scripts/import-openai-enhanced.py +867 -0
  30. package/scripts/import-recent-only.py +29 -0
  31. package/scripts/import-single-project.py +278 -0
  32. package/scripts/import-watcher.py +169 -0
  33. package/config/claude-desktop-config.json +0 -12
  34. package/dist/cli.d.ts +0 -3
  35. package/dist/cli.d.ts.map +0 -1
  36. package/dist/cli.js +0 -55
  37. package/dist/cli.js.map +0 -1
  38. package/dist/embeddings-gemini.d.ts +0 -76
  39. package/dist/embeddings-gemini.d.ts.map +0 -1
  40. package/dist/embeddings-gemini.js +0 -158
  41. package/dist/embeddings-gemini.js.map +0 -1
  42. package/dist/embeddings.d.ts +0 -67
  43. package/dist/embeddings.d.ts.map +0 -1
  44. package/dist/embeddings.js +0 -252
  45. package/dist/embeddings.js.map +0 -1
  46. package/dist/index.d.ts +0 -3
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js +0 -439
  49. package/dist/index.js.map +0 -1
  50. package/dist/project-isolation.d.ts +0 -29
  51. package/dist/project-isolation.d.ts.map +0 -1
  52. package/dist/project-isolation.js +0 -78
  53. package/dist/project-isolation.js.map +0 -1
  54. package/scripts/install-agent.js +0 -70
  55. package/scripts/setup-wizard.js +0 -596
  56. package/src/cli.ts +0 -56
  57. package/src/embeddings-gemini.ts +0 -176
  58. package/src/embeddings.ts +0 -296
  59. package/src/index.ts +0 -513
  60. package/src/project-isolation.ts +0 -93
package/src/index.ts DELETED
@@ -1,513 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import {
5
- CallToolRequestSchema,
6
- ErrorCode,
7
- ListToolsRequestSchema,
8
- McpError,
9
- } from '@modelcontextprotocol/sdk/types.js';
10
- import { QdrantClient } from '@qdrant/js-client-rest';
11
- import { createEmbeddingService, detectEmbeddingModel, EmbeddingService } from './embeddings.js';
12
- import { ProjectIsolationManager, ProjectIsolationConfig, DEFAULT_ISOLATION_CONFIG } from './project-isolation.js';
13
- import * as dotenv from 'dotenv';
14
- import * as path from 'path';
15
- import { fileURLToPath } from 'url';
16
-
17
- // Get __dirname equivalent for ES modules
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = path.dirname(__filename);
20
-
21
- // Load environment variables from parent directory
22
- dotenv.config({ path: path.join(__dirname, '../../.env') });
23
-
24
- const QDRANT_URL = process.env.QDRANT_URL || 'http://localhost:6333';
25
- const COLLECTION_NAME = process.env.COLLECTION_NAME || 'conversations';
26
- const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
27
- const VOYAGE_API_KEY = process.env.VOYAGE_KEY || process.env['VOYAGE_KEY-2'];
28
- const PREFER_LOCAL_EMBEDDINGS = process.env.PREFER_LOCAL_EMBEDDINGS === 'true';
29
- const ISOLATION_MODE = process.env.ISOLATION_MODE || 'hybrid';
30
- const ALLOW_CROSS_PROJECT = process.env.ALLOW_CROSS_PROJECT === 'true';
31
- const ENABLE_MEMORY_DECAY = process.env.ENABLE_MEMORY_DECAY === 'true';
32
- const DECAY_WEIGHT = parseFloat(process.env.DECAY_WEIGHT || '0.3');
33
- const DECAY_SCALE_DAYS = parseFloat(process.env.DECAY_SCALE_DAYS || '90');
34
-
35
- // Debug: Log environment variables on startup
36
- console.error('🚀 MCP Server starting with environment:');
37
- console.error(` - ENABLE_MEMORY_DECAY: ${ENABLE_MEMORY_DECAY} (from env: ${process.env.ENABLE_MEMORY_DECAY})`);
38
- console.error(` - DECAY_WEIGHT: ${DECAY_WEIGHT} (from env: ${process.env.DECAY_WEIGHT})`);
39
- console.error(` - DECAY_SCALE_DAYS: ${DECAY_SCALE_DAYS} (from env: ${process.env.DECAY_SCALE_DAYS})`);
40
-
41
- interface SearchResult {
42
- id: string;
43
- score: number;
44
- timestamp: string;
45
- role: string;
46
- excerpt: string;
47
- projectName?: string;
48
- conversationId?: string;
49
- }
50
-
51
- class SelfReflectionServer {
52
- private server: Server;
53
- private qdrantClient: QdrantClient;
54
- private embeddingService?: EmbeddingService;
55
- private collectionInfo?: { model: string; dimensions: number };
56
- private isolationManager: ProjectIsolationManager;
57
- private currentProject?: string;
58
-
59
- constructor() {
60
- this.server = new Server(
61
- {
62
- name: 'claude-self-reflection',
63
- version: '0.1.0',
64
- },
65
- {
66
- capabilities: {
67
- tools: {},
68
- },
69
- }
70
- );
71
-
72
- this.qdrantClient = new QdrantClient({ url: QDRANT_URL });
73
-
74
- // Initialize project isolation
75
- const isolationConfig: ProjectIsolationConfig = {
76
- mode: ISOLATION_MODE as 'isolated' | 'shared' | 'hybrid',
77
- allowCrossProject: ALLOW_CROSS_PROJECT,
78
- projectIdentifier: ProjectIsolationManager.detectCurrentProject()
79
- };
80
- this.isolationManager = new ProjectIsolationManager(this.qdrantClient, isolationConfig);
81
- this.currentProject = isolationConfig.projectIdentifier;
82
-
83
- this.setupToolHandlers();
84
- }
85
-
86
- private async initialize() {
87
- try {
88
- // Create embedding service with Voyage AI if available
89
- this.embeddingService = await createEmbeddingService({
90
- openaiApiKey: OPENAI_API_KEY,
91
- voyageApiKey: VOYAGE_API_KEY,
92
- preferLocal: PREFER_LOCAL_EMBEDDINGS,
93
- });
94
-
95
- // For Voyage collections, we don't need to check dimensions as they're consistent
96
- if (this.embeddingService.getModelName().includes('voyage')) {
97
- console.error('Using Voyage AI embeddings for search across project collections');
98
- }
99
- } catch (error) {
100
- console.error('Failed to initialize embedding service:', error);
101
- console.error('Server will run in degraded mode with text-based search');
102
- }
103
- }
104
-
105
- private setupToolHandlers() {
106
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
107
- tools: [
108
- {
109
- name: 'reflect_on_past',
110
- description: 'Search for relevant past conversations using semantic search',
111
- inputSchema: {
112
- type: 'object',
113
- properties: {
114
- query: {
115
- type: 'string',
116
- description: 'The search query to find semantically similar conversations',
117
- },
118
- limit: {
119
- type: 'number',
120
- description: 'Maximum number of results to return (default: 5)',
121
- default: 5,
122
- },
123
- project: {
124
- type: 'string',
125
- description: 'Filter by project name (optional)',
126
- },
127
- crossProject: {
128
- type: 'boolean',
129
- description: 'Search across all projects (default: false, requires permission)',
130
- default: false,
131
- },
132
- minScore: {
133
- type: 'number',
134
- description: 'Minimum similarity score (0-1, default: 0.7)',
135
- default: 0.7,
136
- },
137
- useDecay: {
138
- type: 'boolean',
139
- description: 'Apply time-based decay to prioritize recent memories (default: uses environment setting)',
140
- },
141
- },
142
- required: ['query'],
143
- },
144
- },
145
- {
146
- name: 'store_reflection',
147
- description: 'Store an important insight or reflection for future reference',
148
- inputSchema: {
149
- type: 'object',
150
- properties: {
151
- content: {
152
- type: 'string',
153
- description: 'The insight or reflection to store',
154
- },
155
- tags: {
156
- type: 'array',
157
- items: { type: 'string' },
158
- description: 'Tags to categorize this reflection',
159
- },
160
- },
161
- required: ['content'],
162
- },
163
- },
164
- ],
165
- }));
166
-
167
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
168
- if (request.params.name === 'reflect_on_past') {
169
- return this.handleReflectOnPast(request.params.arguments);
170
- } else if (request.params.name === 'store_reflection') {
171
- return this.handleStoreReflection(request.params.arguments);
172
- }
173
-
174
- throw new McpError(
175
- ErrorCode.MethodNotFound,
176
- `Unknown tool: ${request.params.name}`
177
- );
178
- });
179
- }
180
-
181
- private async getVoyageCollections(): Promise<string[]> {
182
- try {
183
- const collections = await this.qdrantClient.getCollections();
184
- return collections.collections
185
- .map(c => c.name)
186
- .filter(name => name.endsWith('_voyage'));
187
- } catch (error) {
188
- console.error('Failed to get Voyage collections:', error);
189
- return [];
190
- }
191
- }
192
-
193
- private async handleReflectOnPast(args: any) {
194
- const { query, limit = 5, project, minScore = 0.7, crossProject = false, useDecay } = args;
195
- const shouldUseDecay = useDecay !== undefined ? useDecay : ENABLE_MEMORY_DECAY;
196
-
197
- // Log debug info but don't return early
198
- console.error(`🔍 DEBUG: MCP handleReflectOnPast called!
199
- - query: "${query}"
200
- - useDecay: ${useDecay} (type: ${typeof useDecay})
201
- - shouldUseDecay: ${shouldUseDecay}
202
- - ENABLE_MEMORY_DECAY: ${ENABLE_MEMORY_DECAY}
203
- - DECAY_WEIGHT: ${DECAY_WEIGHT}
204
- - DECAY_SCALE_DAYS: ${DECAY_SCALE_DAYS}
205
- - minScore: ${minScore}`);
206
-
207
- // Extra debug for decay path
208
- if (shouldUseDecay) {
209
- console.error(`🔄 DECAY PATH SHOULD BE TAKEN - shouldUseDecay is true`);
210
- } else {
211
- console.error(`❌ DECAY PATH NOT TAKEN - shouldUseDecay is false`);
212
- }
213
-
214
- try {
215
- // Initialize if not already done
216
- if (!this.embeddingService) {
217
- await this.initialize();
218
- }
219
-
220
- let results: SearchResult[] = [];
221
-
222
- if (this.embeddingService) {
223
- // Use vector search with embeddings
224
- console.error(`Generating embedding for query: "${query}"`);
225
- const queryEmbedding = await this.embeddingService.generateEmbedding(query);
226
-
227
- // Get all Voyage collections
228
- const voyageCollections = await this.getVoyageCollections();
229
-
230
- if (voyageCollections.length === 0) {
231
- console.error('No Voyage collections found');
232
- return {
233
- content: [
234
- {
235
- type: 'text',
236
- text: `No Voyage collections found. Please run the import process first.`,
237
- },
238
- ],
239
- };
240
- }
241
-
242
- console.error(`Searching across ${voyageCollections.length} Voyage collections: ${voyageCollections.slice(0, 3).join(', ')}...`);
243
-
244
- if (voyageCollections.length === 0) {
245
- console.error(`⚠️ WARNING: No Voyage collections found!`);
246
- return {
247
- content: [{
248
- type: 'text',
249
- text: 'No conversation collections found. Please import conversations first.'
250
- }]
251
- };
252
- }
253
-
254
-
255
- // Search across multiple collections
256
- const searchPromises = voyageCollections.map(async (collectionName) => {
257
- try {
258
- let searchResponse;
259
-
260
- if (shouldUseDecay) {
261
- console.error(`🔄 DECAY MODE ACTIVE for collection ${collectionName}`);
262
- console.error(` - minScore parameter: ${minScore} (will be ignored for initial search)`);
263
- console.error(` - DECAY_WEIGHT: ${DECAY_WEIGHT}`);
264
- console.error(` - DECAY_SCALE_DAYS: ${DECAY_SCALE_DAYS}`);
265
-
266
- // IMPORTANT: No score_threshold for decay mode - we need all results to apply decay
267
- searchResponse = await this.qdrantClient.search(collectionName, {
268
- vector: queryEmbedding,
269
- limit: Math.ceil(limit * 3), // Get more candidates for decay filtering
270
- // NO score_threshold - we'll filter after decay is applied
271
- with_payload: true,
272
- });
273
-
274
- console.error(` - Initial results from ${collectionName}: ${searchResponse.length} items`);
275
- if (searchResponse.length > 0) {
276
- console.error(` - Score range: ${searchResponse[searchResponse.length - 1].score.toFixed(3)} to ${searchResponse[0].score.toFixed(3)}`);
277
- }
278
-
279
- // Apply decay scoring with proper formula
280
- const now = Date.now();
281
- const scaleMs = DECAY_SCALE_DAYS * 24 * 60 * 60 * 1000;
282
-
283
- try {
284
- searchResponse = searchResponse.map((point: any, index: number) => {
285
- let ageMs = 0;
286
- let originalScore = point.score;
287
-
288
- try {
289
- if (point.payload?.timestamp) {
290
- const timestamp = new Date(point.payload.timestamp as string).getTime();
291
- if (!isNaN(timestamp)) {
292
- ageMs = now - timestamp;
293
- } else {
294
- console.error(` - Invalid timestamp for point ${point.id}: ${point.payload.timestamp}`);
295
- }
296
- }
297
-
298
- // Calculate exponential decay factor (newer = higher factor)
299
- const decayFactor = Math.exp(-ageMs / scaleMs);
300
-
301
- // Apply decay formula: base_score + decay_weight * decay_factor
302
- const adjustedScore = originalScore + (DECAY_WEIGHT * decayFactor);
303
-
304
- if (index < 3) { // Log first 3 items for debugging
305
- console.error(` Decay calculation for item ${index + 1}:`);
306
- console.error(` - timestamp: ${point.payload?.timestamp || 'missing'}`);
307
- console.error(` - age: ${(ageMs / 86400000).toFixed(1)} days`);
308
- console.error(` - originalScore: ${originalScore.toFixed(3)}`);
309
- console.error(` - decayFactor: ${decayFactor.toFixed(4)}`);
310
- console.error(` - adjustedScore: ${adjustedScore.toFixed(3)} (boost: +${(adjustedScore - originalScore).toFixed(3)})`);
311
- }
312
-
313
- return {
314
- ...point,
315
- score: adjustedScore
316
- };
317
- } catch (decayError) {
318
- console.error(` Error calculating decay for point ${point.id}:`, decayError);
319
- // Return original point with original score on error
320
- return point;
321
- }
322
- });
323
- } catch (mapError) {
324
- console.error(` Fatal error during decay calculation:`, mapError);
325
- // Fall back to original search results if decay fails completely
326
- searchResponse = searchResponse;
327
- }
328
-
329
- // Apply filtering and sorting
330
- searchResponse = searchResponse
331
- .filter(point => point.score >= minScore) // Apply minScore filter AFTER decay
332
- .sort((a: any, b: any) => b.score - a.score)
333
- .slice(0, limit);
334
-
335
- console.error(` - After decay and filtering (>= ${minScore}): ${searchResponse.length} results`);
336
-
337
- } else {
338
- // Standard search without decay
339
- searchResponse = await this.qdrantClient.search(collectionName, {
340
- vector: queryEmbedding,
341
- limit: Math.ceil(limit * 1.5), // Get extra results per collection
342
- score_threshold: minScore,
343
- with_payload: true,
344
- });
345
- }
346
-
347
- if (searchResponse.length > 0) {
348
- console.error(`Collection ${collectionName}: Found ${searchResponse.length} results`);
349
- }
350
-
351
- return searchResponse.map(point => ({
352
- id: point.id as string,
353
- score: point.score,
354
- timestamp: point.payload?.timestamp as string || new Date().toISOString(),
355
- role: (point.payload?.start_role as string) || (point.payload?.role as string) || 'unknown',
356
- excerpt: ((point.payload?.text as string) || '').substring(0, 500) + '...',
357
- projectName: (point.payload?.project as string) || (point.payload?.project_name as string) || (point.payload?.project_id as string) || collectionName.replace('conv_', '').replace('_voyage', ''),
358
- conversationId: point.payload?.conversation_id as string,
359
- collectionName,
360
- }));
361
- } catch (error) {
362
- console.error(`Failed to search collection ${collectionName}:`, error);
363
- return [];
364
- }
365
- });
366
-
367
- // Wait for all searches to complete
368
- const allResults = await Promise.all(searchPromises);
369
-
370
- // Flatten and sort by score
371
- const flatResults = allResults.flat();
372
- console.error(`Found ${flatResults.length} total results across all collections`);
373
-
374
- results = flatResults
375
- .sort((a, b) => b.score - a.score)
376
- .slice(0, limit);
377
-
378
- } else {
379
- // Fallback to text search
380
- console.error('Using fallback text search (no embeddings available)');
381
-
382
- const voyageCollections = await this.getVoyageCollections();
383
-
384
- // Search across all collections with text matching
385
- const searchPromises = voyageCollections.map(async (collectionName) => {
386
- try {
387
- const scrollResponse = await this.qdrantClient.scroll(collectionName, {
388
- limit: 100,
389
- with_payload: true,
390
- with_vector: false,
391
- });
392
-
393
- const queryWords = query.toLowerCase().split(/\s+/);
394
- return scrollResponse.points
395
- .filter(point => {
396
- const text = (point.payload?.text as string || '').toLowerCase();
397
- return queryWords.some((word: string) => text.includes(word));
398
- })
399
- .map(point => ({
400
- id: point.id as string,
401
- score: 0.5,
402
- timestamp: point.payload?.timestamp as string || new Date().toISOString(),
403
- role: (point.payload?.start_role as string) || (point.payload?.role as string) || 'unknown',
404
- excerpt: ((point.payload?.text as string) || '').substring(0, 500) + '...',
405
- projectName: (point.payload?.project as string) || (point.payload?.project_name as string) || (point.payload?.project_id as string) || collectionName.replace('conv_', '').replace('_voyage', ''),
406
- conversationId: point.payload?.conversation_id as string,
407
- collectionName,
408
- }));
409
- } catch (error) {
410
- console.error(`Failed to search collection ${collectionName}:`, error);
411
- return [];
412
- }
413
- });
414
-
415
- const allResults = await Promise.all(searchPromises);
416
- results = allResults
417
- .flat()
418
- .slice(0, limit);
419
- }
420
-
421
- if (results.length === 0) {
422
- // For debugging, let's check what collections we have and what the search is actually doing
423
- const voyageCollections = await this.getVoyageCollections();
424
- return {
425
- content: [
426
- {
427
- type: 'text',
428
- text: `🔍 DECAY DEBUG: No results for "${query}"
429
-
430
- [DEBUG INFO FROM CLAUDE SELF REFLECT MCP]
431
- - useDecay param: ${useDecay} (type: ${typeof useDecay})
432
- - shouldUseDecay: ${shouldUseDecay}
433
- - minScore: ${minScore}
434
- - embeddingService exists: ${!!this.embeddingService}
435
- - ENABLE_MEMORY_DECAY env: ${ENABLE_MEMORY_DECAY}
436
- - DECAY_WEIGHT env: ${DECAY_WEIGHT}
437
- - collections found: ${voyageCollections.length}
438
- - first 3: ${voyageCollections.slice(0, 3).join(', ')}
439
- - MODE: ${shouldUseDecay ? '🔄 DECAY ACTIVE' : '📊 STANDARD SEARCH'}`,
440
- },
441
- ],
442
- };
443
- }
444
-
445
- const resultText = results
446
- .map((result, i) =>
447
- `**Result ${i + 1}** (Score: ${result.score.toFixed(3)})\n` +
448
- `Time: ${new Date(result.timestamp).toLocaleString()}\n` +
449
- `Project: ${result.projectName || 'unknown'}\n` +
450
- `Role: ${result.role}\n` +
451
- `Excerpt: ${result.excerpt}\n` +
452
- `---`
453
- )
454
- .join('\n\n');
455
-
456
- return {
457
- content: [
458
- {
459
- type: 'text',
460
- text: `Found ${results.length} relevant conversation(s) for "${query}":\n\n${resultText}`,
461
- },
462
- ],
463
- };
464
- } catch (error) {
465
- console.error('Error searching conversations:', error);
466
- throw new McpError(
467
- ErrorCode.InternalError,
468
- `Failed to search conversations: ${error instanceof Error ? error.message : String(error)}`
469
- );
470
- }
471
- }
472
-
473
- private async handleStoreReflection(args: any) {
474
- const { content, tags = [] } = args;
475
-
476
- try {
477
- // This is a placeholder for now
478
- // In production, we'd store this as a special type of conversation chunk
479
- return {
480
- content: [
481
- {
482
- type: 'text',
483
- text: `Reflection stored successfully with tags: ${tags.join(', ') || 'none'}`,
484
- },
485
- ],
486
- };
487
- } catch (error) {
488
- throw new McpError(
489
- ErrorCode.InternalError,
490
- `Failed to store reflection: ${error instanceof Error ? error.message : String(error)}`
491
- );
492
- }
493
- }
494
-
495
- async run() {
496
- // Initialize embedding service early
497
- await this.initialize();
498
-
499
- const transport = new StdioServerTransport();
500
- await this.server.connect(transport);
501
- console.error('Claude Self-Reflection MCP server running');
502
- console.error(`Connected to Qdrant at ${QDRANT_URL}`);
503
- console.error(`Embedding service: ${this.embeddingService?.getModelName() || 'none (text search only)'}`);
504
- console.error(`Voyage API Key: ${VOYAGE_API_KEY ? 'Set' : 'Not set'}`);
505
-
506
- // Check for Voyage collections
507
- const voyageCollections = await this.getVoyageCollections();
508
- console.error(`Found ${voyageCollections.length} Voyage collections ready for search`);
509
- }
510
- }
511
-
512
- const server = new SelfReflectionServer();
513
- server.run().catch(console.error);
@@ -1,93 +0,0 @@
1
- import { createHash } from 'crypto';
2
- import { QdrantClient } from '@qdrant/js-client-rest';
3
-
4
- export interface ProjectIsolationConfig {
5
- mode: 'isolated' | 'shared' | 'hybrid';
6
- allowCrossProject: boolean;
7
- projectIdentifier?: string;
8
- }
9
-
10
- export class ProjectIsolationManager {
11
- private client: QdrantClient;
12
- private config: ProjectIsolationConfig;
13
-
14
- constructor(client: QdrantClient, config: ProjectIsolationConfig) {
15
- this.client = client;
16
- this.config = config;
17
- }
18
-
19
- /**
20
- * Get collection name based on isolation mode
21
- */
22
- getCollectionName(projectName?: string): string {
23
- if (this.config.mode === 'isolated' && projectName) {
24
- // Create project-specific collection name
25
- const projectHash = createHash('md5')
26
- .update(projectName)
27
- .digest('hex')
28
- .substring(0, 8);
29
- return `conv_${projectHash}`;
30
- }
31
-
32
- // Default to shared collection
33
- return 'conversations';
34
- }
35
-
36
- /**
37
- * Get project filter for queries
38
- */
39
- getProjectFilter(projectName?: string): any {
40
- if (!projectName || this.config.mode === 'shared') {
41
- return {};
42
- }
43
-
44
- return {
45
- filter: {
46
- must: [{
47
- key: 'project_id',
48
- match: { value: projectName }
49
- }]
50
- }
51
- };
52
- }
53
-
54
- /**
55
- * Detect current project from environment
56
- */
57
- static detectCurrentProject(): string | undefined {
58
- // Check environment variables
59
- const fromEnv = process.env.CLAUDE_PROJECT_NAME;
60
- if (fromEnv) return fromEnv;
61
-
62
- // Try to detect from working directory
63
- const cwd = process.cwd();
64
- const projectMatch = cwd.match(/\/([^\/]+)$/);
65
- return projectMatch ? projectMatch[1] : undefined;
66
- }
67
-
68
- /**
69
- * Ensure collection exists for project
70
- */
71
- async ensureProjectCollection(projectName: string, vectorSize: number): Promise<void> {
72
- const collectionName = this.getCollectionName(projectName);
73
-
74
- try {
75
- await this.client.getCollection(collectionName);
76
- } catch (error) {
77
- // Collection doesn't exist, create it
78
- await this.client.createCollection(collectionName, {
79
- vectors: {
80
- size: vectorSize,
81
- distance: 'Cosine'
82
- }
83
- });
84
- console.error(`Created project-specific collection: ${collectionName}`);
85
- }
86
- }
87
- }
88
-
89
- export const DEFAULT_ISOLATION_CONFIG: ProjectIsolationConfig = {
90
- mode: 'hybrid',
91
- allowCrossProject: false,
92
- projectIdentifier: ProjectIsolationManager.detectCurrentProject()
93
- };