import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { MCPClient } from '../client.js';
import { readProjectContext } from '../utils/projectContext.js';
interface ProjectSummaryOptions {
output?: string;
budget?: number;
model?: string;
verbose?: boolean;
endpoint?: string;
apiKey?: string;
username?: string;
password?: string;
}
/**
* Generate a comprehensive project summary by analyzing all project files and history
*/
export async function summarizeProject(options: ProjectSummaryOptions = {}) {
const client = new MCPClient(options.endpoint, options.apiKey, options.username, options.password);
try {
console.log(chalk.blue('📊 Analyzing project for summary generation...'));
// Read all project context
const projectContext = readProjectContext();
// Read coding history if it exists
const historyPath = 'project-coding-history.md';
let history = '';
if (fs.existsSync(historyPath)) {
history = fs.readFileSync(historyPath, 'utf-8');
}
// Get project structure
const projectStructure = getProjectStructure('.');
// Count lines of code
const codeStats = getCodeStatistics('.');
// Build comprehensive summary prompt
const summaryPrompt = buildSummaryPrompt(projectContext, history, projectStructure, codeStats);
if (options.verbose) {
console.log(chalk.gray('Context loaded:'));
if (projectContext.sketch) console.log(chalk.gray('- SKETCH.md'));
if (projectContext.roadmap) console.log(chalk.gray('- ROADMAP.md'));
if (projectContext.instructor) console.log(chalk.gray('- mcp-instructor.md'));
if (history) console.log(chalk.gray('- project-coding-history.md'));
console.log(chalk.gray(`- ${projectStructure.fileCount} files analyzed`));
console.log(chalk.gray(`- ${codeStats.totalLines} total lines of code`));
}
console.log(chalk.yellow('🤖 Generating comprehensive project summary...'));
// Request summary from AI
const response = await client.send({
mode: 'chat',
message: summaryPrompt,
budget: options.budget ?? 0, // Use provided budget or default to free tier
});
// Format the summary
const timestamp = new Date().toISOString().split('T')[0];
const summaryContent = `# Project Summary - ${timestamp}
Generated on: ${new Date().toLocaleString()}
${response.message || 'No summary generated'}
---
## Project Statistics
- **Total Files**: ${projectStructure.fileCount}
- **Total Lines of Code**: ${codeStats.totalLines}
- **Primary Languages**: ${codeStats.languages.slice(0, 3).map(l => `${l.name} (${l.lines} lines)`).join(', ')}
- **Directories**: ${projectStructure.dirCount}
## Files Analyzed
${projectStructure.files.slice(0, 10).map(f => `- ${f}`).join('\n')}
${projectStructure.files.length > 10 ? `\n... and ${projectStructure.files.length - 10} more files` : ''}
---
*Generated by ai-mcp-gateway CLI*
`;
// Save summary to file
const outputFile = options.output || `PROJECT-SUMMARY-${timestamp}.md`;
fs.writeFileSync(outputFile, summaryContent);
console.log(chalk.green(`✅ Project summary generated successfully!`));
console.log(chalk.cyan(`📄 Summary saved to: ${outputFile}`));
console.log(chalk.gray(`💰 Cost: $${response.cost?.toFixed(4) || '0.0000'}`));
} catch (error: any) {
console.error(chalk.red('❌ Error generating project summary:'), error.message);
process.exit(1);
}
}
/**
* Build comprehensive prompt for project summarization
*/
function buildSummaryPrompt(
context: any,
history: string,
structure: any,
stats: any
): string {
let prompt = `Please analyze this project and generate a comprehensive summary. Include:
1. **Project Overview** - What is this project about?
2. **Architecture & Technology Stack** - Key technologies, frameworks, patterns used
3. **Current Status** - What's implemented vs. what's planned
4. **Key Features** - Main functionality and capabilities
5. **Development Progress** - Based on coding history if available
6. **Code Quality & Structure** - Analysis of codebase organization
7. **Next Steps** - Recommendations for future development
## Project Context
`;
// Add project documentation context
if (context.sketch) {
prompt += `### Project Sketch\n${context.sketch}\n\n`;
}
if (context.roadmap) {
prompt += `### Project Roadmap\n${context.roadmap}\n\n`;
}
if (context.instructor) {
prompt += `### Instructions/Requirements\n${context.instructor}\n\n`;
}
if (context.readme) {
prompt += `### README\n${context.readme}\n\n`;
}
// Add project structure
prompt += `## Project Structure (${structure.fileCount} files)\n\n`;
prompt += '```\n';
prompt += structure.tree;
prompt += '\n```\n\n';
// Add code statistics
prompt += `## Code Statistics\n\n`;
prompt += `- Total lines: ${stats.totalLines}\n`;
prompt += `- Languages: ${stats.languages.map((l: any) => `${l.name} (${l.lines} lines)`).join(', ')}\n\n`;
// Add coding history if available
if (history) {
prompt += `## Development History\n\n${history}\n\n`;
}
prompt += `Please provide a detailed, well-structured analysis in markdown format.`;
return prompt;
}
/**
* Get project directory structure
*/
function getProjectStructure(dir: string): { files: string[], fileCount: number, dirCount: number, tree: string } {
const files: string[] = [];
let fileCount = 0;
let dirCount = 0;
function walkDir(currentPath: string, prefix: string = ''): string {
let tree = '';
try {
const items = fs.readdirSync(currentPath)
.filter(item => !item.startsWith('.') && item !== 'node_modules')
.sort();
for (let i = 0; i < items.length; i++) {
const item = items[i];
const itemPath = path.join(currentPath, item);
const isLast = i === items.length - 1;
const connector = isLast ? '└── ' : '├── ';
try {
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
tree += `${prefix}${connector}${item}/\n`;
dirCount++;
const newPrefix = prefix + (isLast ? ' ' : '│ ');
tree += walkDir(itemPath, newPrefix);
} else {
tree += `${prefix}${connector}${item}\n`;
files.push(path.relative(dir, itemPath));
fileCount++;
}
} catch (err) {
// Skip inaccessible files/directories
}
}
} catch (err) {
// Skip inaccessible directories
}
return tree;
}
const tree = walkDir(dir);
return { files, fileCount, dirCount, tree };
}
/**
* Get code statistics by language
*/
function getCodeStatistics(dir: string): { totalLines: number, languages: Array<{ name: string, lines: number }> } {
const languageMap: { [key: string]: number } = {};
let totalLines = 0;
const extensions = {
'.js': 'JavaScript',
'.ts': 'TypeScript',
'.jsx': 'JavaScript React',
'.tsx': 'TypeScript React',
'.py': 'Python',
'.java': 'Java',
'.cpp': 'C++',
'.c': 'C',
'.cs': 'C#',
'.php': 'PHP',
'.rb': 'Ruby',
'.go': 'Go',
'.rs': 'Rust',
'.swift': 'Swift',
'.kt': 'Kotlin',
'.scala': 'Scala',
'.html': 'HTML',
'.css': 'CSS',
'.scss': 'SCSS',
'.sass': 'SASS',
'.less': 'LESS',
'.vue': 'Vue',
'.sql': 'SQL',
'.sh': 'Shell',
'.bash': 'Bash',
'.ps1': 'PowerShell',
'.md': 'Markdown',
'.json': 'JSON',
'.xml': 'XML',
'.yaml': 'YAML',
'.yml': 'YAML'
};
function countLinesInFile(filePath: string): number {
try {
const content = fs.readFileSync(filePath, 'utf-8');
return content.split('\n').length;
} catch {
return 0;
}
}
function processDirectory(currentPath: string) {
try {
const items = fs.readdirSync(currentPath);
for (const item of items) {
if (item.startsWith('.') || item === 'node_modules') continue;
const itemPath = path.join(currentPath, item);
try {
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
processDirectory(itemPath);
} else {
const ext = path.extname(item).toLowerCase();
const language = extensions[ext as keyof typeof extensions];
if (language) {
const lines = countLinesInFile(itemPath);
languageMap[language] = (languageMap[language] || 0) + lines;
totalLines += lines;
}
}
} catch {
// Skip inaccessible files
}
}
} catch {
// Skip inaccessible directories
}
}
processDirectory(dir);
const languages = Object.entries(languageMap)
.map(([name, lines]) => ({ name, lines }))
.sort((a, b) => b.lines - a.lines);
return { totalLines, languages };
}