UNPKG

@noanswer/context-compose

Version:

Orchestrate complex AI interactions with Context Compose. A powerful CLI and server for building, validating, and managing context for large language models using the Model Context Protocol (MCP).

268 lines 9.74 kB
import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { parse as parseYaml } from 'yaml'; import { GetContextToolSchema } from '../../schemas/get-context.js'; /** * Get Context tool core logic */ /** * Context ID formatting (preserve hyphens and handle context- prefix) */ function formatContextId(contextId) { // If contextId already starts with 'context-', remove it to avoid duplication if (contextId.startsWith('context-')) { return contextId.substring(8); // Remove 'context-' prefix } return contextId; } /** * Read and parse YAML file */ function readYamlFile(filePath) { if (!existsSync(filePath)) { throw new Error(`File not found: ${filePath}`); } try { const content = readFileSync(filePath, 'utf8'); const parsed = parseYaml(content); return parsed; } catch (error) { throw new Error(`YAML file parsing failed (${filePath}): ${error instanceof Error ? error.message : String(error)}`); } } /** * Read context file */ function readContextFile(projectRoot, contextId) { const formattedId = formatContextId(contextId); // All contexts use .contextcompose directory const contextFilePath = join(projectRoot, '.contextcompose', `${formattedId}-context.yaml`); return readYamlFile(contextFilePath); } /** * Read workflow file */ function readWorkflowFile(projectRoot, workflowPath) { const fullPath = join(projectRoot, '.contextcompose', workflowPath); return readYamlFile(fullPath); } /** * Read rules files */ function readRulesFiles(projectRoot, rulesPaths) { return rulesPaths.map((rulePath) => { const fullPath = join(projectRoot, '.contextcompose', rulePath); return readYamlFile(fullPath); }); } /** * Read MCPs files */ function readMcpsFiles(projectRoot, mcpsPaths) { return mcpsPaths.map((mcpPath) => { const fullPath = join(projectRoot, '.contextcompose', mcpPath); return readYamlFile(fullPath); }); } /** * Read component files (generic) */ function readComponentFiles(projectRoot, filePaths) { return filePaths.map((filePath) => { const fullPath = join(projectRoot, '.contextcompose', filePath); return readYamlFile(fullPath); }); } /** * Read single component file (generic) */ function readComponentFile(projectRoot, filePath) { const fullPath = join(projectRoot, '.contextcompose', filePath); return readYamlFile(fullPath); } /** * Process Jobs section - dynamically handle all sections */ function processJobsSection(projectRoot, context) { const processedSections = {}; // All contexts use .contextcompose directory const configPath = '.contextcompose'; for (const [sectionName, sectionValue] of Object.entries(context)) { if (!sectionValue) continue; try { if (typeof sectionValue === 'string') { // Single file (e.g., workflow) const fullPath = join(projectRoot, configPath, sectionValue); const component = readYamlFile(fullPath); processedSections[sectionName] = [component]; } else if (Array.isArray(sectionValue)) { // File array (e.g., rules, mcps, notify, issue, etc.) const components = sectionValue.map((filePath) => { const fullPath = join(projectRoot, configPath, filePath); return readYamlFile(fullPath); }); processedSections[sectionName] = components; } } catch (error) { console.warn(`⚠️ Failed to read ${sectionName} files: ${error instanceof Error ? error.message : String(error)}`); processedSections[sectionName] = []; } } return processedSections; } /** * Combine all prompts (with dynamic section support) */ function combinePrompts(contextYaml, processedSections, useEnhancedPrompt = false) { const sections = []; // Task basic information sections.push(`# Task: ${contextYaml.name}`); sections.push(`**Description:** ${contextYaml.description}`); // Define the desired order for sections const sectionOrder = ['workflow', 'rules', 'mcps', 'notify', 'context']; // Process sections in the specified order FIRST for (const sectionName of sectionOrder) { const components = processedSections[sectionName]; if (!components || components.length === 0) continue; // Generate section title const sectionTitle = getSectionTitle(sectionName); sections.push(`\n## ${sectionTitle}`); // Single component case (e.g., workflow) if (components.length === 1) { const component = components[0]; if (component) { sections.push(`**Name:** ${component.name}`); sections.push(`**Description:** ${component.description}`); const promptToUse = useEnhancedPrompt && component['prompt-enhanced'] ? component['prompt-enhanced'] : component.prompt; sections.push(`\n${promptToUse}`); } } else { // Multiple components case (e.g., rules, mcps, notify, issue, etc.) components.forEach((component, index) => { if (component) { sections.push(`\n### ${index + 1}. ${component.name}`); sections.push(`**Description:** ${component.description}`); const promptToUse = useEnhancedPrompt && component['prompt-enhanced'] ? component['prompt-enhanced'] : component.prompt; sections.push(`\n${promptToUse}`); } }); } } // Process any remaining sections that are not in the predefined order for (const [sectionName, components] of Object.entries(processedSections)) { if (sectionOrder.includes(sectionName) || components.length === 0) continue; // Generate section title const sectionTitle = getSectionTitle(sectionName); sections.push(`\n## ${sectionTitle}`); // Single component case if (components.length === 1) { const component = components[0]; if (component) { sections.push(`**Name:** ${component.name}`); sections.push(`**Description:** ${component.description}`); const promptToUse = useEnhancedPrompt && component['prompt-enhanced'] ? component['prompt-enhanced'] : component.prompt; sections.push(`\n${promptToUse}`); } } else { // Multiple components case components.forEach((component, index) => { if (component) { sections.push(`\n### ${index + 1}. ${component.name}`); sections.push(`**Description:** ${component.description}`); const promptToUse = useEnhancedPrompt && component['prompt-enhanced'] ? component['prompt-enhanced'] : component.prompt; sections.push(`\n${promptToUse}`); } }); } } // Add context prompt LAST (after all component prompts) if (contextYaml.prompt) { sections.push(`\n## Task Prompt\n${contextYaml.prompt}`); } return sections.join('\n'); } /** * Convert section name to user-friendly title */ function getSectionTitle(sectionName) { const titleMap = { workflow: 'Workflow', rules: 'Rules', mcps: 'MCPs (Model Context Protocols)', notify: 'Notifications', issue: 'Issue Management', }; return (titleMap[sectionName] || sectionName.charAt(0).toUpperCase() + sectionName.slice(1)); } /** * Get Context core logic */ export async function executeGetContext(input) { const { contextId, projectRoot, enhancedPrompt = false } = input; try { // 1. Read context file const contextYaml = readContextFile(projectRoot, contextId); // 2. Validate context structure if (!contextYaml || !contextYaml.context) { throw new Error('Invalid context structure: context property is missing or null'); } // 3. Process all Jobs sections dynamically const processedSections = processJobsSection(projectRoot, contextYaml.context); // 4. Combine all prompts const combinedPrompt = combinePrompts(contextYaml, processedSections, enhancedPrompt); return { success: true, message: `✅ Context '${contextId}' is ready.`, contextId, combinedPrompt, files: contextYaml.context, }; } catch (error) { return { success: false, message: `❌ Failed to get context: ${error instanceof Error ? error.message : String(error)}`, contextId, }; } } /** * Get Context tool execution function */ export async function executeGetContextTool(args) { const input = GetContextToolSchema.parse(args); return executeGetContext(input); } // 기존 클래스와의 호환성을 위한 객체 export export const GetContextTool = { formatContextId, readYamlFile, readContextFile, readWorkflowFile, readRulesFiles, readMcpsFiles, readComponentFiles, readComponentFile, processJobsSection, combinePrompts, getSectionTitle, execute: executeGetContext, }; //# sourceMappingURL=get-context.js.map