UNPKG

@tosin2013/mcp-shrimp-task-manager

Version:

Enhanced MCP Shrimp Task Manager with comprehensive LLM integration. A task management tool built for AI Agents, emphasizing chain-of-thought, reflection, and style consistency. Features real GPT-4 ↔ MCP tools communication, comprehensive testing pipeline

263 lines 9.52 kB
/** * Template Engine for the Idea Honing Tool * * This component manages specification templates and their rendering, * supporting both default and custom templates. */ import fsPromises from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; // Get the base directory for templates const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const TEMPLATES_DIR = path.resolve(__dirname, '../templates'); const CUSTOM_TEMPLATES_DIR = path.join(TEMPLATES_DIR, 'custom'); /** * Loads a template by name * * @param templateName - Name of the template to load (defaults to 'default-template') * @returns Promise that resolves with the loaded template */ export async function loadTemplate(templateName = 'default-template') { try { // Check if a custom template exists first const customTemplatePath = path.join(CUSTOM_TEMPLATES_DIR, `${templateName}.md`); const defaultTemplatePath = path.join(TEMPLATES_DIR, `${templateName}.md`); let templatePath; let templateExists = false; // Try custom template first try { await fsPromises.access(customTemplatePath); templatePath = customTemplatePath; templateExists = true; } catch { // If custom template doesn't exist, try default template try { await fsPromises.access(defaultTemplatePath); templatePath = defaultTemplatePath; templateExists = true; } catch { // If neither exists, use the default-template.md templatePath = path.join(TEMPLATES_DIR, 'default-template.md'); // Check if even the default template exists try { await fsPromises.access(templatePath); } catch { throw new Error(`Template not found: ${templateName}`); } } } // Load the template content const content = await fsPromises.readFile(templatePath, 'utf-8'); // Parse the template to extract sections const sections = parseTemplateContent(content); return { name: templateName, content, sections, }; } catch (error) { throw new Error(`Failed to load template: ${error instanceof Error ? error.message : String(error)}`); } } /** * Lists all available templates * * @returns Promise that resolves with an array of template names */ export async function listTemplates() { try { const templates = new Set(); // Get default templates try { const defaultTemplates = await fsPromises.readdir(TEMPLATES_DIR); defaultTemplates .filter(file => file.endsWith('.md') && !file.startsWith('.')) .forEach(file => templates.add(file.replace('.md', ''))); } catch (error) { console.error(`Error reading default templates: ${error}`); } // Get custom templates try { const customTemplates = await fsPromises.readdir(CUSTOM_TEMPLATES_DIR); customTemplates .filter(file => file.endsWith('.md') && !file.startsWith('.')) .forEach(file => templates.add(file.replace('.md', ''))); } catch (error) { console.error(`Error reading custom templates: ${error}`); } return Array.from(templates); } catch (error) { throw new Error(`Failed to list templates: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validates a template structure * * @param template - Template to validate * @returns True if the template is valid, false otherwise */ export function validateTemplate(template) { // Check if the template has a name if (!template.name) return false; // Check if the template has content if (!template.content) return false; // Check if the template has sections if (!template.sections || template.sections.length === 0) return false; // Check if all required sections have titles and placeholders for (const section of template.sections) { if (!section.id || !section.title) return false; if (section.required && !section.placeholder) return false; } return true; } /** * Renders a template with provided data * * @param template - Template to render * @param data - Data to use for rendering * @returns Rendered content */ export function renderTemplate(template, data) { let renderedContent = template.content; // Replace placeholders in the content for (const [key, value] of Object.entries(data)) { const placeholder = `{${key}}`; renderedContent = renderedContent.replace(new RegExp(placeholder, 'g'), String(value)); } // Handle conditional sections for (const section of template.sections) { if (section.condition) { const conditionMet = evaluateCondition(section.condition, data); if (!conditionMet) { // Remove the section if the condition is not met const sectionRegex = new RegExp(`## ${section.title}[\\s\\S]*?(?=## |$)`, 'g'); renderedContent = renderedContent.replace(sectionRegex, ''); } } } return renderedContent; } /** * Creates specification sections from a template * * @param template - Template to use * @param data - Data to use for rendering * @returns Array of specification sections */ export function createSectionsFromTemplate(template, data) { const sections = []; // Process each template section for (const templateSection of template.sections) { // Skip sections with unmet conditions if (templateSection.condition && !evaluateCondition(templateSection.condition, data)) { continue; } // Create a new specification section const section = { id: templateSection.id, title: templateSection.title, content: templateSection.placeholder || '', order: templateSection.order, }; // Replace placeholders in the content for (const [key, value] of Object.entries(data)) { const placeholder = `{${key}}`; section.content = section.content.replace(new RegExp(placeholder, 'g'), String(value)); } sections.push(section); } return sections; } /** * Parses template content to extract sections * * @param content - Template content * @returns Array of template sections */ function parseTemplateContent(content) { const sections = []; const lines = content.split('\n'); let currentSection = null; let currentContent = ''; let sectionIndex = 0; for (const line of lines) { // Check for section headers if (line.startsWith('## ')) { // Save the previous section if there is one if (currentSection && currentSection.title) { currentSection.placeholder = currentContent.trim(); currentSection.order = sectionIndex++; sections.push(currentSection); } // Start a new section const title = line.substring(3).trim(); currentSection = { id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'), title, placeholder: '', required: !title.includes('(Optional)'), }; currentContent = ''; // Check for conditional sections const conditionMatch = title.match(/\{if:\s*([^}]+)\}/); if (conditionMatch) { currentSection.condition = conditionMatch[1].trim(); currentSection.title = title.replace(/\{if:\s*([^}]+)\}/, '').trim(); } } else if (currentSection) { // Add the line to the current section content currentContent += line + '\n'; } } // Save the last section if (currentSection && currentSection.title) { currentSection.placeholder = currentContent.trim(); currentSection.order = sectionIndex++; sections.push(currentSection); } return sections; } /** * Evaluates a condition expression * * @param condition - Condition expression * @param data - Data to use for evaluation * @returns True if the condition is met, false otherwise */ function evaluateCondition(condition, data) { try { // Simple condition evaluation - this would be enhanced in a real implementation // Format: "key == value" or "key != value" or "key" if (condition.includes('==')) { const [key, value] = condition.split('==').map(part => part.trim()); return data[key] === value; } else if (condition.includes('!=')) { const [key, value] = condition.split('!=').map(part => part.trim()); return data[key] !== value; } else { // Check if the key exists and has a truthy value return Boolean(data[condition.trim()]); } } catch (error) { console.error(`Error evaluating condition "${condition}": ${error}`); return false; } } //# sourceMappingURL=template-engine.js.map