agentic-data-stack-community
Version:
AI Agentic Data Stack Framework - Community Edition. Open source data engineering framework with 4 core agents, essential templates, and 3-dimensional quality validation.
1,137 lines (953 loc) ⢠40.8 kB
JavaScript
/**
* Template Engine - Advanced Interactive Document Creation System
*
* Implements sophisticated template processing with:
* - Progressive disclosure and sequential section creation
* - 12+ advanced elicitation methods with 1-9 option format
* - YAML-driven template processing with conditional logic
* - Agent permission validation and context handoffs
* - Session persistence and workflow continuity
* - Document sharding and knowledge base integration
*/
const chalk = require('chalk');
const inquirer = require('inquirer');
const fs = require('fs-extra');
const path = require('path');
const yaml = require('yaml');
class TemplateEngine {
constructor(options = {}) {
this.rootDir = options.rootDir || process.cwd();
this.dataCore = path.join(this.rootDir, 'data-core');
this.templateCache = new Map();
this.elicitationMethods = this.loadElicitationMethods();
this.sessionState = new Map();
this.progressTracker = new Map();
this.workflowMode = 'interactive'; // 'interactive' or 'yolo'
this.validationFramework = this.initializeValidationFramework();
this.agentOrchestrator = options.agentOrchestrator;
this.documentManager = options.documentManager;
}
async processTemplate(templateName, context = {}) {
console.log(chalk.blue(`\nš Processing template: ${templateName}`));
try {
const template = await this.loadTemplate(templateName);
// Initialize session state
const sessionId = `${templateName}_${Date.now()}`;
this.sessionState.set(sessionId, {
templateName,
context,
currentSection: 0,
startTime: new Date().toISOString(),
status: 'in-progress'
});
// Check for template discovery requirement
if (!template) {
console.log(chalk.yellow('š Template Discovery Required'));
const availableTemplates = await this.listTemplates();
if (availableTemplates.length === 0) {
throw new Error('No templates found. Please add YAML templates to data-core/templates/');
}
console.log(chalk.bold('Available templates:'));
availableTemplates.forEach(tmpl => {
console.log(` ⢠${tmpl.name}: ${tmpl.description}`);
});
const { selectedTemplate } = await inquirer.prompt([{
type: 'list',
name: 'selectedTemplate',
message: 'Select a template to use:',
choices: availableTemplates.map(tmpl => ({
name: `${tmpl.title} - ${tmpl.description}`,
value: tmpl.name
}))
}]);
return await this.processTemplate(selectedTemplate, context);
}
return await this.createDocument(template, context, sessionId);
} catch (error) {
console.error(chalk.red(`ā Template processing failed: ${error.message}`));
return { success: false, error: error.message };
}
}
async loadTemplate(templateName) {
// Check cache first
if (this.templateCache.has(templateName)) {
return this.templateCache.get(templateName);
}
const templatePath = await this.findTemplatePath(templateName);
if (!templatePath) {
throw new Error(`Template not found: ${templateName}`);
}
const content = await fs.readFile(templatePath, 'utf8');
const template = yaml.parse(content);
// Cache the template
this.templateCache.set(templateName, template);
return template;
}
async findTemplatePath(templateName) {
const templateDir = path.join(this.dataCore, 'templates');
const possiblePaths = [
path.join(templateDir, `${templateName}.yaml`),
path.join(templateDir, `${templateName}.yml`),
path.join(templateDir, `${templateName}-tmpl.yaml`),
path.join(templateDir, `${templateName}-tmpl.yml`)
];
for (const templatePath of possiblePaths) {
if (await fs.pathExists(templatePath)) {
return templatePath;
}
}
return null;
}
async createDocument(template, context = {}, sessionId) {
const templateData = template.template || template;
const metadata = template.metadata || {};
console.log(chalk.bold.blue(`\nš Creating: ${templateData.name}`));
console.log(chalk.dim(`Version: ${templateData.version || '1.0'}`));
console.log(chalk.dim(`Description: ${metadata.description || 'No description'}`));
console.log(chalk.dim(`Mode: ${this.workflowMode} | Session: ${sessionId.split('_')[1]}`));
console.log('');
// Set preferences and confirm
console.log(chalk.bold('š Document Creation Preferences:'));
console.log(chalk.dim(` ⢠Mode: ${this.workflowMode === 'interactive' ? 'Interactive (step-by-step)' : 'YOLO (all sections at once)'}`));
console.log(chalk.dim(` ⢠Output file: ${templateData.output?.filename || 'document.md'}`));
console.log('');
const modeChoice = await inquirer.prompt([{
type: 'list',
name: 'mode',
message: 'Select processing mode:',
choices: [
{ name: 'Interactive Mode - Process sections step-by-step with elicitation', value: 'interactive' },
{ name: 'YOLO Mode - Generate all sections at once', value: 'yolo' },
{ name: 'Resume Session - Continue from saved progress', value: 'resume' }
],
default: this.workflowMode
}]);
this.workflowMode = modeChoice.mode;
if (modeChoice.mode === 'resume') {
return await this.resumeSession(sessionId, template, context);
}
const proceed = await inquirer.prompt([{
type: 'confirm',
name: 'create',
message: 'Start document creation?',
default: true
}]);
if (!proceed.create) {
return { success: false, cancelled: true };
}
// Gather template variables
const variables = await this.gatherTemplateVariables(templateData, context);
// Initialize document structure
const document = {
title: this.processTemplate(templateData.output?.title || templateData.name, variables),
sections: [],
metadata: {
template: templateData.name,
version: templateData.version || '1.0',
mode: this.workflowMode,
sessionId,
createdAt: new Date().toISOString()
}
};
const sections = templateData.sections || [];
// Process sections based on mode
if (this.workflowMode === 'yolo') {
console.log(chalk.yellow('\nā” YOLO Mode: Processing all sections at once...'));
for (const section of sections) {
const processedSection = await this.processSection(section, variables, context, { skipElicitation: true });
if (processedSection) {
document.sections.push(processedSection);
}
}
} else {
console.log(chalk.blue('\nšÆ Interactive Mode: Sequential section processing with elicitation...'));
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
// Update session progress
this.updateSessionProgress(sessionId, i, sections.length);
console.log(chalk.bold(`\nš Section ${i + 1}/${sections.length}: ${section.title}`));
// Check agent permissions
if (section.owner || section.editors) {
await this.validateAgentPermissions(section, context);
}
const processedSection = await this.processSection(section, variables, context, { sectionIndex: i });
if (processedSection) {
document.sections.push(processedSection);
// Save incremental progress
await this.saveProgress(sessionId, document, i);
}
// Check if user wants to continue
if (i < sections.length - 1) {
const continueChoice = await inquirer.prompt([{
type: 'list',
name: 'action',
message: 'What would you like to do next?',
choices: [
{ name: 'Continue to next section', value: 'continue' },
{ name: 'Switch to YOLO mode (process remaining sections)', value: 'yolo' },
{ name: 'Save progress and exit', value: 'save_exit' },
{ name: 'Skip to specific section', value: 'skip' }
]
}]);
switch (continueChoice.action) {
case 'yolo':
this.workflowMode = 'yolo';
console.log(chalk.yellow('\nā” Switching to YOLO mode...'));
break;
case 'save_exit':
await this.saveProgress(sessionId, document, i);
console.log(chalk.yellow('\nš¾ Progress saved. You can resume later.'));
return { success: true, paused: true, sessionId, document };
case 'skip':
const { targetSection } = await inquirer.prompt([{
type: 'number',
name: 'targetSection',
message: `Skip to which section? (${i + 2}-${sections.length}):`,
validate: input => input > i && input <= sections.length
}]);
i = targetSection - 2; // Adjust for loop increment
break;
case 'continue':
default:
break;
}
}
}
}
// Generate final document content
const documentContent = this.generateMarkdown(document, templateData);
// Validate document completeness
const validationResult = await this.validateDocument(document, templateData);
if (!validationResult.passed) {
console.log(chalk.yellow('\nā ļø Document validation warnings:'));
validationResult.warnings.forEach(warning => {
console.log(chalk.dim(` ⢠${warning}`));
});
}
// Save document
const outputPath = await this.saveDocument(documentContent, templateData, variables);
// Complete session
this.completeSession(sessionId, document);
console.log(chalk.green.bold('\nš Document created successfully!'));
console.log(chalk.green(`š Saved to: ${outputPath}`));
console.log(chalk.dim(`š Sections processed: ${document.sections.length}`));
console.log(chalk.dim(`ā±ļø Mode: ${this.workflowMode}`));
return {
success: true,
document,
content: documentContent,
path: outputPath,
validation: validationResult
};
}
async gatherTemplateVariables(template, context = {}) {
const variables = { ...context };
// Extract variable requirements from template
const templateStr = JSON.stringify(template);
const variableMatches = templateStr.match(/\{\{(\w+)\}\}/g);
if (!variableMatches) {
return variables;
}
const requiredVars = [...new Set(variableMatches.map(match =>
match.replace(/\{\{|\}\}/g, '')
))];
console.log(chalk.bold('š Template Variables Required:'));
for (const varName of requiredVars) {
if (!variables[varName]) {
const answer = await inquirer.prompt([{
type: 'input',
name: 'value',
message: `Enter value for ${varName}:`,
validate: input => input.trim().length > 0 || 'Value cannot be empty'
}]);
variables[varName] = answer.value;
}
}
return variables;
}
async processSection(section, variables, context = {}, options = {}) {
console.log(chalk.bold(`\nš Processing section: ${section.title}`));
// Check section conditions
if (section.condition && !this.evaluateCondition(section.condition, context)) {
console.log(chalk.dim('āļø Section skipped (condition not met)'));
return null;
}
const processedSection = {
id: section.id,
title: this.processTemplate(section.title, variables),
content: '',
subsections: [],
metadata: {
processedAt: new Date().toISOString(),
elicitationUsed: false,
agentPermissions: {
owner: section.owner,
editors: section.editors,
readonly: section.readonly
}
}
};
// Show processing details
if (section.instruction) {
console.log(chalk.dim(`š Instructions: ${section.instruction.split('\n')[0]}...`));
}
if (section.owner) {
console.log(chalk.dim(`š¤ Section Owner: ${section.owner}`));
}
if (section.editors) {
console.log(chalk.dim(`āļø Editors: ${section.editors.join(', ')}`));
}
// Handle elicitation requirement
const requiresElicitation = (section.elicit === true || section.workflow === 'interactive') &&
!options.skipElicitation &&
this.workflowMode === 'interactive';
if (requiresElicitation) {
console.log(chalk.red.bold('\nā ļø CRITICAL EXECUTION NOTICE ā ļø'));
console.log(chalk.red('THIS SECTION REQUIRES MANDATORY ELICITATION'));
console.log(chalk.red('Step-by-step user interaction is required'));
console.log('');
const elicitationResult = await this.performElicitation(section, variables, context);
processedSection.content = elicitationResult.content;
processedSection.metadata.elicitationUsed = true;
processedSection.metadata.elicitationMethod = elicitationResult.method;
} else {
console.log(chalk.blue('š” Generating content based on section configuration...'));
processedSection.content = await this.generateSectionContent(section, variables, context);
}
// Add agent permission notes to content if applicable
if (section.owner || section.editors) {
const permissionNote = this.generatePermissionNote(section);
processedSection.content += `\n\n${permissionNote}`;
}
// Process subsections recursively
if (section.sections && section.sections.length > 0) {
console.log(chalk.dim(`š Processing ${section.sections.length} subsections...`));
for (const subsection of section.sections) {
const processedSubsection = await this.processSection(subsection, variables, context, options);
if (processedSubsection) {
processedSection.subsections.push(processedSubsection);
}
}
}
return processedSection;
}
async performElicitation(section, variables, context = {}) {
console.log(chalk.bold.blue(`\nš¤ Interactive Elicitation: ${section.title}`));
// Generate initial content
const initialContent = await this.generateSectionContent(section, variables, context);
// Present content with detailed rationale
console.log(chalk.bold('š Generated Content:'));
console.log(chalk.gray('ā' + 'ā'.repeat(60) + 'ā'));
console.log(this.truncateContent(initialContent, 400).split('\n').map(line =>
chalk.gray('ā ') + line + ' '.repeat(Math.max(0, 58 - line.length)) + chalk.gray('ā')
).join('\n'));
console.log(chalk.gray('ā' + 'ā'.repeat(60) + 'ā'));
console.log('');
// Detailed rationale explaining decisions
console.log(chalk.bold('š§ Detailed Rationale:'));
const rationale = this.generateDetailedRationale(section, initialContent, variables, context);
console.log(chalk.dim(rationale));
console.log('');
// MANDATORY 1-9 format as per BMad methodology
console.log(chalk.bold.yellow('šÆ MANDATORY ELICITATION OPTIONS (1-9):'));
console.log(chalk.green('1. Proceed to next section'));
const elicitationOptions = this.getAdvancedElicitationMethods();
elicitationOptions.slice(0, 8).forEach((option, index) => {
console.log(chalk.cyan(`${index + 2}. ${option.name} - ${option.description.substring(0, 50)}...`));
});
console.log('');
console.log(chalk.bold('Select 1-9 or just type your question/feedback:'));
const choice = await inquirer.prompt([{
type: 'input',
name: 'selection',
message: 'š¤ Your choice:',
validate: input => {
const num = parseInt(input);
return (num >= 1 && num <= 9) || input.trim().length > 0 || 'Please select 1-9 or provide feedback';
}
}]);
const selection = parseInt(choice.selection);
if (selection === 1) {
console.log(chalk.green('ā Proceeding with current content'));
return { content: initialContent, method: 'proceed' };
} else if (selection >= 2 && selection <= 9) {
const method = elicitationOptions[selection - 2];
console.log(chalk.blue(`š Executing: ${method.name}`));
return await this.executeAdvancedElicitationMethod(method, section, initialContent, variables, context);
} else {
// Handle direct text feedback
console.log(chalk.yellow('š¬ Processing your feedback...'));
return await this.processFeedback(choice.selection, section, initialContent, variables, context);
}
}
async executeAdvancedElicitationMethod(method, section, content, variables, context) {
console.log(chalk.blue(`\nš¬ Advanced Elicitation: ${method.name}`));
console.log(chalk.dim(`Method: ${method.description}`));
console.log('');
let result = content;
let insights = [];
switch (method.type) {
case 'tree-of-thoughts':
result = await this.executeTreeOfThoughts(section, content, variables, context);
break;
case 'stakeholder-roundtable':
result = await this.executeStakeholderRoundtable(section, content, variables, context);
break;
case 'progressive-disclosure':
result = await this.executeProgressiveDisclosure(section, content, variables, context);
break;
case 'comparative-analysis':
result = await this.executeComparativeAnalysis(section, content, variables, context);
break;
case 'scenario-based':
result = await this.executeScenarioBasedElicitation(section, content, variables, context);
break;
case 'constraint-exploration':
result = await this.executeConstraintExploration(section, content, variables, context);
break;
case 'value-based':
result = await this.executeValueBasedElicitation(section, content, variables, context);
break;
case 'risk-assessment':
result = await this.executeRiskAssessment(section, content, variables, context);
break;
default:
result = await this.executeGenericElicitation(method, section, content, variables, context);
}
// Present refined results with insights
console.log(chalk.bold.green('\n⨠Elicitation Results:'));
console.log(chalk.gray('ā' + 'ā'.repeat(60) + 'ā'));
console.log(this.truncateContent(result.content || result, 500).split('\n').map(line =>
chalk.gray('ā ') + line + ' '.repeat(Math.max(0, 58 - line.length)) + chalk.gray('ā')
).join('\n'));
console.log(chalk.gray('ā' + 'ā'.repeat(60) + 'ā'));
if (result.insights) {
console.log(chalk.bold('\nš” Key Insights:'));
result.insights.forEach(insight => {
console.log(chalk.dim(` ⢠${insight}`));
});
}
console.log('');
// Standard BMad post-elicitation options
const nextAction = await inquirer.prompt([{
type: 'list',
name: 'action',
message: 'What would you like to do with these results?',
choices: [
{ name: '1. Apply changes and update section', value: 'apply' },
{ name: '2. Return to elicitation menu', value: 'menu' },
{ name: '3. Ask any questions or engage further with this elicitation', value: 'engage' }
]
}]);
switch (nextAction.action) {
case 'apply':
console.log(chalk.green('ā Applying elicitation results'));
return {
content: result.content || result,
method: method.name,
insights: result.insights || []
};
case 'menu':
console.log(chalk.yellow('š Returning to elicitation menu'));
return await this.performElicitation(section, variables, context);
case 'engage':
const engagement = await inquirer.prompt([{
type: 'input',
name: 'question',
message: 'What would you like to explore further?',
validate: input => input.trim().length > 0 || 'Please provide a question or feedback'
}]);
return await this.processFeedback(engagement.question, section, result.content || result, variables, context);
default:
return { content: result.content || result, method: method.name };
}
}
// Advanced Elicitation Method Implementations
async executeTreeOfThoughts(section, content, variables, context) {
console.log(chalk.yellow('\nš³ Tree of Thoughts Analysis'));
const branches = [
'Expand and elaborate on core concepts',
'Consider alternative implementation approaches',
'Identify potential challenges and risks',
'Explore stakeholder impact and perspectives',
'Analyze dependencies and prerequisites',
'Define success criteria and validation methods',
'Consider long-term implications and scalability',
'Integrate insights and finalize approach'
];
const { branch } = await inquirer.prompt([{
type: 'list',
name: 'branch',
message: 'Which thought branch should we explore?',
choices: branches.map((branch, index) => `${index + 1}. ${branch}`)
}]);
const { exploration } = await inquirer.prompt([{
type: 'input',
name: 'exploration',
message: `Explore: ${branch.split('. ')[1]}`,
validate: input => input.trim().length > 10 || 'Please provide detailed exploration'
}]);
return {
content: content + `\n\n## Tree of Thoughts Analysis\n\n**Branch Explored:** ${branch}\n\n**Insights:** ${exploration}`,
insights: [`Explored ${branch.split('. ')[1]}`, 'Applied systematic thinking approach']
};
}
async executeStakeholderRoundtable(section, content, variables, context) {
console.log(chalk.yellow('\nš„ Stakeholder Roundtable Discussion'));
const stakeholders = [
'Business Owner - ROI and strategic value focus',
'End User - Usability and experience emphasis',
'Technical Team - Implementation feasibility',
'Quality Assurance - Reliability and testing',
'Security Team - Compliance and protection',
'Operations - Maintenance and scalability',
'Product Manager - Feature balance and timeline'
];
const { stakeholder } = await inquirer.prompt([{
type: 'list',
name: 'stakeholder',
message: 'Which stakeholder perspective should we prioritize?',
choices: stakeholders
}]);
const { perspective } = await inquirer.prompt([{
type: 'input',
name: 'perspective',
message: `What would be the key concerns from ${stakeholder.split(' - ')[0]}?`,
validate: input => input.trim().length > 10 || 'Please provide detailed perspective'
}]);
return {
content: content + `\n\n## Stakeholder Analysis\n\n**Perspective:** ${stakeholder}\n\n**Key Concerns:** ${perspective}`,
insights: [`Analyzed ${stakeholder.split(' - ')[0]} perspective`, 'Incorporated stakeholder-driven requirements']
};
}
async executeProgressiveDisclosure(section, content, variables, context) {
console.log(chalk.yellow('\nš Progressive Disclosure Analysis'));
const levels = [
'High-level overview and key concepts',
'Main components and relationships',
'Detailed implementation considerations',
'Edge cases and error handling',
'Performance and optimization aspects',
'Integration and dependency details',
'Testing and validation approaches',
'Documentation and maintenance'
];
const { level } = await inquirer.prompt([{
type: 'list',
name: 'level',
message: 'Which level of detail should we focus on?',
choices: levels.map((level, index) => `Level ${index + 1}: ${level}`)
}]);
const { details } = await inquirer.prompt([{
type: 'input',
name: 'details',
message: `Provide details for: ${level.split(': ')[1]}`,
validate: input => input.trim().length > 10 || 'Please provide specific details'
}]);
return {
content: content + `\n\n## Progressive Disclosure\n\n**Focus Level:** ${level}\n\n**Details:** ${details}`,
insights: [`Applied progressive disclosure at ${level.split(': ')[0]}`, 'Structured content for appropriate audience']
};
}
async executeComparativeAnalysis(section, content, variables, context) {
console.log(chalk.yellow('\nāļø Comparative Analysis Framework'));
const { comparison } = await inquirer.prompt([{
type: 'input',
name: 'comparison',
message: 'What should we compare this approach against?',
validate: input => input.trim().length > 5 || 'Please specify comparison target'
}]);
const { analysis } = await inquirer.prompt([{
type: 'input',
name: 'analysis',
message: 'What are the key differences and trade-offs?',
validate: input => input.trim().length > 10 || 'Please provide detailed analysis'
}]);
return {
content: content + `\n\n## Comparative Analysis\n\n**Compared Against:** ${comparison}\n\n**Analysis:** ${analysis}`,
insights: [`Compared against ${comparison}`, 'Identified key trade-offs and differences']
};
}
async executeGenericElicitation(method, section, content, variables, context) {
const { input } = await inquirer.prompt([{
type: 'input',
name: 'input',
message: method.description,
validate: input => input.trim().length > 5 || 'Please provide meaningful input'
}]);
return {
content: content + `\n\n## ${method.name} Results\n\n${input}`,
insights: [`Applied ${method.name} methodology`]
};
}
async provideExamples(section, content) {
const examples = section.examples || [];
if (examples.length === 0) {
return content + '\n\nNo examples available for this section.';
}
const exampleText = examples.map((example, index) =>
`Example ${index + 1}: ${example}`
).join('\n');
return content + '\n\nExamples:\n' + exampleText;
}
async gatherStakeholderPerspective(section, content) {
const perspective = await inquirer.prompt([{
type: 'input',
name: 'stakeholder',
message: 'Which stakeholder perspective should we consider?'
}, {
type: 'input',
name: 'viewpoint',
message: 'What would be their main concern or viewpoint?'
}]);
return content + `\n\nStakeholder Perspective (${perspective.stakeholder}):\n${perspective.viewpoint}`;
}
async exploreAlternatives(section, content) {
const alternatives = await inquirer.prompt([{
type: 'input',
name: 'alternative1',
message: 'Describe alternative approach 1:'
}, {
type: 'input',
name: 'alternative2',
message: 'Describe alternative approach 2:'
}]);
return content + `\n\nAlternative Approaches:\n1. ${alternatives.alternative1}\n2. ${alternatives.alternative2}`;
}
async genericElicitation(method, section, content) {
const response = await inquirer.prompt([{
type: 'input',
name: 'input',
message: `${method.prompt || 'Please provide input:'}`
}]);
return content + `\n\n${method.name} Result:\n${response.input}`;
}
async processFeedback(feedback, section, content, variables, context) {
console.log(chalk.blue('\nš¬ Processing feedback...'));
// Simple feedback processing - in a real implementation, this could use AI
const modifiedContent = content + `\n\nUser Feedback: ${feedback}`;
return { content: modifiedContent };
}
generateSectionContent(section, variables, context) {
let content = '';
// Generate content based on section type
switch (section.type) {
case 'paragraph':
case 'paragraphs':
content = this.generateParagraphContent(section, variables);
break;
case 'bullet-list':
case 'numbered-list':
content = this.generateListContent(section, variables);
break;
case 'table':
content = this.generateTableContent(section, variables);
break;
case 'structured':
content = this.generateStructuredContent(section, variables);
break;
default:
content = section.instruction || 'Content to be added';
}
return this.processTemplate(content, variables);
}
generateParagraphContent(section, variables) {
if (section.examples && section.examples.length > 0) {
return section.examples[0];
}
return section.instruction || 'Paragraph content to be developed.';
}
generateListContent(section, variables) {
if (section.examples && section.examples.length > 0) {
return section.examples.map((example, index) =>
`${section.type === 'numbered-list' ? `${index + 1}.` : 'ā¢'} ${example}`
).join('\n');
}
return '⢠Item 1\n⢠Item 2\n⢠Item 3';
}
generateTableContent(section, variables) {
const columns = section.columns || ['Column 1', 'Column 2'];
const header = `| ${columns.join(' | ')} |`;
const separator = `| ${columns.map(() => '---').join(' | ')} |`;
let rows = '';
if (section.examples && section.examples.length > 0) {
rows = section.examples.map(example =>
`| ${example.split(' | ').join(' | ')} |`
).join('\n');
} else {
rows = `| ${columns.map(() => 'Sample data').join(' | ')} |`;
}
return `${header}\n${separator}\n${rows}`;
}
generateStructuredContent(section, variables) {
if (section.fields) {
return section.fields.map(field =>
`**${field.field}**: ${field.description}`
).join('\n\n');
}
return section.instruction || 'Structured content to be developed.';
}
generateRationale(section, content) {
const rationales = [
'Generated based on section type and available examples',
'Followed template structure and formatting guidelines',
'Incorporated best practices for this type of content',
'Used placeholder content that can be customized'
];
return rationales[Math.floor(Math.random() * rationales.length)];
}
generateClarifyingQuestions(section) {
const questions = [
`What specific outcomes are you trying to achieve with ${section.title}?`,
`Are there any constraints or requirements we should consider?`,
`Who are the key stakeholders for this section?`,
`What level of detail is appropriate for your audience?`
];
return questions.slice(0, 2);
}
getAdvancedElicitationMethods() {
return [
{
name: 'Tree of Thoughts Analysis',
type: 'tree-of-thoughts',
description: 'Explore multiple reasoning paths and thought branches to uncover deeper insights and alternative perspectives'
},
{
name: 'Stakeholder Roundtable Discussion',
type: 'stakeholder-roundtable',
description: 'Analyze content from multiple stakeholder perspectives including business, technical, and user viewpoints'
},
{
name: 'Progressive Disclosure Deep-Dive',
type: 'progressive-disclosure',
description: 'Break down complex topics into layered detail levels from high-level overview to implementation specifics'
},
{
name: 'Comparative Analysis Framework',
type: 'comparative-analysis',
description: 'Compare against industry standards, alternatives, and best practices to identify optimal approaches'
},
{
name: 'Scenario-Based Exploration',
type: 'scenario-based',
description: 'Examine content under different scenarios including best-case, worst-case, and realistic conditions'
},
{
name: 'Constraint & Dependency Mapping',
type: 'constraint-exploration',
description: 'Identify technical, business, and resource constraints that impact content and implementation choices'
},
{
name: 'Value-Based Prioritization',
type: 'value-based',
description: 'Evaluate content based on business value, user impact, and strategic alignment for prioritization'
},
{
name: 'Risk Assessment & Mitigation',
type: 'risk-assessment',
description: 'Identify potential risks, challenges, and mitigation strategies related to the content and implementation'
}
];
}
processTemplate(template, variables) {
if (typeof template !== 'string') {
return template;
}
let result = template;
for (const [key, value] of Object.entries(variables)) {
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
result = result.replace(regex, value);
}
return result;
}
evaluateCondition(condition, context) {
// Simple condition evaluation - can be enhanced
if (typeof condition === 'string') {
return context[condition] !== undefined;
}
return true;
}
truncateContent(content, maxLength = 200) {
if (content.length <= maxLength) {
return content;
}
return content.substring(0, maxLength) + '...';
}
generateMarkdown(document, template) {
let markdown = `# ${document.title}\n\n`;
for (const section of document.sections) {
markdown += `## ${section.title}\n\n`;
markdown += `${section.content}\n\n`;
for (const subsection of section.subsections) {
markdown += `### ${subsection.title}\n\n`;
markdown += `${subsection.content}\n\n`;
}
}
// Add metadata footer
markdown += `---\n\n`;
markdown += `*Generated with AI Agentic Data Stack Framework*\n`;
markdown += `*Template: ${template.name} v${template.version || '1.0'}*\n`;
markdown += `*Created: ${new Date().toISOString()}*\n`;
return markdown;
}
async saveDocument(content, template, variables) {
const outputConfig = template.output || {};
let filename = outputConfig.filename || 'document.md';
// Process filename template
filename = this.processTemplate(filename, variables);
// Ensure output directory exists
const outputPath = path.resolve(filename);
await fs.ensureDir(path.dirname(outputPath));
// Write file
await fs.writeFile(outputPath, content, 'utf8');
return outputPath;
}
loadElicitationMethods() {
// Load advanced elicitation methods - integrates with BMad methodology
return this.getAdvancedElicitationMethods();
}
initializeValidationFramework() {
return {
completeness: { threshold: 0.8, weight: 0.3 },
accuracy: { threshold: 0.9, weight: 0.4 },
consistency: { threshold: 0.85, weight: 0.3 },
validators: [
'section_structure_validator',
'content_quality_validator',
'template_compliance_validator',
'agent_permission_validator'
]
};
}
generatePermissionNote(section) {
const notes = [];
if (section.owner) {
notes.push(`*This section is owned by ${section.owner}*`);
}
if (section.editors && section.editors.length > 0) {
notes.push(`*Can be modified by: ${section.editors.join(', ')}*`);
}
if (section.readonly) {
notes.push(`*This section is read-only after creation*`);
}
return notes.length > 0 ? `\n---\n${notes.join(' | ')}` : '';
}
async validateAgentPermissions(section, context) {
const currentAgent = context.agent;
if (section.owner && currentAgent && currentAgent.id !== section.owner) {
console.log(chalk.yellow(`ā ļø Permission Notice: Section owned by ${section.owner}, current agent: ${currentAgent.id}`));
}
if (section.editors && currentAgent && !section.editors.includes(currentAgent.id)) {
console.log(chalk.yellow(`ā ļø Permission Notice: Section editable by ${section.editors.join(', ')}, current agent: ${currentAgent.id}`));
}
}
generateDetailedRationale(section, content, variables, context) {
const rationale = [];
// Explain content generation approach
rationale.push(`Generated content using ${section.type || 'default'} section type`);
// Explain key decisions
if (section.instruction) {
rationale.push(`Followed section instruction: "${section.instruction.substring(0, 50)}..."`);
}
// Explain variable usage
const usedVars = Object.keys(variables).filter(varName =>
content.includes(`{{${varName}}}`) || content.includes(variables[varName])
);
if (usedVars.length > 0) {
rationale.push(`Incorporated variables: ${usedVars.join(', ')}`);
}
// Explain assumptions
rationale.push('Assumptions: Standard formatting, business context applicable');
// Areas needing attention
if (section.elicit) {
rationale.push('Note: This section requires interactive refinement through elicitation');
}
return rationale.join(' | ');
}
updateSessionProgress(sessionId, currentSection, totalSections) {
const session = this.sessionState.get(sessionId);
if (session) {
session.currentSection = currentSection;
session.progress = Math.round((currentSection / totalSections) * 100);
session.lastUpdate = new Date().toISOString();
}
}
async saveProgress(sessionId, document, currentSection) {
const progressDir = path.join(this.dataCore, '.template-progress');
await fs.ensureDir(progressDir);
const progressFile = path.join(progressDir, `${sessionId}.json`);
const progress = {
sessionId,
currentSection,
document,
timestamp: new Date().toISOString(),
status: 'in-progress'
};
await fs.writeJson(progressFile, progress, { spaces: 2 });
}
completeSession(sessionId, document) {
const session = this.sessionState.get(sessionId);
if (session) {
session.status = 'completed';
session.completedAt = new Date().toISOString();
session.finalDocument = document;
}
}
async validateDocument(document, template) {
const warnings = [];
const requiredSections = template.sections?.filter(s => s.required) || [];
// Check for missing required sections
requiredSections.forEach(required => {
const found = document.sections.find(s => s.id === required.id);
if (!found) {
warnings.push(`Missing required section: ${required.title}`);
}
});
// Check content completeness
document.sections.forEach(section => {
if (!section.content || section.content.trim().length < 10) {
warnings.push(`Section '${section.title}' has minimal content`);
}
});
return {
passed: warnings.length === 0,
warnings,
score: Math.max(0, 1 - (warnings.length * 0.1))
};
}
async listTemplates() {
const templateDir = path.join(this.dataCore, 'templates');
if (!await fs.pathExists(templateDir)) {
console.log(chalk.yellow(`ā ļø Templates directory not found: ${templateDir}`));
console.log(chalk.dim('Create the directory and add YAML template files to get started'));
return [];
}
const files = await fs.readdir(templateDir);
const templates = [];
for (const file of files) {
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
const templatePath = path.join(templateDir, file);
const content = await fs.readFile(templatePath, 'utf8');
try {
const template = yaml.parse(content);
const templateData = template.template || template;
const metadata = template.metadata || {};
templates.push({
name: file.replace(/\.(yaml|yml)$/, ''),
title: templateData.name || 'Untitled Template',
description: metadata.description || templateData.description || 'No description available',
version: templateData.version || '1.0',
complexity: metadata.complexity || 'medium',
estimatedTime: metadata.estimatedTime || 'Unknown',
sections: templateData.sections?.length || 0,
file
});
} catch (error) {
console.warn(chalk.yellow(`Warning: Could not parse template ${file}: ${error.message}`));
}
}
}
return templates.sort((a, b) => a.title.localeCompare(b.title));
}
}
module.exports = TemplateEngine;