Skip to main content
Glama
projectContext.ts12.8 kB
/** * Project Context Management * Handles reading and updating project documentation files */ import * as fs from 'fs'; import * as path from 'path'; import chalk from 'chalk'; export interface ProjectContextFiles { sketch?: string; logicFlow?: string; roadmap?: string; instructor?: string; history?: string; } export interface ProjectHistoryEntry { timestamp: string; task: string; summary: string; budgetUsed: number; budgetRemaining: number; model?: string; layer?: string; } /** * Standard project file names to look for */ const PROJECT_FILES = { sketch: ['SKETCH.md', 'sketch.md', 'project-sketch.md'], logicFlow: ['LOGIC_FLOW.md', 'logic-flow.md', 'logic_flow.md'], roadmap: ['ROADMAP.md', 'roadmap.md', 'project-roadmap.md'], instructor: ['mcp-instruction.md', 'mcp-instructor.md', 'INSTRUCTOR.md', 'instructor.md', 'INSTRUCTION.md', 'instruction.md'], history: ['project-coding-history.md', 'HISTORY.md', 'history.md'] }; /** * Read all available project context files */ export function readProjectContext(workingDir: string = process.cwd()): ProjectContextFiles { const context: ProjectContextFiles = {}; // Try to find each type of file for (const [key, possibleNames] of Object.entries(PROJECT_FILES)) { for (const fileName of possibleNames) { const filePath = path.join(workingDir, fileName); if (fs.existsSync(filePath)) { try { const content = fs.readFileSync(filePath, 'utf-8'); context[key as keyof ProjectContextFiles] = content; break; // Use first found file of this type } catch (error) { console.log(chalk.yellow(`⚠️ Could not read ${fileName}: ${error}`)); } } } } return context; } /** * Build enhanced prompt with project context */ /** * Check if essential project files are missing */ export function hasMinimalProjectContext(context: ProjectContextFiles): boolean { return !!(context.sketch || context.roadmap || context.instructor); } /** * Create missing project documentation files based on AI analysis */ export async function createMissingProjectFiles( workingDir: string, summaryContent: string, verbose: boolean = false ): Promise<void> { if (verbose) { console.log(chalk.blue('📝 Creating missing project documentation files...')); } // Extract sections from summary to create individual files const sections = extractSummarySection(summaryContent); // Create SKETCH.md if it doesn't exist const sketchPath = path.join(workingDir, 'SKETCH.md'); if (!fs.existsSync(sketchPath) && sections.overview) { const sketchContent = `# Project Sketch ${sections.overview}\n\n## Architecture\n\n${sections.architecture || 'To be defined'}\n\n## Key Features\n\n${sections.features || 'To be defined'}`; fs.writeFileSync(sketchPath, sketchContent); console.log(chalk.green('✅ Created SKETCH.md')); } // Create ROADMAP.md if it doesn't exist const roadmapPath = path.join(workingDir, 'ROADMAP.md'); if (!fs.existsSync(roadmapPath) && sections.roadmap) { const roadmapContent = `# Project Roadmap\n\n${sections.roadmap}\n\n## Next Steps\n\n${sections.nextSteps || 'To be defined'}`; fs.writeFileSync(roadmapPath, roadmapContent); console.log(chalk.green('✅ Created ROADMAP.md')); } // Create LOGIC_FLOW.md if architecture information is available const logicFlowPath = path.join(workingDir, 'LOGIC_FLOW.md'); if (!fs.existsSync(logicFlowPath) && sections.architecture) { const logicContent = `# Logic Flow\n\n## System Architecture\n\n${sections.architecture}\n\n## Data Flow\n\n${sections.dataFlow || 'To be documented'}`; fs.writeFileSync(logicFlowPath, logicContent); console.log(chalk.green('✅ Created LOGIC_FLOW.md')); } // Create mcp-instructor.md with basic project instructions const instructorPath = path.join(workingDir, 'mcp-instructor.md'); if (!fs.existsSync(instructorPath)) { const instructorContent = `# Project Instructions\n\n## Overview\n\n${sections.overview || 'Project overview to be defined'}\n\n## Development Guidelines\n\n- Follow established patterns and conventions\n- Maintain code quality and documentation\n- Test thoroughly before committing changes\n\n## Architecture Notes\n\n${sections.architecture || 'Architecture guidelines to be defined'}`; fs.writeFileSync(instructorPath, instructorContent); console.log(chalk.green('✅ Created mcp-instructor.md')); } } /** * Extract sections from AI-generated summary */ function extractSummarySection(content: string): { overview?: string; architecture?: string; features?: string; roadmap?: string; nextSteps?: string; dataFlow?: string; } { const sections: any = {}; // Simple extraction based on common markdown headers const overviewMatch = content.match(/##?\s*(?:Project\s+)?Overview[\s\S]*?(?=\n##|$)/i); if (overviewMatch) { sections.overview = overviewMatch[0].replace(/##?\s*(?:Project\s+)?Overview\s*/i, '').trim(); } const architectureMatch = content.match(/##?\s*(?:Architecture|Technology\s+Stack)[\s\S]*?(?=\n##|$)/i); if (architectureMatch) { sections.architecture = architectureMatch[0].replace(/##?\s*(?:Architecture|Technology\s+Stack)\s*/i, '').trim(); } const featuresMatch = content.match(/##?\s*(?:Key\s+)?Features[\s\S]*?(?=\n##|$)/i); if (featuresMatch) { sections.features = featuresMatch[0].replace(/##?\s*(?:Key\s+)?Features\s*/i, '').trim(); } const roadmapMatch = content.match(/##?\s*(?:Roadmap|Future\s+Development)[\s\S]*?(?=\n##|$)/i); if (roadmapMatch) { sections.roadmap = roadmapMatch[0].replace(/##?\s*(?:Roadmap|Future\s+Development)\s*/i, '').trim(); } const nextStepsMatch = content.match(/##?\s*Next\s+Steps[\s\S]*?(?=\n##|$)/i); if (nextStepsMatch) { sections.nextSteps = nextStepsMatch[0].replace(/##?\s*Next\s+Steps\s*/i, '').trim(); } return sections; } export function buildContextualPrompt( originalPrompt: string, context: ProjectContextFiles, taskType: 'code' | 'analysis' | 'planning' = 'code' ): string { let prompt = originalPrompt; // Add project context if available const contextParts: string[] = []; if (context.instructor) { contextParts.push(`## Project Instructions\n${context.instructor}`); } if (context.sketch) { contextParts.push(`## Project Sketch\n${context.sketch}`); } if (context.logicFlow) { contextParts.push(`## Logic Flow\n${context.logicFlow}`); } if (context.roadmap) { contextParts.push(`## Roadmap\n${context.roadmap}`); } if (context.history) { const recentHistory = extractRecentHistory(context.history); if (recentHistory) { contextParts.push(`## Recent Work History\n${recentHistory}`); } } if (contextParts.length > 0) { const contextSection = contextParts.join('\n\n'); prompt = `# Project Context\n\n${contextSection}\n\n# Current Task\n\n${originalPrompt}`; } return prompt; } /** * Extract recent history entries (last 5) */ function extractRecentHistory(historyContent: string): string | null { try { const lines = historyContent.split('\n'); const entries: string[] = []; let currentEntry: string[] = []; let entryCount = 0; for (const line of lines) { if (line.startsWith('## ') && entryCount < 5) { if (currentEntry.length > 0) { entries.push(currentEntry.join('\n')); entryCount++; } currentEntry = [line]; } else if (currentEntry.length > 0) { currentEntry.push(line); } } // Add last entry if exists if (currentEntry.length > 0 && entryCount < 5) { entries.push(currentEntry.join('\n')); } return entries.length > 0 ? entries.join('\n\n') : null; } catch (error) { return null; } } /** * Append entry to project coding history */ export function appendToHistory( workingDir: string, entry: ProjectHistoryEntry ): boolean { try { const historyFile = path.join(workingDir, 'project-coding-history.md'); const timestamp = new Date().toISOString(); const entryText = `## ${entry.timestamp} - ${entry.task} **Summary:** ${entry.summary} **Budget:** $${entry.budgetUsed.toFixed(4)} used, $${entry.budgetRemaining.toFixed(4)} remaining ${entry.model ? `**Model:** ${entry.model}` : ''} ${entry.layer ? `**Layer:** ${entry.layer}` : ''} --- `; // Create file if it doesn't exist if (!fs.existsSync(historyFile)) { const header = `# Project Coding History This file tracks all coding tasks, summaries, and budget usage for this project. --- `; fs.writeFileSync(historyFile, header + entryText); } else { // Prepend to existing file (newest first) const existing = fs.readFileSync(historyFile, 'utf-8'); const headerEnd = existing.indexOf('\n---\n'); if (headerEnd !== -1) { const header = existing.substring(0, headerEnd + 5); const rest = existing.substring(headerEnd + 5); fs.writeFileSync(historyFile, header + entryText + rest); } else { fs.appendFileSync(historyFile, entryText); } } return true; } catch (error) { console.log(chalk.yellow(`⚠️ Could not update history: ${error}`)); return false; } } /** * Update project documentation files after task completion */ export function updateProjectFiles( workingDir: string, updates: { sketch?: string; logicFlow?: string; roadmap?: string; } ): boolean { try { let updated = false; if (updates.sketch) { const sketchFile = findProjectFile(workingDir, PROJECT_FILES.sketch); if (sketchFile) { fs.writeFileSync(sketchFile, updates.sketch); console.log(chalk.green(`✓ Updated ${path.basename(sketchFile)}`)); updated = true; } } if (updates.logicFlow) { const flowFile = findProjectFile(workingDir, PROJECT_FILES.logicFlow); if (flowFile) { fs.writeFileSync(flowFile, updates.logicFlow); console.log(chalk.green(`✓ Updated ${path.basename(flowFile)}`)); updated = true; } } if (updates.roadmap) { const roadmapFile = findProjectFile(workingDir, PROJECT_FILES.roadmap); if (roadmapFile) { fs.writeFileSync(roadmapFile, updates.roadmap); console.log(chalk.green(`✓ Updated ${path.basename(roadmapFile)}`)); updated = true; } } return updated; } catch (error) { console.log(chalk.yellow(`⚠️ Could not update project files: ${error}`)); return false; } } /** * Find the first existing file from a list of possible names */ function findProjectFile(workingDir: string, possibleNames: string[]): string | null { for (const fileName of possibleNames) { const filePath = path.join(workingDir, fileName); if (fs.existsSync(filePath)) { return filePath; } } return null; } /** * Create simple task summary for history tracking */ export function createSimpleTaskSummary( type: 'create' | 'review' | 'analyze', fileName: string, prompt: string ): string { const action = type === 'create' ? 'Generated' : type === 'review' ? 'Reviewed' : 'Analyzed'; const shortPrompt = prompt.length > 100 ? prompt.substring(0, 100) + '...' : prompt; return `${action} ${fileName}: ${shortPrompt}`; } /** * Display project context summary */ export function displayContextSummary(context: ProjectContextFiles): void { const foundFiles: string[] = []; if (context.instructor) foundFiles.push('📋 Instructor'); if (context.sketch) foundFiles.push('📐 Sketch'); if (context.logicFlow) foundFiles.push('🔄 Logic Flow'); if (context.roadmap) foundFiles.push('🗺️ Roadmap'); if (context.history) foundFiles.push('📚 History'); if (foundFiles.length > 0) { console.log(chalk.cyan(`\n🔍 Found project context: ${foundFiles.join(', ')}`)); } }

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/babasida246/ai-mcp-gateway'

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