Skip to main content
Glama
plan-auto-save.service.ts4.34 kB
import fs from 'fs'; import path from 'path'; import databaseService from './database.service.js'; import embeddingService from './embedding.service.js'; /** * Service responsible for automatically saving SDOF plans to both the filesystem * and the knowledge base. */ export class PlanAutoSaveService { private static instance: PlanAutoSaveService; private constructor() {} /** * Get the singleton instance of the PlanAutoSaveService */ public static getInstance(): PlanAutoSaveService { if (!PlanAutoSaveService.instance) { PlanAutoSaveService.instance = new PlanAutoSaveService(); } return PlanAutoSaveService.instance; } /** * Save a plan to both the filesystem and the knowledge base * * @param planTitle The title of the plan * @param planContent The markdown content of the plan * @param planType The type of plan (e.g., 'exploration', 'analysis', 'implementation') * @param tags Additional tags to categorize the plan * @returns Object containing the file path and knowledge base entry ID */ public async savePlan( planTitle: string, planContent: string, planType: string, tags: string[] = [] ): Promise<{ filePath: string; entryId: string }> { // 1. Save to filesystem const filePath = await this.saveToFilesystem(planTitle, planContent, planType); // 2. Save to knowledge base const entryId = await this.saveToKnowledgeBase(planTitle, planContent, planType, tags); return { filePath, entryId }; } /** * Save plan to filesystem in the appropriate directory */ private async saveToFilesystem( planTitle: string, planContent: string, planType: string ): Promise<string> { // Create sanitized filename from title const sanitizedTitle = planTitle .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, ''); // Get current date for folder organization const date = new Date(); const dateStr = date.toISOString().split('T')[0]; // Determine directory based on plan type let dirPath: string; switch (planType.toLowerCase()) { case 'exploration': dirPath = path.join('docs', 'plans', 'explorations'); break; case 'analysis': dirPath = path.join('docs', 'plans', 'analyses'); break; case 'implementation': dirPath = path.join('docs', 'plans', 'implementations'); break; case 'evaluation': dirPath = path.join('docs', 'plans', 'evaluations'); break; case 'integration': dirPath = path.join('docs', 'plans', 'integrations'); break; case 'synthesis': dirPath = path.join('docs', 'plans', 'syntheses'); break; default: dirPath = path.join('docs', 'plans', 'misc'); } // Create directory if it doesn't exist if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } // Create file path const fileName = `${dateStr}-${sanitizedTitle}.md`; const filePath = path.join(dirPath, fileName); // Write file fs.writeFileSync(filePath, planContent); return filePath; } /** * Save plan to knowledge base */ private async saveToKnowledgeBase( planTitle: string, planContent: string, planType: string, tags: string[] ): Promise<string> { // Connect to database await databaseService.connect(); // Generate embedding for combined content const embeddingText = `${planTitle}\n${planContent}`; const vector = await embeddingService.generateEmbedding(embeddingText); // Create knowledge entry const newEntry = await databaseService.createEntry({ title: planTitle, content: planContent, contentType: planType, category: 'sdof-plan', tags: [...tags, 'sdof', 'plan', planType], sourceReference: 'sdof-orchestrator', vector }); // Safely get the ID as a string, handling potential undefined or null const entryId = newEntry?._id ? (typeof newEntry._id.toString === 'function' ? newEntry._id.toString() : String(newEntry._id) ) : 'unknown-id'; return entryId; } } // Export singleton instance export default PlanAutoSaveService.getInstance();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tgf-between-your-legs/sdof-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server