Skip to main content
Glama
entity.service.ts42 kB
import { ToolHandlerContext } from '../../mcp/types/sdk-custom'; import { Component, ComponentStatus, Decision, FileInput, File as FileRecord, Rule, RuleInput, Tag, TagInput, } from '../../types'; import { CoreService } from '../core/core.service'; import { IEntityService, IServiceContainer } from '../core/service-container.interface'; import * as componentOps from '../memory-operations/component.ops'; import * as decisionOps from '../memory-operations/decision.ops'; import * as fileOps from '../memory-operations/file.ops'; import * as ruleOps from '../memory-operations/rule.ops'; import * as tagOps from '../memory-operations/tag.ops'; /** * Entity Service with full database functionality restored */ export class EntityService extends CoreService implements IEntityService { constructor(serviceContainer: IServiceContainer) { super(serviceContainer); } // Component operations async upsertComponent( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, componentData: { id: string; name: string; kind?: string; status?: ComponentStatus; depends_on?: string[]; }, ): Promise<Component | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.upsertComponent] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const componentRepo = this.repositoryProvider.getComponentRepository(clientProjectRoot); const component = await componentOps.upsertComponentOp( mcpContext, repositoryName, branch, componentData, repositoryRepo, componentRepo, ); logger.info( `[EntityService.upsertComponent] Component ${componentData.id} upserted successfully in ${repositoryName}:${branch}`, ); return component; } catch (error: any) { logger.error( `[EntityService.upsertComponent] Error upserting component ${componentData.id} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async getComponent( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, componentId: string, ): Promise<Component | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.getComponent] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const componentRepo = this.repositoryProvider.getComponentRepository(clientProjectRoot); const component = await componentRepo.findByIdAndBranch(repositoryName, componentId, branch); return component; } catch (error: any) { logger.error( `[EntityService.getComponent] Error getting component ${componentId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async updateComponent( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, componentId: string, updates: Partial<Component>, ): Promise<Component | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.updateComponent] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const componentRepo = this.repositoryProvider.getComponentRepository(clientProjectRoot); // For updates, we need to get the existing component first, then upsert with updates const existingComponent = await componentRepo.findByIdAndBranch( repositoryName, componentId, branch, ); if (!existingComponent) { logger.warn( `[EntityService.updateComponent] Component ${componentId} not found in ${repositoryName}:${branch}`, ); return null; } const updatedData = { id: componentId, name: updates.name || existingComponent.name, kind: updates.kind || existingComponent.kind || undefined, status: updates.status || existingComponent.status || undefined, depends_on: updates.depends_on || existingComponent.depends_on, }; const component = await componentOps.upsertComponentOp( mcpContext, repositoryName, branch, updatedData, repositoryRepo, componentRepo, ); logger.info( `[EntityService.updateComponent] Component ${componentId} updated successfully in ${repositoryName}:${branch}`, ); return component; } catch (error: any) { logger.error( `[EntityService.updateComponent] Error updating component ${componentId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async deleteComponent( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, componentId: string, ): Promise<boolean> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.deleteComponent] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const result = await componentOps.deleteComponentOp( mcpContext, kuzuClient, repositoryRepo, repositoryName, branch, componentId, ); logger.info( `[EntityService.deleteComponent] Component ${componentId} deletion result: ${result} in ${repositoryName}:${branch}`, ); return result; } catch (error: any) { logger.error( `[EntityService.deleteComponent] Error deleting component ${componentId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // Decision operations async upsertDecision( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, decisionData: { id: string; name: string; date: string; context?: string; }, ): Promise<Decision | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.upsertDecision] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const decisionRepo = this.repositoryProvider.getDecisionRepository(clientProjectRoot); const decisionInput = { ...decisionData, repository: repositoryName, branch, }; const decision = await decisionOps.upsertDecisionOp( mcpContext, repositoryName, branch, decisionInput, repositoryRepo, decisionRepo, ); logger.info( `[EntityService.upsertDecision] Decision ${decisionData.id} upserted successfully in ${repositoryName}:${branch}`, ); return decision; } catch (error: any) { logger.error( `[EntityService.upsertDecision] Error upserting decision ${decisionData.id} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async getDecision( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, decisionId: string, ): Promise<Decision | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.getDecision] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const decisionRepo = this.repositoryProvider.getDecisionRepository(clientProjectRoot); const decision = await decisionRepo.findByIdAndBranch(repositoryName, decisionId, branch); return decision; } catch (error: any) { logger.error( `[EntityService.getDecision] Error getting decision ${decisionId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async updateDecision( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, decisionId: string, updates: Partial<Decision>, ): Promise<Decision | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.updateDecision] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const decisionRepo = this.repositoryProvider.getDecisionRepository(clientProjectRoot); // For updates, get existing decision first, then upsert with updates const existingDecision = await decisionRepo.findByIdAndBranch( repositoryName, decisionId, branch, ); if (!existingDecision) { logger.warn( `[EntityService.updateDecision] Decision ${decisionId} not found in ${repositoryName}:${branch}`, ); return null; } const updatedData: any = { id: decisionId, name: updates.name || existingDecision.name, date: updates.date || existingDecision.date, repository: repositoryName, branch, }; // Only add context if it's not null if (updates.context !== undefined) { updatedData.context = updates.context; } else if (existingDecision.context !== null) { updatedData.context = existingDecision.context; } const decision = await decisionOps.upsertDecisionOp( mcpContext, repositoryName, branch, updatedData, repositoryRepo, decisionRepo, ); logger.info( `[EntityService.updateDecision] Decision ${decisionId} updated successfully in ${repositoryName}:${branch}`, ); return decision; } catch (error: any) { logger.error( `[EntityService.updateDecision] Error updating decision ${decisionId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async deleteDecision( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, decisionId: string, ): Promise<boolean> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.deleteDecision] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const result = await decisionOps.deleteDecisionOp( mcpContext, kuzuClient, repositoryRepo, repositoryName, branch, decisionId, ); logger.info( `[EntityService.deleteDecision] Decision ${decisionId} deletion result: ${result} in ${repositoryName}:${branch}`, ); return result; } catch (error: any) { logger.error( `[EntityService.deleteDecision] Error deleting decision ${decisionId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // Rule operations async upsertRule( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, rule: RuleInput, branch: string = 'main', ): Promise<Rule | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.upsertRule] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const ruleRepo = this.repositoryProvider.getRuleRepository(clientProjectRoot); const ruleResult = await ruleOps.upsertRuleOp( mcpContext, repositoryName, branch, rule, repositoryRepo, ruleRepo, ); logger.info( `[EntityService.upsertRule] Rule ${rule.id} upserted successfully in ${repositoryName}:${branch}`, ); return ruleResult; } catch (error: any) { logger.error( `[EntityService.upsertRule] Error upserting rule ${rule.id} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async getRule( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, ruleId: string, ): Promise<Rule | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.getRule] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const ruleRepo = this.repositoryProvider.getRuleRepository(clientProjectRoot); const rule = await ruleRepo.findByIdAndBranch(repositoryName, ruleId, branch); return rule; } catch (error: any) { logger.error( `[EntityService.getRule] Error getting rule ${ruleId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async updateRule( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, ruleId: string, updates: Partial<Rule>, ): Promise<Rule | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.updateRule] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const ruleRepo = this.repositoryProvider.getRuleRepository(clientProjectRoot); // For updates, get existing rule first, then upsert with updates const existingRule = await ruleRepo.findByIdAndBranch(repositoryName, ruleId, branch); if (!existingRule) { logger.warn( `[EntityService.updateRule] Rule ${ruleId} not found in ${repositoryName}:${branch}`, ); return null; } const updatedData: any = { id: ruleId, name: updates.name || existingRule.name, created: updates.created || existingRule.created, repository: repositoryName, branch, }; // Only add optional fields if they're not null if (updates.triggers !== undefined) { updatedData.triggers = updates.triggers; } else if (existingRule.triggers !== null) { updatedData.triggers = existingRule.triggers; } if (updates.content !== undefined) { updatedData.content = updates.content; } else if (existingRule.content !== null) { updatedData.content = existingRule.content; } if (updates.status || existingRule.status) { updatedData.status = updates.status || existingRule.status; } const rule = await ruleOps.upsertRuleOp( mcpContext, repositoryName, branch, updatedData, repositoryRepo, ruleRepo, ); logger.info( `[EntityService.updateRule] Rule ${ruleId} updated successfully in ${repositoryName}:${branch}`, ); return rule; } catch (error: any) { logger.error( `[EntityService.updateRule] Error updating rule ${ruleId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async deleteRule( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, ruleId: string, ): Promise<boolean> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.deleteRule] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const result = await ruleOps.deleteRuleOp( mcpContext, kuzuClient, repositoryRepo, repositoryName, branch, ruleId, ); logger.info( `[EntityService.deleteRule] Rule ${ruleId} deletion result: ${result} in ${repositoryName}:${branch}`, ); return result; } catch (error: any) { logger.error( `[EntityService.deleteRule] Error deleting rule ${ruleId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // File operations async addFile( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, fileData: FileInput, ): Promise<any> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.addFile] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const fileRepo = this.repositoryProvider.getFileRepository(clientProjectRoot); const fileResult = await fileOps.addFileOp( mcpContext, repositoryName, branch, fileData, repositoryRepo, fileRepo, ); logger.info( `[EntityService.addFile] File ${fileData.id} added successfully in ${repositoryName}:${branch}`, ); return { success: fileResult.success, message: fileResult.message, entity: fileResult.file, }; } catch (error: any) { logger.error( `[EntityService.addFile] Error adding file ${fileData.id} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async getFile( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, fileId: string, ): Promise<FileRecord | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.getFile] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const fileRepo = this.repositoryProvider.getFileRepository(clientProjectRoot); // Get repository first to get the repo node ID const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const repository = await repositoryRepo.findByName(repositoryName, branch); if (!repository || !repository.id) { logger.warn(`[EntityService.getFile] Repository ${repositoryName}:${branch} not found.`); return null; } const file = await fileRepo.findFileById(repository.id, branch, fileId); return file; } catch (error: any) { logger.error( `[EntityService.getFile] Error getting file ${fileId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async deleteFile( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, fileId: string, ): Promise<boolean> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.deleteFile] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const result = await fileOps.deleteFileOp( mcpContext, kuzuClient, repositoryRepo, repositoryName, branch, fileId, ); logger.info( `[EntityService.deleteFile] File ${fileId} deletion result: ${result} in ${repositoryName}:${branch}`, ); return result; } catch (error: any) { logger.error( `[EntityService.deleteFile] Error deleting file ${fileId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // Tag operations async addTag( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, tagData: TagInput, ): Promise<any> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.addTag] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const tagRepo = this.repositoryProvider.getTagRepository(clientProjectRoot); const tagResult = await tagOps.addTagOp( mcpContext, repositoryName, branch, tagData, repositoryRepo, tagRepo, ); logger.info( `[EntityService.addTag] Tag ${tagData.id} added successfully in ${repositoryName}:${branch}`, ); return { success: tagResult.success, message: tagResult.message, entity: tagResult.tag, }; } catch (error: any) { logger.error( `[EntityService.addTag] Error adding tag ${tagData.id} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async getTag( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, tagId: string, ): Promise<Tag | null> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.getTag] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const tagRepo = this.repositoryProvider.getTagRepository(clientProjectRoot); const tag = await tagRepo.findTagById(tagId); return tag; } catch (error: any) { logger.error( `[EntityService.getTag] Error getting tag ${tagId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async deleteTag( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, tagId: string, ): Promise<boolean> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.deleteTag] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const result = await tagOps.deleteTagOp(mcpContext, kuzuClient, tagId); logger.info( `[EntityService.deleteTag] Tag ${tagId} deletion result: ${result} in ${repositoryName}:${branch}`, ); return result; } catch (error: any) { logger.error( `[EntityService.deleteTag] Error deleting tag ${tagId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // Context operations async deleteContext( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, contextId: string, ): Promise<boolean> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.deleteContext] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const repository = await repositoryRepo.findByName(repositoryName, branch); if (!repository || !repository.id) { logger.warn( `[EntityService.deleteContext] Repository ${repositoryName}:${branch} not found.`, ); return false; } const graphUniqueId = `${repositoryName}:${branch}:${contextId}`; const deleteQuery = ` MATCH (c:Context {graph_unique_id: $graphUniqueId}) DETACH DELETE c RETURN 1 as deletedCount `; const result = await kuzuClient.executeQuery(deleteQuery, { graphUniqueId }); const success = result.length > 0; logger.info( `[EntityService.deleteContext] Context ${contextId} deletion result: ${success} in ${repositoryName}:${branch}`, ); return success; } catch (error: any) { logger.error( `[EntityService.deleteContext] Error deleting context ${contextId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // Association operations async associateFileWithComponent( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, componentId: string, fileId: string, ): Promise<any> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.associateFileWithComponent] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const componentGraphId = `${repositoryName}:${branch}:${componentId}`; // Note: File table uses 'id' as primary key, Component table uses 'graph_unique_id' // Schema: IMPLEMENTS relationship goes FROM Component TO File const associationQuery = ` MATCH (f:File {id: $fileId}) MATCH (c:Component {graph_unique_id: $componentGraphId}) WHERE f.repository = $repositoryName AND f.branch = $branch MERGE (c)-[:IMPLEMENTS]->(f) RETURN f, c `; const result = await kuzuClient.executeQuery(associationQuery, { fileId, componentGraphId, repositoryName, branch, }); const success = result.length > 0; logger.info( `[EntityService.associateFileWithComponent] File ${fileId} associated with component ${componentId}: ${success} in ${repositoryName}:${branch}`, ); return { type: 'file-component', success, message: success ? 'File associated with component successfully' : 'Failed to associate file with component', association: { from: fileId, to: componentId, relationship: 'IMPLEMENTS', }, }; } catch (error: any) { logger.error( `[EntityService.associateFileWithComponent] Error associating file ${fileId} with component ${componentId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async tagItem( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, itemId: string, itemType: string, tagId: string, ): Promise<any> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.tagItem] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const itemGraphId = `${repositoryName}:${branch}:${itemId}`; // Note: Tag table uses 'id' as primary key, Component table uses 'graph_unique_id' const tagQuery = ` MATCH (item:${itemType} {graph_unique_id: $itemGraphId}) MATCH (tag:Tag {id: $tagId}) WHERE tag.repository = $repositoryName AND tag.branch = $branch MERGE (item)-[:TAGGED_WITH]->(tag) RETURN item, tag `; const result = await kuzuClient.executeQuery(tagQuery, { itemGraphId, tagId, repositoryName, branch, }); const success = result.length > 0; logger.info( `[EntityService.tagItem] ${itemType} ${itemId} tagged with ${tagId}: ${success} in ${repositoryName}:${branch}`, ); return { type: 'item-tag', success, message: success ? 'Item tagged successfully' : 'Failed to tag item', association: { from: itemId, to: tagId, relationship: 'TAGGED_WITH', }, }; } catch (error: any) { logger.error( `[EntityService.tagItem] Error tagging ${itemType} ${itemId} with ${tagId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } // Bulk operations async bulkDeleteByType( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, entityType: string, options: { dryRun?: boolean; force?: boolean } = {}, ): Promise<{ count: number; entities: Array<{ type: string; id: string; name?: string }>; warnings: string[]; }> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.bulkDeleteByType] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); const repositoryRepo = this.repositoryProvider.getRepositoryRepository(clientProjectRoot); const repository = await repositoryRepo.findByName(repositoryName, branch); if (!repository || !repository.id) { logger.warn( `[EntityService.bulkDeleteByType] Repository ${repositoryName}:${branch} not found.`, ); return { count: 0, entities: [], warnings: [`Repository ${repositoryName}:${branch} not found`], }; } // Query to find entities of the specified type const findQuery = ` MATCH (n:${entityType}) WHERE n.repository = $repositoryName AND n.branch = $branch RETURN n.id as id, n.name as name `; const entities = await kuzuClient.executeQuery(findQuery, { repositoryName, branch }); if (options.dryRun) { logger.info( `[EntityService.bulkDeleteByType] Dry run: Would delete ${entities.length} ${entityType} entities`, ); return { count: entities.length, entities: entities.map((e: any) => ({ type: entityType, id: e.id, name: e.name })), warnings: [], }; } // Perform actual deletion const deleteQuery = ` MATCH (n:${entityType}) WHERE n.repository = $repositoryName AND n.branch = $branch DETACH DELETE n RETURN count(n) as deletedCount `; const result = await kuzuClient.executeQuery(deleteQuery, { repositoryName, branch }); const deletedCount = result[0]?.deletedCount || 0; logger.info( `[EntityService.bulkDeleteByType] Deleted ${deletedCount} ${entityType} entities in ${repositoryName}:${branch}`, ); return { count: deletedCount, entities: entities.map((e: any) => ({ type: entityType, id: e.id, name: e.name })), warnings: [], }; } catch (error: any) { logger.error( `[EntityService.bulkDeleteByType] Error deleting ${entityType} entities in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async bulkDeleteByTag( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, branch: string, tagId: string, options: { dryRun?: boolean; force?: boolean } = {}, ): Promise<{ count: number; entities: Array<{ type: string; id: string; name?: string }>; warnings: string[]; }> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.bulkDeleteByTag] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); // Find all entities tagged with the specified tag const findQuery = ` MATCH (n)-[:TAGGED_WITH]->(t:Tag {id: $tagId}) WHERE (n.repository = $repositoryName AND n.branch = $branch) OR (n.graph_unique_id STARTS WITH $repoPrefix) RETURN n.id as id, n.name as name, labels(n)[0] as type `; const repoPrefix = `${repositoryName}:${branch}:`; const entities = await kuzuClient.executeQuery(findQuery, { tagId, repositoryName, branch, repoPrefix, }); if (options.dryRun) { logger.info( `[EntityService.bulkDeleteByTag] Dry run: Would delete ${entities.length} entities tagged with ${tagId}`, ); return { count: entities.length, entities: entities.map((e: any) => ({ type: e.type, id: e.id, name: e.name })), warnings: [], }; } // Delete entities one by one to handle different entity types let deletedCount = 0; for (const entity of entities) { try { const deleteQuery = ` MATCH (n:${entity.type} {id: $id}) WHERE (n.repository = $repositoryName AND n.branch = $branch) OR (n.graph_unique_id STARTS WITH $repoPrefix) DETACH DELETE n RETURN 1 as deleted `; const result = await kuzuClient.executeQuery(deleteQuery, { id: entity.id, repositoryName, branch, repoPrefix, }); if (result.length > 0) { deletedCount++; } } catch (error: any) { logger.warn(`Failed to delete ${entity.type} ${entity.id}: ${error.message}`); } } logger.info( `[EntityService.bulkDeleteByTag] Deleted ${deletedCount} entities tagged with ${tagId} in ${repositoryName}:${branch}`, ); return { count: deletedCount, entities: entities.map((e: any) => ({ type: e.type, id: e.id, name: e.name })), warnings: [], }; } catch (error: any) { logger.error( `[EntityService.bulkDeleteByTag] Error deleting entities tagged with ${tagId} in ${repositoryName}:${branch}: ${error.message}`, { error: error.toString() }, ); throw error; } } async bulkDeleteByBranch( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, targetBranch: string, options: { dryRun?: boolean; force?: boolean } = {}, ): Promise<{ count: number; entities: Array<{ type: string; id: string; name?: string }>; warnings: string[]; }> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.bulkDeleteByBranch] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); // Find all entities in the target branch const entityTypes = ['Component', 'Decision', 'Rule', 'Context']; let allEntities: any[] = []; for (const entityType of entityTypes) { const findQuery = ` MATCH (n:${entityType}) WHERE n.repository = $repositoryName AND n.branch = $targetBranch RETURN n.id as id, n.name as name, '${entityType}' as type `; const entities = await kuzuClient.executeQuery(findQuery, { repositoryName, targetBranch }); allEntities = allEntities.concat(entities); } if (options.dryRun) { logger.info( `[EntityService.bulkDeleteByBranch] Dry run: Would delete ${allEntities.length} entities from branch ${targetBranch}`, ); return { count: allEntities.length, entities: allEntities.map((e: any) => ({ type: e.type, id: e.id, name: e.name })), warnings: [], }; } // Delete all entities from the branch let totalDeleted = 0; for (const entityType of entityTypes) { const deleteQuery = ` MATCH (n:${entityType}) WHERE n.repository = $repositoryName AND n.branch = $targetBranch DETACH DELETE n RETURN count(n) as deletedCount `; const result = await kuzuClient.executeQuery(deleteQuery, { repositoryName, targetBranch }); totalDeleted += result[0]?.deletedCount || 0; } logger.info( `[EntityService.bulkDeleteByBranch] Deleted ${totalDeleted} entities from branch ${targetBranch} in ${repositoryName}`, ); return { count: totalDeleted, entities: allEntities.map((e: any) => ({ type: e.type, id: e.id, name: e.name })), warnings: [], }; } catch (error: any) { logger.error( `[EntityService.bulkDeleteByBranch] Error deleting entities from branch ${targetBranch} in ${repositoryName}: ${error.message}`, { error: error.toString() }, ); throw error; } } async bulkDeleteByRepository( mcpContext: ToolHandlerContext, clientProjectRoot: string, repositoryName: string, options: { dryRun?: boolean; force?: boolean } = {}, ): Promise<{ count: number; entities: Array<{ type: string; id: string; name?: string }>; warnings: string[]; }> { const logger = mcpContext.logger || console; if (!this.repositoryProvider) { logger.error('[EntityService.bulkDeleteByRepository] RepositoryProvider not initialized'); throw new Error('RepositoryProvider not initialized'); } try { const kuzuClient = await this.getKuzuClient(mcpContext, clientProjectRoot); // Find all entities in the repository (all branches) const entityTypes = ['Component', 'Decision', 'Rule', 'Context']; let allEntities: any[] = []; for (const entityType of entityTypes) { const findQuery = ` MATCH (n:${entityType}) WHERE n.repository = $repositoryName RETURN n.id as id, n.name as name, n.branch as branch, '${entityType}' as type `; const entities = await kuzuClient.executeQuery(findQuery, { repositoryName }); allEntities = allEntities.concat(entities); } if (options.dryRun) { logger.info( `[EntityService.bulkDeleteByRepository] Dry run: Would delete ${allEntities.length} entities from repository ${repositoryName}`, ); return { count: allEntities.length, entities: allEntities.map((e: any) => ({ type: e.type, id: e.id, name: e.name })), warnings: [], }; } // Delete all entities from the repository let totalDeleted = 0; for (const entityType of entityTypes) { const deleteQuery = ` MATCH (n:${entityType}) WHERE n.repository = $repositoryName DETACH DELETE n RETURN count(n) as deletedCount `; const result = await kuzuClient.executeQuery(deleteQuery, { repositoryName }); totalDeleted += result[0]?.deletedCount || 0; } logger.info( `[EntityService.bulkDeleteByRepository] Deleted ${totalDeleted} entities from repository ${repositoryName}`, ); return { count: totalDeleted, entities: allEntities.map((e: any) => ({ type: e.type, id: e.id, name: e.name })), warnings: [], }; } catch (error: any) { logger.error( `[EntityService.bulkDeleteByRepository] Error deleting entities from repository ${repositoryName}: ${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