UNPKG

@memory-bank/mcp

Version:

Memory-enabled Co-Pilot (MCP) server for managing project documentation and context

224 lines 11.1 kB
import { logger } from '../../shared/utils/logger.js'; import { DomainErrors } from '../../shared/errors/DomainError.js'; import { ApplicationErrors } from '../../shared/errors/ApplicationError.js'; import { BaseError } from '../../shared/errors/BaseError.js'; /** * Controller for branch related operations */ export class BranchController { readBranchDocumentUseCase; writeBranchDocumentUseCase; searchDocumentsByTagsUseCase; updateTagIndexUseCase; getRecentBranchesUseCase; readBranchCoreFilesUseCase; createBranchCoreFilesUseCase; presenter; configProvider; componentLogger = logger.withContext({ component: 'BranchController' }); constructor(readBranchDocumentUseCase, writeBranchDocumentUseCase, searchDocumentsByTagsUseCase, updateTagIndexUseCase, getRecentBranchesUseCase, readBranchCoreFilesUseCase, createBranchCoreFilesUseCase, presenter, configProvider) { this.readBranchDocumentUseCase = readBranchDocumentUseCase; this.writeBranchDocumentUseCase = writeBranchDocumentUseCase; this.searchDocumentsByTagsUseCase = searchDocumentsByTagsUseCase; this.updateTagIndexUseCase = updateTagIndexUseCase; this.getRecentBranchesUseCase = getRecentBranchesUseCase; this.readBranchCoreFilesUseCase = readBranchCoreFilesUseCase; this.createBranchCoreFilesUseCase = createBranchCoreFilesUseCase; this.presenter = presenter; this.configProvider = configProvider; } /** * Read a branch document */ async readDocument(branchName, path) { try { // branchName が undefined でもログにはそのまま記録される this.componentLogger.info('Reading branch document', { operation: 'readDocument', branchName, path }); // UseCase にそのまま渡す(UseCase側で undefined を処理) const document = await this.readBranchDocumentUseCase.execute({ branchName, // undefined の可能性あり path, }); return this.presenter.presentSuccess(document); } catch (error) { // エラーログでも branchName は undefined の可能性がある this.componentLogger.error('Failed to read branch document', { operation: 'readDocument', branchName, path, error }); return this.handleError(error); } } /** * Write a branch document */ async writeDocument(params) { // Destructure params const { branchName, path, content, tags, patches } = params; try { // Determine conditions first const hasPatches = patches && Array.isArray(patches) && patches.length > 0; // Check if content is not undefined, not null, AND not an empty string const hasContent = content !== undefined && content !== null && content !== ''; // branchName が undefined でもログにはそのまま記録される this.componentLogger.info('Writing branch document', { operation: 'writeDocument', branchName, path, hasContent, hasPatches }); if (hasPatches) { // Call WriteBranchDocumentUseCase with patches const result = await this.writeBranchDocumentUseCase.execute({ branchName, // undefined の可能性あり document: { path: path, tags: tags, content: undefined // Pass undefined when using patches }, patches: patches // Pass the patches array }); // Log the result from the use case and the data being presented return this.presenter.presentSuccess(result.document); } else if (hasContent) { // If content is provided (and no patches), call the existing UseCase // Content is already known to be a non-empty string here due to hasContent check const result = await this.writeBranchDocumentUseCase.execute({ branchName, // undefined の可能性あり document: { path: path, content: content, // Pass the valid string content tags: tags // Pass tags if available } // No patches field here }); // Return the document DTO directly from the use case result return this.presenter.presentSuccess(result.document); } else { // Handle the case where neither content nor patches are provided (or patches is an empty array) // This might need a dedicated initialization UseCase or logic. // For now, return the initialization message similar to routes.ts logic. // Or call UseCase with empty content for initialization? Let's return a specific message for now. const patchesType = typeof patches; const patchesValue = JSON.stringify(patches); const patchesLength = Array.isArray(patches) ? patches.length : 'N/A'; const contentType = typeof content; const contentValue = JSON.stringify(content); const debugMessage = `DEBUG: Entered init branch unexpectedly. patches type: ${patchesType}, patches length: ${patchesLength}, patches value: ${patchesValue}, content type: ${contentType}, content value: ${contentValue}`; // branchName が undefined でもログにはそのまま記録される this.componentLogger.info(debugMessage, { operation: 'writeDocument', branchName, path }); // Return debug information in the response return this.presenter.presentSuccess({ message: debugMessage }); } } catch (error) { // Log the error with destructured variables available in this scope // エラーログでも branchName は undefined の可能性がある this.componentLogger.error('Failed to write branch document', { operation: 'writeDocument', branchName, path, error }); // Log with available context return this.handleError(error); } } // Closing brace for the writeDocument method /** * Read core files for a branch */ async readCoreFiles(branchName) { try { this.componentLogger.info('Reading branch core files', { operation: 'readCoreFiles', branchName }); const result = await this.readBranchCoreFilesUseCase.execute({ branchName }); return this.presenter.presentSuccess(result); } catch (error) { this.componentLogger.error('Failed to read branch core files', { operation: 'readCoreFiles', branchName, error }); return this.handleError(error); } } /** * Create core files for a branch */ async createCoreFiles(branchName, files) { try { this.validateFiles(files); this.componentLogger.info('Creating branch core files', { operation: 'createCoreFiles', branchName }); await this.createBranchCoreFilesUseCase.execute({ branchName: branchName, files, }); return this.presenter.presentSuccess({ message: 'Core files created successfully' }); } catch (error) { this.componentLogger.error('Failed to create branch core files', { operation: 'createCoreFiles', branchName, error }); return this.handleError(error); } } /** * Search documents by tags */ // Update method signature and implementation to use new input schema // Update method signature: remove docs from params as it's obtained via configProvider async searchByTags(params) { try { const { tags, branchName, match } = params; // Remove docs from destructuring this.componentLogger.info('Searching branch documents by tags', { operation: 'searchByTags', branchName, tags, match }); // 元のログに戻す // Get docs path from injected configProvider const docsPath = this.configProvider.getConfig().docsRoot; // Use a different variable name if (!docsPath) { throw new Error("Docs path could not be determined from config."); } const result = await this.searchDocumentsByTagsUseCase.execute({ tags, branchName, match: match ?? 'or', // Default to 'or' scope: 'branch', // Always search within the specified branch docs: docsPath, // Pass determined docs path }); return this.presenter.presentSuccess(result); // Return the SearchResults object } catch (error) { this.componentLogger.error('Failed to search documents by tags', { operation: 'searchByTags', branchName: params.branchName, tags: params.tags, match: params.match, error }); // Use params.tags return this.handleError(error); } } /** * Update tag index for a branch */ async updateTagIndex(branchName) { try { this.componentLogger.info('Updating tag index', { operation: 'updateTagIndex', branchName }); await this.updateTagIndexUseCase.execute({ branchName }); return this.presenter.presentSuccess({ message: 'Tag index updated successfully' }); } catch (error) { this.componentLogger.error('Failed to update tag index', { operation: 'updateTagIndex', error }); return this.handleError(error); } } /** * Get recent branches */ async getRecentBranches() { try { this.componentLogger.info('Getting recent branches', { operation: 'getRecentBranches' }); const branches = await this.getRecentBranchesUseCase.execute({}); return this.presenter.presentSuccess(branches); } catch (error) { this.componentLogger.error('Failed to get recent branches', { operation: 'getRecentBranches', error }); return this.handleError(error); } } /** * Validate files input */ validateFiles(files) { if (!files || typeof files !== 'object') { throw DomainErrors.validationError('Files must be provided as an object'); } } /** * Handle errors */ handleError(error) { if (error instanceof BaseError) { return this.presenter.presentError(error); } // Convert unknown errors to ApplicationError const applicationError = ApplicationErrors.unexpectedControllerError('BranchController', error instanceof Error ? error : undefined); return this.presenter.presentError(applicationError); } } //# sourceMappingURL=BranchController.js.map