Skip to main content
Glama
memory-bank.service.ts12.6 kB
import * as fs from 'fs'; import { z } from 'zod'; import * as toolSchemas from '../../mcp/schemas/unified-tool-schemas'; import { ToolHandlerContext } from '../../mcp/types/sdk-custom'; import { Repository } from '../../types'; import { ensureAbsolutePath } from '../../utils/path.utils'; import { RepositoryAnalyzer } from '../../utils/repository-analyzer'; import { CoreService } from '../core/core.service'; import { IMemoryBankService } from '../core/service-container.interface'; import * as repositoryOps from '../memory-operations/repository.ops'; export class MemoryBankService extends CoreService implements IMemoryBankService { // No need for setter method - use service container instead async initMemoryBank( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string = 'main', ): Promise<z.infer<typeof toolSchemas.InitMemoryBankOutputSchema>> { const logger = mcpContext.logger || console; logger.info( `[MemoryBankService.initMemoryBank] ENTERED. Repo: ${repositoryName}:${branch}, CPR: ${clientProjectRoot}`, ); if (!this.repositoryProvider) { logger.error('[MemoryBankService.initMemoryBank] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } clientProjectRoot = ensureAbsolutePath(clientProjectRoot); logger.info(`[MemoryBankService.initMemoryBank] Absolute CPR: ${clientProjectRoot}`); try { await mcpContext.sendProgress({ status: 'in_progress', message: `Validating database path for ${repositoryName}:${branch}...`, percent: 20, }); // Initialize KuzuDB client and repositories const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); await mcpContext.sendProgress({ status: 'in_progress', message: `Creating repository ${repositoryName}:${branch}...`, percent: 40, }); // Create or get the repository const repository = await repositoryOps.getOrCreateRepositoryOp( mcpContext, repositoryName, branch, repositoryRepo, ); if (!repository) { logger.error( `[MemoryBankService.initMemoryBank] Failed to create repository ${repositoryName}:${branch}`, ); return { success: false, message: `Failed to create repository ${repositoryName}:${branch}`, path: clientProjectRoot, }; } await mcpContext.sendProgress({ status: 'in_progress', message: `Analyzing repository structure and generating metadata...`, percent: 70, }); // Automatically seed intelligent metadata after repository creation const seedResult = await this.seedIntelligentMetadata( mcpContext, clientProjectRoot, repositoryName, branch, ); // CRITICAL FIX: Make metadata seeding failures more visible and configurable if (!seedResult.success) { const errorMessage = `Failed to seed metadata: ${seedResult.message}`; logger.error(`[MemoryBankService.initMemoryBank] ${errorMessage}`, { repositoryName, branch, clientProjectRoot, seedResult, }); // Check if this is a critical metadata failure that should fail initialization const isCriticalMetadataFailure = this.isCriticalMetadataFailure(seedResult.message); if (isCriticalMetadataFailure) { logger.error( `[MemoryBankService.initMemoryBank] Critical metadata failure detected, failing initialization`, { errorMessage, repositoryName, branch }, ); return { success: false, message: `Memory bank initialization failed due to critical metadata error: ${seedResult.message}`, path: clientProjectRoot, }; } else { // Non-critical failure - log as error but continue logger.warn( `[MemoryBankService.initMemoryBank] Non-critical metadata seeding failure, continuing with initialization`, { errorMessage, repositoryName, branch }, ); } } else { logger.info( `[MemoryBankService.initMemoryBank] Metadata seeding completed successfully for ${repositoryName}:${branch}`, ); } await mcpContext.sendProgress({ status: 'in_progress', message: `Memory bank initialization complete for ${repositoryName}:${branch}`, percent: 100, }); logger.info( `[MemoryBankService.initMemoryBank] Successfully initialized memory bank for ${repositoryName}:${branch}`, ); return { success: true, message: `Memory bank initialized for ${repositoryName} (branch: ${branch})${!seedResult.success ? ' (with metadata seeding warnings)' : ''}`, path: clientProjectRoot, }; } catch (error: any) { logger.error( `[MemoryBankService.initMemoryBank] Error initializing memory bank for ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); return { success: false, message: `Failed to initialize memory bank: ${error.message}`, path: clientProjectRoot, }; } } /** * Validates that the clientProjectRoot path exists and is accessible * @param clientProjectRoot - The path to validate * @param logger - Logger instance for error reporting * @throws Error if path is invalid or inaccessible */ private async validateClientProjectRoot(clientProjectRoot: string, logger: any): Promise<void> { try { // Check if path exists and is readable in one operation await fs.promises.access(clientProjectRoot, fs.constants.F_OK | fs.constants.R_OK); // Check if path is a directory const stats = await fs.promises.stat(clientProjectRoot); if (!stats.isDirectory()) { const errorMessage = `Client project root path is not a directory: ${clientProjectRoot}`; logger.error(`[MemoryBankService.validateClientProjectRoot] ${errorMessage}`); throw new Error(errorMessage); } logger.info( `[MemoryBankService.validateClientProjectRoot] Path validation successful: ${clientProjectRoot}`, ); } catch (error: any) { // Re-throw validation errors if (error.message.includes('Client project root path')) { throw error; } // Handle unexpected filesystem errors const errorMessage = `Failed to validate client project root path: ${error.message}`; logger.error(`[MemoryBankService.validateClientProjectRoot] ${errorMessage}`, { clientProjectRoot, error: error.toString(), }); throw new Error(errorMessage); } } /** * Determine if a metadata failure is critical enough to fail the entire initialization * Critical failures include database connection issues, schema problems, etc. * Non-critical failures include analysis errors, missing files, etc. */ private isCriticalMetadataFailure(errorMessage: string): boolean { const criticalPatterns = [ 'database connection', 'schema error', 'MetadataService not available', 'MetadataService not initialized', 'transaction failed', 'connection timeout', 'access denied', 'permission denied', 'disk full', 'out of memory', ]; const normalizedError = errorMessage.toLowerCase(); return criticalPatterns.some((pattern) => normalizedError.includes(pattern)); } /** * Seeds intelligent metadata by analyzing the actual repository structure and characteristics */ async seedIntelligentMetadata( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string = 'main', ): Promise<{ success: boolean; message: string }> { const logger = mcpContext.logger || console; logger.info( `[MemoryBankService.seedIntelligentMetadata] Analyzing repository ${repositoryName}:${branch}`, ); try { // Validate that clientProjectRoot exists and is accessible before analysis await this.validateClientProjectRoot(clientProjectRoot, logger); // Analyze the repository structure and characteristics const analyzer = new RepositoryAnalyzer(clientProjectRoot, logger); const analysisResult = await analyzer.analyzeRepository(); logger.info( `[MemoryBankService.seedIntelligentMetadata] Analysis complete for ${repositoryName}:${branch}`, { projectType: analysisResult.projectType, languages: analysisResult.techStack.languages, architecture: analysisResult.architecture.pattern, }, ); // Get metadata service from service container const metadataService = await this.serviceContainer.getMetadataService(); const metadataContent = { id: `${repositoryName}-${branch}-metadata`, project: { name: repositoryName, created: analysisResult.createdDate, type: analysisResult.projectType, size: analysisResult.size, }, tech_stack: { languages: analysisResult.techStack.languages, frameworks: analysisResult.techStack.frameworks, databases: analysisResult.techStack.databases, tools: analysisResult.techStack.tools, package_manager: analysisResult.techStack.packageManager, runtime: analysisResult.techStack.runtime, }, architecture: analysisResult.architecture.pattern, architecture_details: { layers: analysisResult.architecture.layers, patterns: analysisResult.architecture.patterns, complexity: analysisResult.architecture.complexity, }, memory_spec_version: '3.0.0', analysis_date: new Date().toISOString(), }; const updateResult = await metadataService.updateMetadata( mcpContext, clientProjectRoot, repositoryName, metadataContent, branch, ); if (updateResult?.success) { logger.info( `[MemoryBankService.seedIntelligentMetadata] Successfully seeded metadata for ${repositoryName}:${branch}`, ); return { success: true, message: `Intelligent metadata seeded for ${repositoryName}:${branch}`, }; } else { const errorMessage = updateResult?.message || 'Unknown error during metadata update'; logger.error( `[MemoryBankService.seedIntelligentMetadata] Failed to update metadata: ${errorMessage}`, ); return { success: false, message: `Failed to seed metadata: ${errorMessage}`, }; } } catch (error: any) { logger.error( `[MemoryBankService.seedIntelligentMetadata] Error seeding metadata for ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); return { success: false, message: `Failed to analyze and seed metadata: ${error.message}`, }; } } async getOrCreateRepository( mcpContext: ToolHandlerContext, clientProjectRoot: string, name: string, branch: string = 'main', ): Promise<Repository> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[MemoryBankService.getOrCreateRepository] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const repository = await repositoryOps.getOrCreateRepositoryOp( mcpContext, name, branch, repositoryRepo, ); if (!repository) { const errorMessage = `Failed to create or retrieve repository ${name}:${branch}`; logger.error(`[MemoryBankService.getOrCreateRepository] ${errorMessage}`); throw new Error(errorMessage); } logger.info( `[MemoryBankService.getOrCreateRepository] Repository ${name}:${branch} retrieved/created successfully`, ); return repository; } catch (error: any) { logger.error( `[MemoryBankService.getOrCreateRepository] Error for ${name}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } }

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/Jakedismo/KuzuMem-MCP'

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