@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
297 lines • 10.9 kB
JavaScript
/**
* Remember Tool - Busca em todos os arquivos .md dos projetos
*
* 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
*
* Funcionalidades:
* - Busca em memory.md, task.md, plan.md
* - Busca semântica com score de relevância
* - Filtros por tipo de arquivo e projeto
* - Suporte a busca case-sensitive e palavra completa
*/
import * as fs from 'fs/promises';
import { SyncManager } from './sync-manager.js';
export class RememberTool {
syncManager;
constructor() {
this.syncManager = new SyncManager();
}
/**
* Lists all available memories, topics, tasks, and plans
*/
async list(projectName, fileType = 'all') {
try {
const projects = projectName ? [projectName] : await this.syncManager.listProjects();
const content = [];
for (const project of projects) {
await this.syncManager.ensureProjectStructure(project);
const projectContent = {
projectName: project,
memories: [],
tasks: [],
plans: []
};
if (fileType === 'memory' || fileType === 'all') {
projectContent.memories = await this.extractMemoryTopics(this.syncManager.getProjectFilePath(project, 'memory.md'));
}
if (fileType === 'task' || fileType === 'all') {
projectContent.tasks = await this.extractTaskTitles(this.syncManager.getProjectFilePath(project, 'task.md'));
}
if (fileType === 'planning' || fileType === 'all') {
projectContent.plans = await this.extractPlanTitles(this.syncManager.getProjectFilePath(project, 'plan.md'));
}
content.push(projectContent);
}
return {
projects,
content
};
}
catch (error) {
console.error('Error listing content:', error);
return { projects: [], content: [] };
}
}
/**
* Reads specific content from memory files
*/
async read(projectName, fileName, itemName) {
try {
await this.syncManager.ensureProjectStructure(projectName);
let filePath;
switch (fileName) {
case 'memory':
filePath = this.syncManager.getProjectFilePath(projectName, 'memory.md');
break;
case 'task':
filePath = this.syncManager.getProjectFilePath(projectName, 'task.md');
break;
case 'planning':
filePath = this.syncManager.getProjectFilePath(projectName, 'plan.md');
break;
default:
return null;
}
const content = await fs.readFile(filePath, 'utf8');
if (itemName) {
return this.extractSpecificItem(content, itemName, fileName);
}
return content;
}
catch (error) {
console.error('Error reading content:', error);
return null;
}
}
/**
* Searches across all memory files
*/
async search(options) {
try {
const projects = options.projectName ? [options.projectName] : await this.syncManager.listProjects();
const results = [];
for (const project of projects) {
await this.syncManager.ensureProjectStructure(project);
const filesToSearch = this.getFilesToSearch(project, options.fileType || 'all');
for (const { fileName, filePath } of filesToSearch) {
const fileResults = await this.searchInFile(project, fileName, filePath, options);
results.push(...fileResults);
}
}
// Sort by score (descending) and limit results
results.sort((a, b) => b.score - a.score);
if (options.maxResults) {
return results.slice(0, options.maxResults);
}
return results;
}
catch (error) {
console.error('Error searching:', error);
return [];
}
}
/**
* Extracts memory topics from memory.md
*/
async extractMemoryTopics(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
const topics = [];
const lines = content.split('\n');
for (const line of lines) {
const topicMatch = line.match(/^### (.+) - (.+)/);
if (topicMatch) {
topics.push(`${topicMatch[1]} - ${topicMatch[2]}`);
}
}
return topics;
}
catch {
return [];
}
}
/**
* Extracts task titles from task.md
*/
async extractTaskTitles(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
const tasks = [];
const lines = content.split('\n');
for (const line of lines) {
const taskMatch = line.match(/^\s*- \[.\] \[\w+\] (.+) \(ID: (.+)\)/);
if (taskMatch) {
tasks.push(`${taskMatch[1]} (${taskMatch[2]})`);
}
}
return tasks;
}
catch {
return [];
}
}
/**
* Extracts plan titles from planning.md
*/
async extractPlanTitles(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
const plans = [];
const lines = content.split('\n');
for (const line of lines) {
const planMatch = line.match(/^### (.+) \(ID: (.+)\)/);
if (planMatch) {
plans.push(`${planMatch[1]} (${planMatch[2]})`);
}
}
return plans;
}
catch {
return [];
}
}
/**
* Extracts specific item content
*/
extractSpecificItem(content, itemName, fileType) {
const lines = content.split('\n');
let inItem = false;
let itemContent = [];
let itemPattern;
switch (fileType) {
case 'memory':
itemPattern = new RegExp(`^### ${itemName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
break;
case 'task':
itemPattern = new RegExp(`\\(ID: ${itemName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`);
break;
case 'planning':
itemPattern = new RegExp(`\\(ID: ${itemName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)`);
break;
default:
return null;
}
for (const line of lines) {
if (itemPattern.test(line)) {
inItem = true;
itemContent.push(line);
continue;
}
if (inItem) {
if (line.startsWith('###') || line.startsWith('##')) {
break;
}
itemContent.push(line);
}
}
return itemContent.length > 0 ? itemContent.join('\n') : null;
}
/**
* Gets files to search based on file type
*/
getFilesToSearch(projectName, fileType) {
const files = [];
if (fileType === 'all' || fileType === 'memory') {
files.push({
fileName: 'memory.md',
filePath: this.syncManager.getProjectFilePath(projectName, 'memory.md')
});
}
if (fileType === 'all' || fileType === 'task') {
files.push({
fileName: 'task.md',
filePath: this.syncManager.getProjectFilePath(projectName, 'task.md')
});
}
if (fileType === 'all' || fileType === 'planning') {
files.push({
fileName: 'plan.md',
filePath: this.syncManager.getProjectFilePath(projectName, 'plan.md')
});
}
return files;
}
/**
* Searches in a specific file
*/
async searchInFile(projectName, fileName, filePath, options) {
try {
const content = await fs.readFile(filePath, 'utf8');
const lines = content.split('\n');
const matches = [];
let score = 0;
const searchQuery = options.caseSensitive ? options.query : options.query.toLowerCase();
const searchPattern = options.wholeWord ? new RegExp(`\\b${searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, options.caseSensitive ? 'g' : 'gi') : null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line)
continue;
const searchLine = options.caseSensitive ? line : line.toLowerCase();
let found = false;
if (options.wholeWord && searchPattern) {
found = searchPattern.test(line);
}
else {
found = searchLine.includes(searchQuery);
}
if (found) {
const contextStart = Math.max(0, i - 2);
const contextEnd = Math.min(lines.length - 1, i + 2);
const context = lines.slice(contextStart, contextEnd + 1).join('\n');
matches.push({
line: i + 1,
content: line.trim(),
context
});
// Calculate score based on match quality
if (line.toLowerCase().includes(searchQuery)) {
score += 10;
}
if (line.startsWith('#')) {
score += 5; // Headers are more important
}
if (line.includes('**')) {
score += 3; // Bold text is more important
}
}
}
if (matches.length > 0) {
return [{
projectName,
fileName,
filePath,
matches,
score
}];
}
return [];
}
catch {
return [];
}
}
}
//# sourceMappingURL=remember-tool.js.map