Skip to main content
Glama
GraphitiClient.ts16.2 kB
import { CodeRepresentation, RetrievalResult, SACLConfig, CodeRelationships, RelatedComponent, RelationshipGraph, RelationshipType, GraphTraversalResult, RelationshipTraversalConfig, DEFAULT_RELATIONSHIP_WEIGHTS } from '../types/index.js'; /** * GraphitiClient handles integration with Graphiti knowledge graph * Stores semantic-augmented code representations and enables retrieval */ export class GraphitiClient { private config: GraphitiConfig; private client: any; // Will be Graphiti client instance constructor(config: GraphitiConfig) { this.config = config; this.initializeClient(); } private async initializeClient(): Promise<void> { try { // Import Graphiti client dynamically (if available) // const { Graphiti } = await import('graphiti-core'); // For now, simulate the client initialization console.log(`Initializing Graphiti client`); console.log(`Neo4j URI: ${this.config.neo4jUri}`); // this.client = new Graphiti( // this.config.neo4jUri, // this.config.neo4jUser, // this.config.neo4jPassword // ); // Namespaces will be created on-demand per repository } catch (error) { console.error('Failed to initialize Graphiti client:', error); throw new Error(`Graphiti initialization failed: ${error}`); } } /** * Create isolated namespace for repository * Ensures code from different repos doesn't interfere */ private async createNamespace(namespace: string): Promise<void> { try { // Set up namespace-specific constraints and indices console.log(`Creating namespace: ${namespace}`); // In real implementation: // await this.client.add_episode({ // name: `Namespace Creation: ${namespace}`, // episode_body: `Repository namespace: ${namespace}`, // source: 'metadata', // group_id: namespace // }); } catch (error) { console.error('Failed to create namespace:', error); throw error; } } /** * Store code representation in knowledge graph with specific namespace */ async storeCodeRepresentation(code: CodeRepresentation, namespace?: string): Promise<void> { try { const episodeData = this.createEpisodeFromCode(code); console.log(`Storing code representation: ${code.filePath}`); // In real implementation: // await this.client.add_episode({ // name: `Code Analysis: ${code.filePath}`, // episode_body: JSON.stringify(episodeData), // source: 'json', // source_description: 'SACL Code Analysis', // group_id: namespace || this.config.namespace // }); } catch (error) { console.error('Failed to store code representation:', error); throw error; } } /** * Search for code using hybrid approach (semantic + keyword) */ async searchCode(query: string, limit: number = 10, namespace?: string): Promise<CodeRepresentation[]> { try { console.log(`Searching for: "${query}" in namespace: ${namespace || this.config.namespace}`); // In real implementation: // const results = await this.client.search( // query, // { // num_results: limit, // group_id: namespace || this.config.namespace // } // ); // For now, return mock results return this.createMockSearchResults(query, limit); } catch (error) { console.error('Search failed:', error); throw error; } } /** * Get bias metrics for debugging */ async getBiasMetrics(filePath?: string): Promise<BiasMetrics> { try { // In real implementation, query graph for bias-related nodes console.log(`Getting bias metrics for: ${filePath || 'all files'}`); return { averageBiasScore: 0.45, highBiasFiles: ['file1.py', 'file2.js'], biasDistribution: { low: 15, medium: 8, high: 3 }, improvementSuggestions: [ 'Reduce reliance on variable naming for semantic understanding', 'Focus on structural patterns over comments', 'Improve functional signature extraction' ] }; } catch (error) { console.error('Failed to get bias metrics:', error); throw error; } } /** * Clear namespace (for testing or repository reset) */ async clearNamespace(): Promise<void> { try { console.log(`Clearing namespace: ${this.config.namespace}`); // In real implementation: // await this.client.clear_graph(); } catch (error) { console.error('Failed to clear namespace:', error); throw error; } } /** * Get processing statistics */ async getStats(): Promise<GraphitiStats> { try { // In real implementation, query graph for statistics return { totalFiles: 150, processedFiles: 147, totalNodes: 1250, totalEdges: 3400, lastUpdate: new Date(), namespaceSize: '45.2 MB' }; } catch (error) { console.error('Failed to get stats:', error); throw error; } } /** * Convert code representation to Graphiti episode format */ private createEpisodeFromCode(code: CodeRepresentation): any { return { file_path: code.filePath, content_summary: code.content.slice(0, 500), textual_features: code.textualFeatures, structural_features: code.structuralFeatures, semantic_features: code.semanticFeatures, bias_score: code.biasScore, embedding: code.augmentedEmbedding, last_modified: code.lastModified.toISOString(), processing_metadata: { sacl_version: '1.0.0', processed_at: new Date().toISOString(), namespace: this.config.namespace } }; } /** * Create mock search results for testing */ private createMockSearchResults(query: string, limit: number): CodeRepresentation[] { const mockResults: CodeRepresentation[] = []; for (let i = 0; i < Math.min(limit, 5); i++) { mockResults.push({ filePath: `src/example${i + 1}.py`, content: `def example_function_${i + 1}():\n # Mock implementation for ${query}\n return "result"`, textualFeatures: { docstrings: [`Function for ${query}`], comments: [`Implementation of ${query}`], identifierNames: [`example_function_${i + 1}`, 'result'], variableNames: ['result'] }, structuralFeatures: { astNodes: 15 + i * 3, complexity: 2 + i, nestingDepth: 1, functionCount: 1, classCount: 0 }, semanticFeatures: { embedding: Array(384).fill(0).map(() => Math.random() - 0.5), functionalSignature: `Processes ${query} and returns result`, behaviorPattern: `Linear processing with ${query} operation` }, biasScore: 0.3 + (i * 0.1), augmentedEmbedding: Array(384).fill(0).map(() => Math.random() - 0.5), lastModified: new Date() }); } return mockResults; } // ================================ // RELATIONSHIP STORAGE & RETRIEVAL // ================================ /** * Store relationship in knowledge graph as Graphiti edge */ async storeRelationship( from: string, to: string, type: RelationshipType, details: { symbols?: string[]; lineNumber?: number; statement?: string; weight?: number; } ): Promise<void> { try { console.log(`Storing relationship: ${from} --${type}--> ${to}`); // In real implementation: // await this.client.add_episode({ // name: `Relationship: ${type}`, // episode_body: JSON.stringify({ // relationship_type: type, // from_file: from, // to_file: to, // details: details, // weight: details.weight || DEFAULT_RELATIONSHIP_WEIGHTS[type] || 0.5 // }), // source: 'relationship', // source_description: 'SACL Relationship Analysis', // group_id: this.config.namespace // }); } catch (error) { console.error('Failed to store relationship:', error); throw error; } } /** * Store all relationships for a file */ async storeFileRelationships(filePath: string, relationships: CodeRelationships): Promise<void> { try { console.log(`Storing relationships for: ${filePath}`); // Store import relationships for (const importRel of relationships.imports) { await this.storeRelationship(importRel.from, importRel.to, 'imports', { symbols: importRel.symbols, lineNumber: importRel.lineNumber, statement: importRel.statement }); } // Store export relationships for (const exportRel of relationships.exports) { await this.storeRelationship(exportRel.from, exportRel.to || 'external', 'exports', { symbols: [exportRel.symbol], lineNumber: exportRel.lineNumber, statement: exportRel.statement }); } // Store function call relationships for (const callRel of relationships.functionCalls) { await this.storeRelationship(callRel.from, callRel.to, 'calls', { symbols: callRel.object ? [callRel.object] : [], lineNumber: callRel.lineNumber }); } // Store inheritance relationships for (const inheritRel of relationships.classInheritance) { const relType = inheritRel.type === 'extends' ? 'extends' : 'implements'; await this.storeRelationship(inheritRel.from, inheritRel.to, relType, { lineNumber: inheritRel.lineNumber }); } // Store dependency relationships for (const depRel of relationships.dependencies) { await this.storeRelationship(depRel.from, depRel.to, 'depends_on', { symbols: depRel.usage, weight: depRel.dependencyType === 'local' ? 0.8 : 0.4 }); } } catch (error) { console.error(`Failed to store relationships for ${filePath}:`, error); throw error; } } /** * Get related components for a file using graph traversal */ async getRelatedComponents( filePath: string, config: Partial<RelationshipTraversalConfig> = {} ): Promise<RelatedComponent[]> { try { const defaultConfig: RelationshipTraversalConfig = { maxDepth: 3, relationshipTypes: ['imports', 'exports', 'calls', 'extends', 'implements'], minRelevanceScore: 0.3, includeReverse: true, weightings: DEFAULT_RELATIONSHIP_WEIGHTS }; const traversalConfig = { ...defaultConfig, ...config }; console.log(`Finding related components for: ${filePath}`); // Mock implementation - in real version, this would traverse the Graphiti graph return this.createMockRelatedComponents(filePath, traversalConfig); } catch (error) { console.error(`Failed to get related components for ${filePath}:`, error); throw error; } } /** * Traverse relationships in graph to find connected components */ async traverseRelationships( startFile: string, relationshipTypes: RelationshipType[], maxDepth: number = 3 ): Promise<GraphTraversalResult> { try { console.log(`Traversing relationships from: ${startFile}`); const startTime = Date.now(); // Mock implementation const relatedComponents = await this.getRelatedComponents(startFile, { maxDepth, relationshipTypes }); const relationshipGraph: RelationshipGraph = { nodes: [ { id: startFile, label: startFile.split('/').pop() || startFile, type: 'file', metadata: { complexity: 5 } }, ...relatedComponents.map(comp => ({ id: comp.filePath, label: comp.componentName, type: comp.componentType, metadata: { biasScore: 0.4 } })) ], edges: relatedComponents.map(comp => ({ from: startFile, to: comp.filePath, type: comp.relationshipType, weight: comp.relevanceScore, label: comp.relationshipDescription, metadata: { bidirectional: false } })), primaryNode: startFile, maxDepth }; return { startNode: startFile, relatedComponents, relationshipGraph, traversalStats: { nodesVisited: relatedComponents.length + 1, edgesTraversed: relatedComponents.length, maxDepthReached: Math.max(...relatedComponents.map(c => c.distance)), processingTime: Date.now() - startTime } }; } catch (error) { console.error('Failed to traverse relationships:', error); throw error; } } /** * Delete relationships for a file (when file is deleted) */ async deleteFileRelationships(filePath: string): Promise<void> { try { console.log(`Deleting relationships for: ${filePath}`); // In real implementation: // await this.client.delete_entities_by_query({ // query: `relationships involving ${filePath}`, // group_id: this.config.namespace // }); } catch (error) { console.error(`Failed to delete relationships for ${filePath}:`, error); throw error; } } /** * Create mock related components for testing */ private createMockRelatedComponents( filePath: string, config: RelationshipTraversalConfig ): RelatedComponent[] { const mockComponents: RelatedComponent[] = []; // Mock related files based on file type and name const fileName = filePath.split('/').pop() || ''; if (fileName.includes('Service')) { mockComponents.push({ filePath: filePath.replace('Service', 'Controller'), componentName: fileName.replace('Service', 'Controller'), componentType: 'file', relationshipType: 'calls', relationshipDescription: 'Controller uses this service', relevanceScore: 0.9, distance: 1, snippet: 'class Controller { constructor(private service: Service) {} }' }); } if (fileName.includes('Controller')) { mockComponents.push({ filePath: filePath.replace('Controller', 'Service'), componentName: fileName.replace('Controller', 'Service'), componentType: 'file', relationshipType: 'imports', relationshipDescription: 'Service imported by controller', relevanceScore: 0.95, distance: 1, snippet: 'import { Service } from "./Service";' }); } // Add some generic related components mockComponents.push({ filePath: filePath.replace(/\.[^.]+$/, '.test$&'), componentName: fileName.replace(/\.[^.]+$/, '.test$&'), componentType: 'file', relationshipType: 'depends_on', relationshipDescription: 'Test file for this component', relevanceScore: 0.7, distance: 1 }); return mockComponents.filter(comp => comp.relevanceScore >= config.minRelevanceScore && comp.distance <= config.maxDepth ).slice(0, 5); // Limit to 5 components } } export interface GraphitiConfig { namespace?: string; // Optional - namespaces provided per operation neo4jUri: string; neo4jUser: string; neo4jPassword: string; openaiApiKey: string; } export interface BiasMetrics { averageBiasScore: number; highBiasFiles: string[]; biasDistribution: { low: number; medium: number; high: number; }; improvementSuggestions: string[]; } export interface GraphitiStats { totalFiles: number; processedFiles: number; totalNodes: number; totalEdges: number; lastUpdate: Date; namespaceSize: string; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ulasbilgen/sacl'

If you have feedback or need assistance with the MCP directory API, please join our Discord server