Skip to main content
Glama

documcp

by tosin2013
optimize-readme.ts16.2 kB
import { z } from "zod"; import { promises as fs } from "fs"; import path from "path"; import { MCPToolResponse } from "../types/api.js"; // Input validation schema const OptimizeReadmeInputSchema = z.object({ readme_path: z.string().min(1, "README path is required"), strategy: z .enum([ "community_focused", "enterprise_focused", "developer_focused", "general", ]) .optional() .default("community_focused"), max_length: z.number().min(50).max(1000).optional().default(300), include_tldr: z.boolean().optional().default(true), preserve_existing: z.boolean().optional().default(false), output_path: z.string().optional(), create_docs_directory: z.boolean().optional().default(true), }); export type OptimizeReadmeInput = z.infer<typeof OptimizeReadmeInputSchema>; interface OptimizationResult { originalLength: number; optimizedLength: number; reductionPercentage: number; optimizedContent: string; extractedSections: ExtractedSection[]; tldrGenerated: string | null; restructuringChanges: RestructuringChange[]; recommendations: string[]; } interface ExtractedSection { title: string; content: string; suggestedLocation: string; reason: string; } interface RestructuringChange { type: "moved" | "condensed" | "split" | "added" | "removed"; section: string; description: string; impact: string; } /** * Optimizes README content by restructuring, condensing, and extracting detailed sections. * * Performs intelligent README optimization including length reduction, structure improvement, * content extraction to separate documentation, and TL;DR generation. Uses different strategies * based on target audience (community, enterprise, developer, general) to maximize effectiveness. * * @param input - The input parameters for README optimization * @param input.readme_path - The file system path to the README file to optimize * @param input.strategy - The optimization strategy to apply (default: "community_focused") * @param input.max_length - Target maximum length in lines (default: 300) * @param input.include_tldr - Whether to generate a TL;DR section (default: true) * @param input.preserve_existing - Whether to preserve existing content structure (default: false) * @param input.output_path - Optional output path for optimized README * @param input.create_docs_directory - Whether to create docs/ directory for extracted content (default: true) * * @returns Promise resolving to README optimization results * @returns optimization - Complete optimization results including length reduction and restructuring * @returns nextSteps - Array of recommended next actions after optimization * * @throws {Error} When README file is inaccessible or invalid * @throws {Error} When optimization processing fails * @throws {Error} When output directory cannot be created * * @example * ```typescript * // Optimize README for community contributors * const result = await optimizeReadme({ * readme_path: "./README.md", * strategy: "community_focused", * max_length: 300, * include_tldr: true * }); * * console.log(`Reduced from ${result.data.optimization.originalLength} to ${result.data.optimization.optimizedLength} lines`); * console.log(`Reduction: ${result.data.optimization.reductionPercentage}%`); * * // Optimize for enterprise with aggressive reduction * const enterprise = await optimizeReadme({ * readme_path: "./README.md", * strategy: "enterprise_focused", * max_length: 200, * preserve_existing: true * }); * ``` * * @since 1.0.0 */ export async function optimizeReadme( input: Partial<OptimizeReadmeInput>, ): Promise< MCPToolResponse<{ optimization: OptimizationResult; nextSteps: string[] }> > { const startTime = Date.now(); try { // Validate input const validatedInput = OptimizeReadmeInputSchema.parse(input); const { readme_path, strategy, max_length, include_tldr, output_path, create_docs_directory, } = validatedInput; // Read original README const originalContent = await fs.readFile(readme_path, "utf-8"); const originalLength = originalContent.split("\n").length; // Parse README structure const sections = parseReadmeStructure(originalContent); // Generate TL;DR if requested const tldrGenerated = include_tldr ? generateTldr(originalContent, sections) : null; // Identify sections to extract const extractedSections = identifySectionsToExtract( sections, strategy, max_length, ); // Create basic optimization result const optimizedContent = originalContent + "\n\n## TL;DR\n\n" + (tldrGenerated || "Quick overview of the project."); const restructuringChanges = [ { type: "added" as const, section: "TL;DR", description: "Added concise project overview", impact: "Helps users quickly understand project value", }, ]; const optimizedLength = optimizedContent.split("\n").length; const reductionPercentage = Math.round( ((originalLength - optimizedLength) / originalLength) * 100, ); // Create docs directory and extract detailed content if requested if (create_docs_directory && extractedSections.length > 0) { await createDocsStructure(path.dirname(readme_path), extractedSections); } // Write optimized README if output path specified if (output_path) { await fs.writeFile(output_path, optimizedContent, "utf-8"); } const recommendations = generateOptimizationRecommendations( originalLength, optimizedLength, extractedSections, strategy, ); const optimization: OptimizationResult = { originalLength, optimizedLength, reductionPercentage, optimizedContent, extractedSections, tldrGenerated, restructuringChanges, recommendations, }; const nextSteps = generateOptimizationNextSteps( optimization, validatedInput, ); return { success: true, data: { optimization, nextSteps, }, metadata: { toolVersion: "1.0.0", executionTime: Date.now() - startTime, timestamp: new Date().toISOString(), }, }; } catch (error) { return { success: false, error: { code: "OPTIMIZATION_FAILED", message: "Failed to optimize README", details: error instanceof Error ? error.message : "Unknown error", resolution: "Check README file path and permissions", }, metadata: { toolVersion: "1.0.0", executionTime: Date.now() - startTime, timestamp: new Date().toISOString(), }, }; } } interface ReadmeSection { title: string; content: string; level: number; startLine: number; endLine: number; wordCount: number; isEssential: boolean; } function parseReadmeStructure(content: string): ReadmeSection[] { const lines = content.split("\n"); const sections: ReadmeSection[] = []; let currentTitle = ""; let currentLevel = 0; let currentStartLine = 0; lines.forEach((line, index) => { const headingMatch = line.match(/^(#{1,6})\s+(.+)$/); if (headingMatch) { // Save previous section if (currentTitle) { const endLine = index - 1; const sectionContent = lines .slice(currentStartLine, endLine + 1) .join("\n"); const wordCount = sectionContent.split(/\s+/).length; const isEssential = isEssentialSection(currentTitle); sections.push({ title: currentTitle, content: sectionContent, level: currentLevel, startLine: currentStartLine, endLine: endLine, wordCount: wordCount, isEssential: isEssential, }); } // Start new section currentTitle = headingMatch[2].trim(); currentLevel = headingMatch[1].length; currentStartLine = index; } }); // Add final section if (currentTitle) { const endLine = lines.length - 1; const sectionContent = lines .slice(currentStartLine, endLine + 1) .join("\n"); const wordCount = sectionContent.split(/\s+/).length; const isEssential = isEssentialSection(currentTitle); sections.push({ title: currentTitle, content: sectionContent, level: currentLevel, startLine: currentStartLine, endLine: endLine, wordCount: wordCount, isEssential: isEssential, }); } return sections; } function isEssentialSection(title: string): boolean { const essentialKeywords = [ "installation", "install", "setup", "getting started", "quick start", "usage", "example", "api", "license", "contributing", ]; return essentialKeywords.some((keyword) => title.toLowerCase().includes(keyword), ); } function generateTldr(content: string, sections: ReadmeSection[]): string { // Extract project name from first heading const projectNameMatch = content.match(/^#\s+(.+)$/m); const projectName = projectNameMatch ? projectNameMatch[1] : "This project"; // Extract description (usually after title or in blockquote) const descriptionMatch = content.match(/>\s*(.+)/); let description = descriptionMatch ? descriptionMatch[1] : ""; // If no description found, try to extract from first paragraph if (!description) { const firstParagraphMatch = content.match(/^[^#\n].{20,200}/m); description = firstParagraphMatch ? firstParagraphMatch[0].substring(0, 100) + "..." : ""; } // Identify key features or use cases const features: string[] = []; sections.forEach((section) => { if ( section.title.toLowerCase().includes("feature") || section.title.toLowerCase().includes("what") || section.title.toLowerCase().includes("why") ) { const bullets = section.content.match(/^\s*[-*+]\s+(.+)$/gm); if (bullets && bullets.length > 0) { features.push( ...bullets .slice(0, 3) .map((b) => b.replace(/^\s*[-*+]\s+/, "").trim()), ); } } }); let tldr = `## TL;DR\n\n${projectName} ${description}\n\n`; if (features.length > 0) { tldr += `**Key features:**\n`; features.slice(0, 3).forEach((feature) => { tldr += `- ${feature}\n`; }); tldr += "\n"; } // Add quick start reference const hasInstallSection = sections.some( (s) => s.title.toLowerCase().includes("install") || s.title.toLowerCase().includes("setup"), ); if (hasInstallSection) { tldr += `**Quick start:** See [Installation](#installation) → [Usage](#usage)\n\n`; } return tldr; } function identifySectionsToExtract( sections: ReadmeSection[], strategy: string, maxLength: number, ): ExtractedSection[] { const extractedSections: ExtractedSection[] = []; const currentLength = sections.reduce( (sum, s) => sum + s.content.split("\n").length, 0, ); if (currentLength <= maxLength) { return extractedSections; // No extraction needed } // Define extraction rules based on strategy const extractionRules = getExtractionRules(strategy); sections.forEach((section) => { for (const rule of extractionRules) { if (rule.matcher(section) && !section.isEssential) { extractedSections.push({ title: section.title, content: section.content, suggestedLocation: rule.suggestedLocation, reason: rule.reason, }); break; } } }); return extractedSections; } function getExtractionRules(strategy: string) { const baseRules = [ { matcher: (section: ReadmeSection) => section.wordCount > 200, suggestedLocation: "docs/detailed-guide.md", reason: "Section too long for main README", }, { matcher: (section: ReadmeSection) => /troubleshoot|faq|common issues|problems/i.test(section.title), suggestedLocation: "docs/troubleshooting.md", reason: "Troubleshooting content better suited for separate document", }, { matcher: (section: ReadmeSection) => /advanced|configuration|config/i.test(section.title), suggestedLocation: "docs/configuration.md", reason: "Advanced configuration details", }, { matcher: (section: ReadmeSection) => /development|developer|build|compile/i.test(section.title), suggestedLocation: "docs/development.md", reason: "Development-specific information", }, ]; if (strategy === "community_focused") { baseRules.push({ matcher: (section: ReadmeSection) => /architecture|design|technical/i.test(section.title), suggestedLocation: "docs/technical.md", reason: "Technical details can overwhelm community contributors", }); } return baseRules; } async function createDocsStructure( projectDir: string, extractedSections: ExtractedSection[], ): Promise<void> { const docsDir = path.join(projectDir, "docs"); try { await fs.mkdir(docsDir, { recursive: true }); } catch { // Directory might already exist } // Create extracted documentation files for (const section of extractedSections) { const filePath = path.join(projectDir, section.suggestedLocation); const fileDir = path.dirname(filePath); try { await fs.mkdir(fileDir, { recursive: true }); await fs.writeFile(filePath, section.content, "utf-8"); } catch (error) { console.warn(`Failed to create ${filePath}:`, error); } } // Create docs index const indexContent = generateDocsIndex(extractedSections); await fs.writeFile(path.join(docsDir, "README.md"), indexContent, "utf-8"); } function generateDocsIndex(extractedSections: ExtractedSection[]): string { let content = "# Documentation\n\n"; content += "This directory contains detailed documentation extracted from the main README for better organization.\n\n"; content += "## Available Documentation\n\n"; extractedSections.forEach((section) => { const filename = path.basename(section.suggestedLocation); content += `- [${section.title}](${filename}) - ${section.reason}\n`; }); return content; } function generateOptimizationRecommendations( originalLength: number, optimizedLength: number, extractedSections: ExtractedSection[], strategy: string, ): string[] { const recommendations: string[] = []; const reduction = originalLength - optimizedLength; if (reduction > 0) { recommendations.push( `✅ Reduced README length by ${reduction} lines (${Math.round( (reduction / originalLength) * 100, )}%)`, ); } if (extractedSections.length > 0) { recommendations.push( `📁 Moved ${extractedSections.length} detailed sections to docs/ directory`, ); } if (strategy === "community_focused") { recommendations.push( "👥 Optimized for community contributors - prioritized quick start and contribution info", ); } recommendations.push( "🔗 Added links to detailed documentation for users who need more information", ); recommendations.push( "📊 Consider adding a table of contents for sections with 5+ headings", ); return recommendations; } function generateOptimizationNextSteps( optimization: OptimizationResult, input: OptimizeReadmeInput, ): string[] { const steps: string[] = []; if (!input.output_path) { steps.push("💾 Review optimized content and save to README.md when ready"); } if (optimization.extractedSections.length > 0) { steps.push("📝 Review extracted documentation files in docs/ directory"); steps.push("🔗 Update any internal links that may have been affected"); } if (optimization.reductionPercentage > 30) { steps.push( "👀 Have team members review the condensed content for accuracy", ); } steps.push("📈 Run analyze_readme again to verify improvements"); steps.push("🎯 Consider setting up automated README length monitoring"); return steps; }

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/tosin2013/documcp'

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