UNPKG

@memory-bank/mcp

Version:

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

237 lines 11.1 kB
import path from "path"; import fs from "fs/promises"; // Keep fs for other methods if needed import { BranchInfo } from "../../../domain/entities/BranchInfo.js"; import { DocumentPath } from "../../../domain/entities/DocumentPath.js"; import { MemoryDocument } from "../../../domain/entities/MemoryDocument.js"; import { Tag } from "../../../domain/entities/Tag.js"; import { logger } from "../../../shared/utils/logger.js"; import { DomainError, DomainErrorCodes } from "../../../shared/errors/DomainError.js"; import { BranchDirectoryManager } from "./BranchDirectoryManager.js"; import { DocumentIO } from "./DocumentIO.js"; import { DocumentLister } from "./DocumentLister.js"; import { TagIndexHandler } from "./TagIndexHandler.js"; import { DocumentFinder } from "./DocumentFinder.js"; // ★ DocumentFinder をインポート ★ /** * Simple file system implementation of branch memory bank repository for testing * Acts as a Facade, delegating operations to specialized classes. */ export class FileSystemBranchMemoryBankRepository { branchMemoryBankPath; branchDirectoryManager; documentIO; documentLister; tagIndexHandler; documentFinder; // ★ DocumentFinder のインスタンスを保持 ★ /** * Constructor * @param rootPath Root path for the memory bank */ constructor(rootPath) { this.branchMemoryBankPath = path.join(rootPath, 'branch-memory-bank'); this.branchDirectoryManager = new BranchDirectoryManager(rootPath); this.documentIO = new DocumentIO(rootPath); this.documentLister = new DocumentLister(rootPath); this.tagIndexHandler = new TagIndexHandler(rootPath); // ★ DocumentFinder に依存クラスを渡してインスタンス生成 ★ this.documentFinder = new DocumentFinder(this.documentLister, this.documentIO); } /** * Check if branch exists * @param branchName Branch name * @returns Promise resolving to boolean indicating if branch exists */ async exists(branchName) { return this.branchDirectoryManager.exists(branchName); } /** * Initialize branch memory bank * @param branchInfo Branch information * @returns Promise resolving when initialization is complete */ async initialize(branchInfo) { const safeBranchName = branchInfo.safeName; logger.debug('Initializing branch memory bank:', { originalName: branchInfo.name, safeName: safeBranchName, }); try { await this.branchDirectoryManager.ensureBranchDirectoryExists(safeBranchName); const now = new Date().toISOString(); const coreFilesToCreate = [ { fileName: 'branchContext.json', content: { schema: 'memory_document_v2', metadata: { id: `${safeBranchName}-context`, title: `Branch Context for ${branchInfo.name}`, documentType: 'branch_context', path: 'branchContext.json', tags: [], createdAt: now, lastModified: now }, content: { description: `Context for branch: ${branchInfo.name}` } } }, { fileName: 'progress.json', content: { schema: 'memory_document_v2', metadata: { id: `${safeBranchName}-progress`, title: `Progress for ${branchInfo.name}`, documentType: 'progress', path: 'progress.json', tags: [], createdAt: now, lastModified: now }, content: { summary: `Initial progress for branch: ${branchInfo.name}`, status: 'initialized', steps: [], next_steps: [], findings: [] } } }, { fileName: 'activeContext.json', content: { schema: 'memory_document_v2', metadata: { id: `${safeBranchName}-active-context`, title: `Active Context for ${branchInfo.name}`, documentType: 'active_context', path: 'activeContext.json', tags: [], createdAt: now, lastModified: now }, content: { current_task: `Initial active context for branch: ${branchInfo.name}`, relevant_files: [], recent_decisions: [] } } }, { fileName: 'systemPatterns.json', content: { schema: 'memory_document_v2', metadata: { id: `${safeBranchName}-system-patterns`, title: `System Patterns for ${branchInfo.name}`, documentType: 'system_patterns', path: 'systemPatterns.json', tags: [], createdAt: now, lastModified: now }, content: { patterns: [] } } } ]; // Use for...of loop for sequential creation to ensure files exist before listDocuments might be called for (const fileInfo of coreFilesToCreate) { const docPath = DocumentPath.create(fileInfo.fileName); const tempDoc = MemoryDocument.create({ path: docPath, content: JSON.stringify(fileInfo.content, null, 2), tags: fileInfo.content.metadata.tags.map((t) => Tag.create(t)), lastModified: new Date(fileInfo.content.metadata.lastModified) }); await this.documentIO.saveDocument(branchInfo, tempDoc); logger.debug(`[initialize] Created core file: ${fileInfo.fileName}`); // Add log for confirmation } logger.info(`[initialize] Finished creating all core files for branch: ${safeBranchName}`); // Add log to confirm loop completion } catch (error) { logger.error('Failed to initialize branch memory bank or create core files:', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); throw new DomainError(DomainErrorCodes.REPOSITORY_ERROR, `Failed to initialize branch memory bank or create core files: ${error instanceof Error ? error.message : String(error)}`); } } /** * Get document from branch * @param branchInfo Branch information * @param documentPath Document path * @returns Promise resolving to document if found, null otherwise */ async getDocument(branchInfo, documentPath) { return this.documentIO.getDocument(branchInfo, documentPath); } /** * Save document to branch * @param branchInfo Branch information * @param document Document to save * @returns Promise resolving when done */ async saveDocument(branchInfo, document) { await this.documentIO.saveDocument(branchInfo, document); } /** * Delete document from branch * @param branchInfo Branch information * @param documentPath Document path * @returns Promise resolving to boolean indicating success */ async deleteDocument(branchInfo, documentPath) { return this.documentIO.deleteDocument(branchInfo, documentPath); } /** * List all documents in branch * @param branchInfo Branch information * @returns Promise resolving to array of document paths */ async listDocuments(branchInfo) { return this.documentLister.listDocuments(branchInfo); } /** * Find documents by tags in branch * @param branchInfo Branch information * @param tags Tags to search for * @returns Promise resolving to array of matching documents */ async findDocumentsByTags(branchInfo, tags) { // ★ Delegate to DocumentFinder ★ return this.documentFinder.findDocumentsByTags(branchInfo, tags); } /** * Get recent branches (これはこのクラスに残すか、別のManagerか?) * @param limit Maximum number of branches to return * @returns Promise resolving to array of recent branches */ async getRecentBranches(limit) { // TODO: Delegate to a new RecentBranchFinder class? Or keep here? try { const entries = await fs.readdir(this.branchMemoryBankPath); const branches = []; for (const entry of entries) { try { const entryPath = path.join(this.branchMemoryBankPath, entry); const stats = await fs.stat(entryPath); if (!stats.isDirectory()) { continue; } const branchInfo = BranchInfo.create(entry); const summary = {}; // Placeholder for summary branches.push({ branchInfo, lastModified: stats.mtime, summary: summary }); } catch (err) { logger.warn(`Skipping invalid branch entry: ${entry}`, { error: err instanceof Error ? err.message : String(err) }); } } branches.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime()); return branches.slice(0, limit || 10); } catch (error) { logger.error(`Error reading recent branches from ${this.branchMemoryBankPath}:`, error); return []; } } /** * Validate branch structure * @param branchInfo Branch information * @returns Promise resolving to boolean indicating if structure is valid */ async validateStructure(branchInfo) { return this.exists(branchInfo.safeName); } /** * Save tag index for branch * @param branchInfo Branch information * @param tagIndex Tag index to save * @returns Promise resolving when done */ async saveTagIndex(branchInfo, tagIndex) { await this.tagIndexHandler.saveTagIndex(branchInfo, tagIndex); } /** * Get tag index for branch * @param branchInfo Branch information * @returns Promise resolving to tag index if found, null otherwise */ async getTagIndex(branchInfo) { return this.tagIndexHandler.getTagIndex(branchInfo); } /** * Find documents by tags in branch using index * @param branchInfo Branch information * @param tags Tags to search for * @param matchAll If true, documents must have all tags (AND), otherwise any tag (OR) * @returns Promise resolving to array of document paths */ async findDocumentPathsByTagsUsingIndex(params) { // ★ Delegate to DocumentFinder ★ // Note: DocumentFinder currently has a fallback implementation. // Actual index usage would require passing TagIndexHandler to DocumentFinder. return this.documentFinder.findDocumentPathsByTagsUsingIndex(params); } } //# sourceMappingURL=FileSystemBranchMemoryBankRepository.js.map