#!/usr/bin/env node const path = require('path'); const fs = require('fs'); // Import context manager (copy functionality since we can't use external dependencies) class ContextManager { constructor(workspacePath = null) { this.workspacePath = workspacePath || path.join(process.cwd(), '.workspace'); this.contextPath = path.join(this.workspacePath, 'context'); this.decisionsPath = path.join(this.workspacePath, 'decisions'); this.progressPath = path.join(this.workspacePath, 'progress'); this.qualityPath = path.join(this.workspacePath, 'quality'); this.maxContextSize = 10 * 1024 * 1024; this.initialize(); } initialize() { const dirs = [this.contextPath, this.decisionsPath, this.progressPath, this.qualityPath]; for (const dir of dirs) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } } async loadSharedContext() { try { const contextFile = path.join(this.contextPath, 'shared-context.md'); if (!fs.existsSync(contextFile)) { return this.getDefaultSharedContext(); } const content = fs.readFileSync(contextFile, 'utf8'); return this.parseSharedContext(content); } catch (error) { console.error('Failed to load shared context:', error.message); return this.getDefaultSharedContext(); } } getDefaultSharedContext() { return { lastUpdated: new Date().toISOString(), activeSessions: [], primaryAgent: 'unknown', currentFocus: 'No active development focus', keyDecisions: [], nextSteps: [], sessionNotes: '' }; } parseSharedContext(content) { const context = this.getDefaultSharedContext(); try { const lastUpdatedMatch = content.match(/\*\*Last Updated:\*\* (.+)/); if (lastUpdatedMatch) context.lastUpdated = lastUpdatedMatch[1]; const activeSessionsMatch = content.match(/\*\*Active Sessions:\*\* (.+)/); if (activeSessionsMatch && activeSessionsMatch[1] !== 'None') { context.activeSessions = activeSessionsMatch[1].split(', ').map(s => s.trim()); } const primaryAgentMatch = content.match(/\*\*Primary Agent:\*\* (.+)/); if (primaryAgentMatch) context.primaryAgent = primaryAgentMatch[1]; const currentFocusMatch = content.match(/## Current Focus\n([\s\S]*?)(?=\n## |$)/); if (currentFocusMatch) context.currentFocus = currentFocusMatch[1].trim(); const keyDecisionsMatch = content.match(/## Key Decisions\n([\s\S]*?)(?=\n## |$)/); if (keyDecisionsMatch) { context.keyDecisions = keyDecisionsMatch[1] .split('\n') .filter(line => line.startsWith('- ')) .map(line => line.substring(2).trim()) .filter(decision => decision && !decision.includes('No decisions recorded')); } const nextStepsMatch = content.match(/## Next Steps\n([\s\S]*?)(?=\n## |$)/); if (nextStepsMatch) { context.nextSteps = nextStepsMatch[1] .split('\n') .filter(line => line.startsWith('- ')) .map(line => line.substring(2).trim()) .filter(step => step && !step.includes('No next steps defined')); } const sessionNotesMatch = content.match(/## Session Notes\n([\s\S]*?)$/); if (sessionNotesMatch) context.sessionNotes = sessionNotesMatch[1].trim(); } catch (error) { console.warn('Failed to parse shared context, using defaults:', error.message); } return context; } async loadProgress() { try { const progressFile = path.join(this.progressPath, 'progress-summary.md'); if (!fs.existsSync(progressFile)) { return { lastUpdated: new Date().toISOString(), currentStory: 'No active story', completedTasks: [], pendingTasks: [], blockers: [], qualityScore: 'Not assessed' }; } const content = fs.readFileSync(progressFile, 'utf8'); const progress = { lastUpdated: new Date().toISOString(), currentStory: 'No active story', completedTasks: [], pendingTasks: [], blockers: [], qualityScore: 'Not assessed' }; const currentStoryMatch = content.match(/\*\*Current Story:\*\* (.+)/); if (currentStoryMatch) progress.currentStory = currentStoryMatch[1]; const qualityScoreMatch = content.match(/\*\*Quality Score:\*\* (.+)/); if (qualityScoreMatch) progress.qualityScore = qualityScoreMatch[1]; return progress; } catch (error) { console.error('Failed to load progress:', error.message); return { lastUpdated: new Date().toISOString(), currentStory: 'No active story', completedTasks: [], pendingTasks: [], blockers: [], qualityScore: 'Not assessed' }; } } async getLatestQualityMetrics() { try { const qualityFile = path.join(this.qualityPath, 'quality-metrics.md'); if (!fs.existsSync(qualityFile)) { return null; } const content = fs.readFileSync(qualityFile, 'utf8'); const assessments = content.split('## Quality Assessment -'); if (assessments.length < 2) { return null; } return { timestamp: assessments[1].split('\n')[0].trim(), available: true }; } catch (error) { console.error('Failed to get latest quality metrics:', error.message); return null; } } } // Context Management CLI async function handleContextCommand() { try { const workspacePath = path.join(process.cwd(), '.workspace'); if (!fs.existsSync(workspacePath)) { console.error('āŒ Workspace directory not found. Run `npx bmad-method install` first.'); process.exit(1); } const contextManager = new ContextManager(); const args = process.argv.slice(2); const command = args[0]; switch (command) { case 'status': await showContextStatus(contextManager); break; case 'load': await loadContext(contextManager); break; case 'update': await updateContext(contextManager, args.slice(1)); break; case 'decisions': await showDecisions(contextManager); break; case 'progress': await showProgress(contextManager); break; case 'export': await exportContext(contextManager); break; default: showUsage(); } } catch (error) { console.error('āŒ Context command failed:', error.message); process.exit(1); } } async function showContextStatus(contextManager) { console.log('šŸ“„ BMAD Context Status'); console.log('======================'); const context = await contextManager.loadSharedContext(); const progress = await contextManager.loadProgress(); const quality = await contextManager.getLatestQualityMetrics(); console.log(`šŸ“ Context: ${contextManager.contextPath}`); console.log(`šŸ• Last Updated: ${context.lastUpdated}`); console.log(`šŸ‘¤ Primary Agent: ${context.primaryAgent}`); console.log(`šŸŽÆ Current Focus: ${context.currentFocus}`); console.log(`šŸ“Š Quality Score: ${progress.qualityScore}`); if (context.activeSessions.length > 0) { console.log(`\nšŸ‘„ Active Sessions: ${context.activeSessions.length}`); context.activeSessions.forEach((session, index) => { console.log(` ${index + 1}. ${session}`); }); } if (context.keyDecisions.length > 0) { console.log(`\nšŸ”‘ Recent Key Decisions:`); context.keyDecisions.slice(-3).forEach((decision, index) => { console.log(` ${index + 1}. ${decision}`); }); } if (context.nextSteps.length > 0) { console.log(`\nā­ļø Next Steps:`); context.nextSteps.forEach((step, index) => { console.log(` ${index + 1}. ${step}`); }); } console.log(`\nšŸ“ˆ Quality Metrics: ${quality ? 'Available' : 'Not available'}`); } async function loadContext(contextManager) { console.log('šŸ“„ Loading workspace context...\n'); const context = await contextManager.loadSharedContext(); console.log('šŸŽÆ Current Focus:'); console.log(context.currentFocus); if (context.keyDecisions.length > 0) { console.log('\nšŸ”‘ Key Decisions:'); context.keyDecisions.forEach((decision, index) => { console.log(` ${index + 1}. ${decision}`); }); } if (context.nextSteps.length > 0) { console.log('\nā­ļø Next Steps:'); context.nextSteps.forEach((step, index) => { console.log(` ${index + 1}. ${step}`); }); } if (context.sessionNotes) { console.log('\nšŸ“ Session Notes:'); console.log(context.sessionNotes); } } async function showDecisions(contextManager) { const decisionsFile = path.join(contextManager.decisionsPath, 'decisions-log.md'); if (!fs.existsSync(decisionsFile)) { console.log('šŸ“‹ No decisions recorded yet.'); return; } const content = fs.readFileSync(decisionsFile, 'utf8'); const decisions = content.split('## Decision ').slice(1); console.log('šŸ“‹ Architectural & Design Decisions'); console.log('==================================='); decisions.slice(-5).forEach((decision, index) => { const lines = decision.split('\n'); const title = lines[0].replace(/^\d+:\s*/, ''); const dateMatch = decision.match(/\*\*Date:\*\* (.+)/); const agentMatch = decision.match(/\*\*Agent:\*\* (.+)/); console.log(`\n${decisions.length - 4 + index}. ${title}`); if (dateMatch) console.log(` šŸ“… ${dateMatch[1]}`); if (agentMatch) console.log(` šŸ‘¤ ${agentMatch[1]}`); }); } async function showProgress(contextManager) { const progress = await contextManager.loadProgress(); console.log('šŸ“ˆ Development Progress'); console.log('======================'); console.log(`šŸŽÆ Current Story: ${progress.currentStory}`); console.log(`šŸ“Š Quality Score: ${progress.qualityScore}`); console.log(`šŸ• Last Updated: ${progress.lastUpdated}`); if (progress.completedTasks && progress.completedTasks.length > 0) { console.log(`\nāœ… Completed Tasks: ${progress.completedTasks.length}`); } if (progress.pendingTasks && progress.pendingTasks.length > 0) { console.log(`ā³ Pending Tasks: ${progress.pendingTasks.length}`); } if (progress.blockers && progress.blockers.length > 0) { console.log(`🚫 Blockers: ${progress.blockers.length}`); } } async function exportContext(contextManager) { try { const context = await contextManager.loadSharedContext(); const progress = await contextManager.loadProgress(); const exportContent = `# Workspace Context Export **Generated:** ${new Date().toISOString()} ## Current Status - **Primary Agent:** ${context.primaryAgent} - **Active Sessions:** ${context.activeSessions.join(', ') || 'None'} - **Current Focus:** ${context.currentFocus} - **Quality Score:** ${progress.qualityScore} ## Key Decisions ${context.keyDecisions.map(d => `- ${d}`).join('\n') || '- No decisions recorded'} ## Next Steps ${context.nextSteps.map(step => `- ${step}`).join('\n') || '- No next steps defined'} ## Session Notes ${context.sessionNotes || 'No session notes available'} `; const exportFile = path.join(process.cwd(), `context-export-${Date.now()}.md`); fs.writeFileSync(exportFile, exportContent); console.log('āœ… Context exported successfully'); console.log(`šŸ“ Export file: ${exportFile}`); } catch (error) { console.error('āŒ Failed to export context:', error.message); } } function showUsage() { console.log('šŸ“„ BMAD Context Management'); console.log('=========================='); console.log(''); console.log('Usage: node context.js '); console.log(''); console.log('Commands:'); console.log(' status - Show current workspace context status'); console.log(' load - Load and display shared context'); console.log(' decisions - Show recent architectural decisions'); console.log(' progress - Show development progress summary'); console.log(' export - Export context to markdown file'); console.log(''); console.log('Examples:'); console.log(' node context.js status'); console.log(' node context.js load'); console.log(' node context.js export'); } if (require.main === module) { handleContextCommand(); } module.exports = { ContextManager };