UNPKG

@andrebuzeli/advanced-memory-markdown-mcp

Version:

Advanced Memory Bank MCP v3.1.5 - Sistema avançado de gerenciamento de memória com isolamento de projetos por IDE, sincronização sob demanda, backup a cada 30min, apenas arquivos .md principais sincronizados, pasta reasoning temporária com limpeza automát

460 lines 17.5 kB
/** * New Task Manager v2.0 - Gerencia tarefas em hierarquia de 3 camadas * * IMPORTANTE - NOME DO PROJETO: * - O projectName DEVE ser exatamente o nome da pasta RAIZ do projeto aberto no IDE * - NÃO é uma subpasta, NÃO é um subprojeto - é a pasta raiz que foi aberta no IDE * - O nome deve ser uma cópia EXATA sem adicionar ou remover nada * * RESPONSABILIDADE - EXECUÇÃO E STATUS: * - Gerenciamento de tarefas operacionais * - Controle de status e progresso individual * - Hierarquia de execução (1, 2, 3 níveis) * - Rastreamento de responsabilidades * - Controle de qualidade e erros * * Funcionalidades v2.0: * - Operações individuais e múltiplas (exceto status) * - Hierarquia de 3 níveis (1, 2, 3) * - Status com emojis visuais (individual apenas) * - Sistema de prioridades * - Campos de erro/falha com descrição * - Armazenamento em task.md no MEMORY_BANK_ROOT * - Sincronização automática com pasta .memory-bank local */ import * as fs from 'fs/promises'; import { SyncManager } from './sync-manager.js'; export class NewTaskManager { syncManager; statusEmojis = { 'not-started': '[TODO]', 'in-progress': '[DOING]', 'complete': '[DONE]', 'retry': '[RETRY]', 'error': '[ERROR]' }; constructor() { this.syncManager = new SyncManager(); } /** * Creates a new task */ async create(projectName, title, description = '', level = 1, parentId, priority = 5, tags = []) { // Ensure project structure exists await this.syncManager.ensureProjectStructure(projectName); const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const taskId = this.generateTaskId(level); const task = { id: taskId, title, description, status: 'not-started', level, ...(parentId && { parentId }), created: Date.now(), modified: Date.now(), priority, tags, subtasks: [] }; await this.addTaskToFile(taskFile, task); return task; } /** * Reads a specific task */ async read(projectName, taskId) { const tasks = await this.list(projectName); return this.findTaskById(tasks, taskId); } /** * Updates a task status */ async status(projectName, taskId, status, errorReason) { const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const tasks = await this.loadTasksFromFile(taskFile); const task = this.findTaskById(tasks, taskId); if (!task) return null; task.status = status; task.modified = Date.now(); // Handle error/retry status with reason if ((status === 'retry' || status === 'error') && errorReason) { task.errorReason = errorReason; task.lastAttempt = Date.now(); } else if (status === 'complete') { // Clear error fields when task is completed const updates = { ...task }; delete updates.errorReason; delete updates.lastAttempt; Object.assign(task, updates); } await this.saveTasksToFile(taskFile, tasks); return task; } /** * Updates a task */ async update(projectName, taskId, updates) { const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const tasks = await this.loadTasksFromFile(taskFile); const task = this.findTaskById(tasks, taskId); if (!task) return null; const updatedTask = { ...task, ...updates, modified: Date.now() }; this.updateTaskInArray(tasks, taskId, updatedTask); await this.saveTasksToFile(taskFile, tasks); return updatedTask; } /** * Deletes a task by ID */ async delete(projectName, taskId) { const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const tasks = await this.loadTasksFromFile(taskFile); const taskToDelete = this.findTaskById(tasks, taskId); if (!taskToDelete) { return null; } // Remove the task and its subtasks this.removeTaskFromArray(tasks, taskId); await this.saveTasksToFile(taskFile, tasks); return taskToDelete; } /** * Lists all tasks for a project */ async list(projectName) { const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); return await this.loadTasksFromFile(taskFile); } // ========== OPERAÇÕES MÚLTIPLAS v2.0 ========== // NOTA: Status permanece individual para controle preciso /** * Creates multiple tasks at once */ async createMultiple(projectName, tasks) { await this.syncManager.ensureProjectStructure(projectName); const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const existingTasks = await this.loadTasksFromFile(taskFile); const createdTasks = []; for (const taskData of tasks) { const task = { id: this.generateTaskId(taskData.level || 1), title: taskData.title, description: taskData.description || '', status: 'not-started', level: taskData.level || 1, ...(taskData.parentId && { parentId: taskData.parentId }), created: Date.now(), modified: Date.now(), priority: taskData.priority || 5, tags: taskData.tags || [], subtasks: [] }; existingTasks.push(task); createdTasks.push(task); } await this.saveTasksToFile(taskFile, existingTasks); return createdTasks; } /** * Reads multiple tasks at once */ async readMultiple(projectName, taskIds) { const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const tasks = await this.loadTasksFromFile(taskFile); return taskIds.map(taskId => this.findTaskById(tasks, taskId)); } /** * Updates multiple tasks at once (excluding status) */ async updateMultiple(projectName, updates) { const taskFile = this.syncManager.getProjectFilePath(projectName, 'task.md'); const tasks = await this.loadTasksFromFile(taskFile); const updatedTasks = []; for (const updateData of updates) { const task = this.findTaskById(tasks, updateData.taskId); if (task) { const updatedTask = { ...task, ...updateData.updates, modified: Date.now() }; this.updateTaskInArray(tasks, updateData.taskId, updatedTask); updatedTasks.push(updatedTask); } else { updatedTasks.push(null); } } await this.saveTasksToFile(taskFile, tasks); return updatedTasks; } /** * Generates a unique task ID based on level */ generateTaskId(level) { const prefix = level === 1 ? 'T' : level === 2 ? 'ST' : 'SST'; const timestamp = Date.now().toString().slice(-6); const random = Math.random().toString(36).substr(2, 3).toUpperCase(); return `${prefix}-${timestamp}-${random}`; } /** * Finds a task by ID recursively */ findTaskById(tasks, taskId) { for (const task of tasks) { if (task.id === taskId) { return task; } const found = this.findTaskById(task.subtasks, taskId); if (found) { return found; } } return null; } /** * Updates a task in the array recursively */ updateTaskInArray(tasks, taskId, updatedTask) { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; if (task && task.id === taskId) { tasks[i] = updatedTask; return true; } if (task && this.updateTaskInArray(task.subtasks, taskId, updatedTask)) { return true; } } return false; } /** * Removes a task and its subtasks from the array recursively */ removeTaskFromArray(tasks, taskId) { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; if (task && task.id === taskId) { tasks.splice(i, 1); return true; } if (task && this.removeTaskFromArray(task.subtasks, taskId)) { return true; } } return false; } /** * Adds a task to the task.md file */ async addTaskToFile(filePath, task) { const tasks = await this.loadTasksFromFile(filePath); if (task.parentId) { // Add as subtask const parent = this.findTaskById(tasks, task.parentId); if (parent) { parent.subtasks.push(task); } } else { // Add as main task tasks.push(task); } await this.saveTasksToFile(filePath, tasks); } /** * Loads tasks from task.md file */ async loadTasksFromFile(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); const tasks = []; // Parse markdown content to extract tasks const lines = content.split('\n'); let inTasksSection = false; let currentTask = null; let currentLevel = 1; let taskStack = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (!line) continue; if (line.startsWith('## Tasks')) { inTasksSection = true; continue; } if (inTasksSection && line.trim().startsWith('---')) { break; } // Skip empty lines and comments in tasks section if (inTasksSection && (line.trim() === '' || line.trim().startsWith('<!--'))) { continue; } if (inTasksSection && line.trim() && !line.startsWith('<!--') && line.match(/^\s*- \[.\]/)) { // Parse task line: - [x] [TODO] Task Title (ID: T-123) const taskMatch = line.match(/^\s*- \[(.?)\] (.+) \(ID: ([^)]+)\)/); if (taskMatch && taskMatch[2] && taskMatch[3]) { const [, checkbox, titleWithEmoji, id] = taskMatch; // Extrair status do início do título const statusMatch = titleWithEmoji.match(/^(\[\w+\])\s*(.+)/); const statusText = statusMatch ? statusMatch[1] : '[TODO]'; const title = (statusMatch ? statusMatch[2] : titleWithEmoji.replace(/^\[\w+\]\s*/, '')) || 'Tarefa sem título'; const status = this.getStatusFromText(statusText || '[TODO]'); const level = this.getLevelFromIndentation(line); const task = { id, title, status, level: level, created: Date.now(), modified: Date.now(), priority: 5, tags: [], subtasks: [] }; // Check next line for description if (i + 1 < lines.length) { const nextLine = lines[i + 1]; if (nextLine) { const descMatch = nextLine.match(/^\s*\*(.+)\*\s*$/); if (descMatch && descMatch[1]) { task.description = descMatch[1].trim(); } } } if (level === 1) { tasks.push(task); taskStack = [task]; } else if (level === 2 && taskStack.length >= 1 && taskStack[0]) { task.parentId = taskStack[0].id; taskStack[0].subtasks.push(task); if (taskStack.length > 1) taskStack[1] = task; else taskStack.push(task); } else if (level === 3 && taskStack.length >= 2 && taskStack[1]) { task.parentId = taskStack[1].id; taskStack[1].subtasks.push(task); } } } // Parse error/failure information if (inTasksSection && line && line.includes('⚠️ **Erro/Falha:**')) { const errorMatch = line.match(/⚠️ \*\*Erro\/Falha:\*\* (.+?)(?:\s*-\s*Última tentativa: (.+))?$/); if (errorMatch && taskStack.length > 0 && errorMatch[1]) { const currentTask = taskStack[taskStack.length - 1]; if (currentTask) { currentTask.errorReason = errorMatch[1]; if (errorMatch[2]) { currentTask.lastAttempt = new Date(errorMatch[2]).getTime(); } } } } } return tasks; } catch { return []; } } /** * Gets status from text */ getStatusFromText(statusText) { switch (statusText) { case '[TODO]': return 'not-started'; case '[DOING]': return 'in-progress'; case '[DONE]': return 'complete'; case '[RETRY]': return 'retry'; case '[ERROR]': return 'error'; default: return 'not-started'; } } /** * Gets status from emoji */ getStatusFromEmoji(emoji) { for (const [status, statusEmoji] of Object.entries(this.statusEmojis)) { if (statusEmoji === emoji) { return status; } } return 'not-started'; } /** * Gets level from line indentation */ getLevelFromIndentation(line) { const match = line.match(/^(\s*)- /); if (!match || !match[1]) return 1; const spaces = match[1].length; return Math.min(Math.floor(spaces / 2) + 1, 3); } /** * Saves tasks to task.md file */ async saveTasksToFile(filePath, tasks) { try { const content = await fs.readFile(filePath, 'utf8'); const lines = content.split('\n'); // Find the tasks section const tasksIndex = lines.findIndex(line => line.startsWith('## Tasks')); const endIndex = lines.findIndex((line, index) => index > tasksIndex && line.startsWith('---')); if (tasksIndex === -1) return; // Build new tasks section const newTasksSection = ['## Tasks', '']; if (tasks.length === 0) { newTasksSection.push('<!-- Nenhuma tarefa criada ainda -->', ''); } else { this.buildTasksMarkdown(tasks, newTasksSection, 0); } // Replace the tasks section const newLines = [ ...lines.slice(0, tasksIndex), ...newTasksSection, ...lines.slice(endIndex) ]; await fs.writeFile(filePath, newLines.join('\n'), 'utf8'); } catch (error) { console.error('Error saving tasks to file:', error); } } /** * Builds markdown for tasks recursively */ buildTasksMarkdown(tasks, lines, indentLevel) { const indent = ' '.repeat(indentLevel); for (const task of tasks) { const emoji = this.statusEmojis[task.status]; const checkbox = task.status === 'complete' ? 'x' : ' '; lines.push(`${indent}- [${checkbox}] ${emoji} ${task.title} (ID: ${task.id})`); if (task.description) { lines.push(`${indent} *${task.description}*`); } if (task.errorReason && (task.status === 'retry' || task.status === 'error')) { const lastAttemptText = task.lastAttempt ? ` - Última tentativa: ${new Date(task.lastAttempt).toLocaleString()}` : ''; lines.push(`${indent} ⚠️ **Erro/Falha:** ${task.errorReason}${lastAttemptText}`); } if (task.subtasks.length > 0) { this.buildTasksMarkdown(task.subtasks, lines, indentLevel + 1); } } lines.push(''); } } //# sourceMappingURL=new-task-manager.js.map