@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
218 lines • 9.48 kB
JavaScript
/**
* Planning Manager - Manages tasks in a Markdown file.
* Inspired by the spec-driven approach of kiro.dev.
*/
import fs from 'fs/promises';
import { existsSync, mkdirSync } from 'fs';
import path from 'path';
import matter from 'gray-matter';
export class PlanningManager {
topicManager;
constructor(topicManager) {
this.topicManager = topicManager;
}
getPlanningFilePath(projectName) {
const projectPath = this.topicManager.getMemoryRootPath();
return path.join(projectPath, projectName, 'planning.md');
}
async loadPlanningFile(projectName) {
const filePath = this.getPlanningFilePath(projectName);
if (!existsSync(filePath)) {
return this.createEmptyPlanningFile(projectName);
}
const fileContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(fileContent);
const objectives = this.parseObjectives(content);
const categories = this.parseTasks(content);
return {
version: data.version || '1.0',
status: data.status || 'New',
last_updated: data.last_updated || new Date().toISOString(),
objectives,
categories,
};
}
async savePlanningFile(projectName, planning) {
const filePath = this.getPlanningFilePath(projectName);
const projectDir = path.dirname(filePath);
if (!existsSync(projectDir)) {
mkdirSync(projectDir, { recursive: true });
}
planning.last_updated = new Date().toISOString();
const frontMatter = {
version: planning.version,
status: planning.status,
last_updated: planning.last_updated,
};
let markdownContent = `## 🎯 Objetivos Principais\n\n`;
planning.objectives.forEach(obj => {
markdownContent += `- ${obj}\n`;
});
markdownContent += `\n## Detalhamento das Tarefas\n\n`;
planning.categories.forEach(category => {
markdownContent += `### ${category.name}\n\n`;
category.tasks.forEach(task => {
const statusIcon = task.status === 'Completed' ? '[DONE]' : task.status === 'In Progress' ? '[DOING]' : '[TODO]';
markdownContent += `- **ID: ${task.id}**\n`;
markdownContent += ` - **Descrição**: ${task.description}\n`;
markdownContent += ` - **Status**: ${task.status} ${statusIcon}\n`;
markdownContent += ` - **Prioridade**: ${task.priority}\n`;
if (task.assignee)
markdownContent += ` - **Responsável**: ${task.assignee}\n`;
if (task.deadline)
markdownContent += ` - **Prazo**: ${task.deadline}\n`;
markdownContent += ` - **Criado**: ${new Date(task.created).toISOString()}\n`;
markdownContent += ` - **Modificado**: ${new Date(task.modified).toISOString()}\n`;
if (task.subtasks.length > 0) {
markdownContent += ` - **Sub-tarefas**:\n`;
task.subtasks.forEach(sub => {
markdownContent += ` - [${sub.completed ? 'x' : ' '}] ${sub.description}\n`;
});
}
markdownContent += `\n`;
});
});
const fileContent = matter.stringify(markdownContent, frontMatter);
await fs.writeFile(filePath, fileContent, 'utf-8');
}
createEmptyPlanningFile(projectName) {
return {
version: '1.0',
status: 'New',
last_updated: new Date().toISOString(),
objectives: [],
categories: [],
};
}
parseObjectives(content) {
const objectivesSection = content.match(/## 🎯 Objetivos Principais\n\n([\s\S]*?)\n\n##/);
if (!objectivesSection || !objectivesSection[1])
return [];
const objectivesContent = objectivesSection[1];
return objectivesContent.split('\n').filter(line => line.startsWith('- ')).map(line => line.substring(2));
}
parseTasks(content) {
const categories = [];
const categorySections = content.split('### ').slice(1);
categorySections.forEach(section => {
const lines = section.split('\n');
if (lines.length === 0 || !lines[0])
return;
const categoryName = lines[0].trim();
const category = { name: categoryName, tasks: [] };
const taskChunks = section.split('- **ID: ').slice(1);
taskChunks.forEach(chunk => {
const taskLines = chunk.split('\n');
if (taskLines.length === 0 || !taskLines[0])
return;
const id = taskLines[0].trim().replace(/\*\*/g, "");
const task = {
id,
description: '',
status: 'Pending',
priority: 'Medium',
created: 0,
modified: 0,
subtasks: []
};
taskLines.forEach(line => {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('- **Descrição**:')) {
task.description = trimmedLine.substring(16).trim();
}
else if (trimmedLine.startsWith('- **Status**:')) {
const statusMatch = trimmedLine.match(/- \*\*Status\*\*: (Pending|In Progress|Completed|Blocked)/);
if (statusMatch) {
task.status = statusMatch[1];
}
}
else if (trimmedLine.startsWith('- **Prioridade**:')) {
task.priority = trimmedLine.substring(17).trim();
}
else if (trimmedLine.startsWith('- **Responsável**:')) {
task.assignee = trimmedLine.substring(18).trim();
}
else if (trimmedLine.startsWith('- **Prazo**:')) {
task.deadline = trimmedLine.substring(12).trim();
}
else if (trimmedLine.startsWith('- **Criado**:')) {
task.created = new Date(trimmedLine.substring(13).trim()).getTime();
}
else if (trimmedLine.startsWith('- **Modificado**:')) {
task.modified = new Date(trimmedLine.substring(17).trim()).getTime();
}
else if (trimmedLine.startsWith('- [')) {
const subtaskMatch = trimmedLine.match(/- \[(x| )\].*/);
if (subtaskMatch && subtaskMatch[2]) {
task.subtasks.push({
completed: subtaskMatch[1] === 'x',
description: subtaskMatch[2].trim(),
});
}
}
});
category.tasks.push(task);
});
categories.push(category);
});
return categories;
}
async addTask(projectName, categoryName, task) {
const planning = await this.loadPlanningFile(projectName);
let category = planning.categories.find(c => c.name === categoryName);
if (!category) {
category = { name: categoryName, tasks: [] };
planning.categories.push(category);
}
const now = Date.now();
const newTaskId = `T-${(planning.categories.flatMap(c => c.tasks).length + 1).toString().padStart(2, '0')}`;
const newTask = {
...task,
id: newTaskId,
created: now,
modified: now,
subtasks: (task.subtasks || []).map(desc => ({ description: desc, completed: false })),
};
category.tasks.push(newTask);
await this.savePlanningFile(projectName, planning);
return newTask;
}
async updateTask(projectName, taskId, updates) {
const planning = await this.loadPlanningFile(projectName);
let task;
let category;
for (const cat of planning.categories) {
task = cat.tasks.find(t => t.id === taskId);
if (task) {
category = cat;
break;
}
}
if (task && category) {
Object.assign(task, updates);
task.modified = Date.now();
await this.savePlanningFile(projectName, planning);
return task;
}
return null;
}
async listTasks(projectName, categoryName) {
const planning = await this.loadPlanningFile(projectName);
if (categoryName) {
const category = planning.categories.find(c => c.name === categoryName);
return category ? category.tasks : [];
}
return planning.categories.flatMap(c => c.tasks);
}
async getTask(projectName, taskId) {
const planning = await this.loadPlanningFile(projectName);
for (const cat of planning.categories) {
const task = cat.tasks.find(t => t.id === taskId);
if (task) {
return task;
}
}
return null;
}
}
//# sourceMappingURL=planning-manager.js.map