@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
JavaScript
/**
* 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