mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
767 lines • 34.8 kB
JavaScript
export class DocumentationAnalyzer {
projectRoot;
essentialFiles = [
{ file: 'README.md', weight: 25, required: true },
{ file: 'CHANGELOG.md', weight: 10, required: false },
{ file: 'LICENSE', weight: 5, required: false },
{ file: 'CONTRIBUTING.md', weight: 8, required: false },
{ file: 'API.md', weight: 15, required: false },
{ file: 'docs/README.md', weight: 10, required: false },
{ file: 'docs/api/', weight: 12, required: false },
{ file: 'docs/examples/', weight: 8, required: false },
{ file: 'docs/guides/', weight: 7, required: false }
];
readmeSections = [
'Installation', 'Usage', 'API', 'Examples', 'Contributing',
'License', 'Getting Started', 'Configuration', 'Troubleshooting'
];
constructor(projectRoot) {
this.projectRoot = projectRoot;
}
async analyze() {
const missingDocs = [];
const suggestions = [];
const issues = [];
const strengths = [];
const fileAnalysis = [];
// Comprehensive documentation analysis
const readmeQuality = await this.analyzeReadmeQuality();
const apiDocumentation = await this.analyzeApiDocumentation();
const { coverage, qualityScore } = await this.analyzeCoverageAndQuality(missingDocs, suggestions, issues, strengths);
const readabilityScore = await this.analyzeReadability();
const fileAnalysisResults = await this.analyzeFileDocumentation();
fileAnalysis.push(...fileAnalysisResults);
// Calculate overall scores
const apiDocumentationScore = apiDocumentation.apiCoveragePercentage;
const score = this.calculateOverallScore(coverage, qualityScore, readabilityScore, apiDocumentationScore, readmeQuality.score);
// Generate comprehensive recommendations
const recommendations = this.generateRecommendations(readmeQuality, apiDocumentation, issues, coverage);
return {
score,
coverage,
qualityScore,
readabilityScore,
apiDocumentationScore,
readmeQuality,
apiDocumentation,
missingDocs,
suggestions,
issues,
strengths,
recommendations,
fileAnalysis
};
}
async analyzeReadmeQuality() {
const fs = await import('fs-extra');
const path = await import('path');
const readmePath = path.join(this.projectRoot, 'README.md');
const result = {
exists: false,
score: 0,
wordCount: 0,
sectionCount: 0,
codeExampleCount: 0,
linkCount: 0,
missingEssentialSections: [],
readabilityMetrics: {
averageWordsPerSentence: 0,
averageSentencesPerParagraph: 0,
complexWordCount: 0,
readingLevel: 'elementary',
readingTimeMinutes: 0
},
structureScore: 0,
contentQuality: 0
};
if (!(await fs.pathExists(readmePath))) {
return result;
}
result.exists = true;
try {
const content = await fs.readFile(readmePath, 'utf-8');
// Basic metrics
result.wordCount = content.split(/\s+/).filter(word => word.length > 0).length;
result.sectionCount = (content.match(/^#{1,6}\s+/gm) || []).length;
result.codeExampleCount = (content.match(/```[\s\S]*?```/g) || []).length;
result.linkCount = (content.match(/\[([^\]]+)\]\(([^)]+)\)/g) || []).length;
// Check for essential sections
const requiredSections = ['installation', 'usage', 'getting started', 'setup'];
const recommendedSections = ['api', 'examples', 'contributing', 'license', 'configuration'];
const lowercaseContent = content.toLowerCase();
const foundRequired = requiredSections.filter(section => lowercaseContent.includes(section) || lowercaseContent.includes(`# ${section}`) || lowercaseContent.includes(`## ${section}`));
const foundRecommended = recommendedSections.filter(section => lowercaseContent.includes(section) || lowercaseContent.includes(`# ${section}`) || lowercaseContent.includes(`## ${section}`));
result.missingEssentialSections = requiredSections.filter(section => !foundRequired.includes(section));
// Calculate readability metrics
result.readabilityMetrics = this.calculateReadabilityMetrics(content);
// Structure score (0-100)
result.structureScore = Math.min(100, ((result.sectionCount > 0 ? 20 : 0) +
(foundRequired.length * 15) +
(foundRecommended.length * 5) +
(result.codeExampleCount > 0 ? 15 : 0) +
(result.linkCount > 0 ? 10 : 0) +
(content.includes('Table of Contents') || content.includes('TOC') ? 10 : 0)));
// Content quality score (0-100)
result.contentQuality = Math.min(100, ((result.wordCount > 100 ? 20 : Math.floor(result.wordCount / 5)) +
(result.codeExampleCount * 10) +
(result.readabilityMetrics.readingLevel === 'high_school' || result.readabilityMetrics.readingLevel === 'college' ? 20 : 10) +
(content.includes('badge') || content.includes('shield') ? 10 : 0) +
(result.linkCount > 2 ? 15 : result.linkCount * 5) +
(content.includes('screenshot') || content.includes('demo') ? 10 : 0)));
// Overall README score
result.score = Math.round((result.structureScore + result.contentQuality) / 2);
}
catch (error) {
// README exists but can't be read
}
return result;
}
async analyzeApiDocumentation() {
const fs = await import('fs-extra');
const path = await import('path');
const glob = await import('glob');
const result = {
totalFunctions: 0,
documentedFunctions: 0,
totalClasses: 0,
documentedClasses: 0,
totalInterfaces: 0,
documentedInterfaces: 0,
totalTypes: 0,
documentedTypes: 0,
apiCoveragePercentage: 0,
qualityScore: 0,
missingParameterDocs: [],
missingReturnDocs: [],
missingExampleDocs: [],
outdatedDocs: []
};
try {
const files = await glob.glob('**/*.{ts,js,tsx,jsx}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*']
});
for (const file of files) {
const filePath = path.join(this.projectRoot, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
await this.analyzeFileApiDocumentation(content, file, result);
}
catch (error) {
// Skip files that can't be read
}
}
// Calculate coverage percentage
const totalApiElements = result.totalFunctions + result.totalClasses + result.totalInterfaces + result.totalTypes;
const documentedApiElements = result.documentedFunctions + result.documentedClasses + result.documentedInterfaces + result.documentedTypes;
result.apiCoveragePercentage = totalApiElements > 0 ? Math.round((documentedApiElements / totalApiElements) * 100) : 100;
// Calculate quality score based on completeness and detail
result.qualityScore = this.calculateApiQualityScore(result);
}
catch (error) {
// Skip if analysis fails
}
return result;
}
async analyzeFileApiDocumentation(content, file, result) {
// Function patterns
const functionPatterns = [
/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/g,
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g,
/(\w+)\s*:\s*\([^)]*\)\s*=>/g,
/(?:public|private|protected|static)?\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/g
];
// Class and interface patterns
const classPattern = /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g;
const interfacePattern = /(?:export\s+)?interface\s+(\w+)/g;
const typePattern = /(?:export\s+)?type\s+(\w+)\s*=/g;
// JSDoc pattern
const jsdocPattern = /\/\*\*[\s\S]*?\*\//g;
// Count functions
let match;
const functions = [];
for (const pattern of functionPatterns) {
while ((match = pattern.exec(content)) !== null) {
if (match[1] && !functions.includes(match[1])) {
functions.push(match[1]);
result.totalFunctions++;
}
}
}
// Count classes
while ((match = classPattern.exec(content)) !== null) {
result.totalClasses++;
}
// Count interfaces
while ((match = interfacePattern.exec(content)) !== null) {
result.totalInterfaces++;
}
// Count types
while ((match = typePattern.exec(content)) !== null) {
result.totalTypes++;
}
// Analyze JSDoc comments
const jsdocComments = content.match(jsdocPattern) || [];
for (const jsdoc of jsdocComments) {
// Check what this JSDoc documents
const afterJsdoc = content.substring(content.indexOf(jsdoc) + jsdoc.length);
if (/^\s*(?:export\s+)?(?:async\s+)?function/.test(afterJsdoc) ||
/^\s*(?:export\s+)?const\s+\w+\s*=\s*(?:async\s+)?\(/.test(afterJsdoc) ||
/^\s*(?:public|private|protected|static)?\s*(?:async\s+)?\w+\s*\(/.test(afterJsdoc)) {
result.documentedFunctions++;
// Check documentation quality
if (!jsdoc.includes('@param')) {
const functionMatch = afterJsdoc.match(/(?:function\s+)?(\w+)/) || afterJsdoc.match(/const\s+(\w+)/);
if (functionMatch && afterJsdoc.includes('(') && afterJsdoc.includes(')')) {
result.missingParameterDocs.push(`${file}: ${functionMatch[1]}`);
}
}
if (!jsdoc.includes('@returns') && !jsdoc.includes('@return')) {
const functionMatch = afterJsdoc.match(/(?:function\s+)?(\w+)/) || afterJsdoc.match(/const\s+(\w+)/);
if (functionMatch) {
result.missingReturnDocs.push(`${file}: ${functionMatch[1]}`);
}
}
if (!jsdoc.includes('@example')) {
const functionMatch = afterJsdoc.match(/(?:function\s+)?(\w+)/) || afterJsdoc.match(/const\s+(\w+)/);
if (functionMatch) {
result.missingExampleDocs.push(`${file}: ${functionMatch[1]}`);
}
}
}
else if (/^\s*(?:export\s+)?(?:abstract\s+)?class/.test(afterJsdoc)) {
result.documentedClasses++;
}
else if (/^\s*(?:export\s+)?interface/.test(afterJsdoc)) {
result.documentedInterfaces++;
}
else if (/^\s*(?:export\s+)?type/.test(afterJsdoc)) {
result.documentedTypes++;
}
}
}
async analyzeCoverageAndQuality(missingDocs, suggestions, issues, strengths) {
const fs = await import('fs-extra');
const path = await import('path');
let totalWeight = 0;
let foundWeight = 0;
// Check essential files
for (const essential of this.essentialFiles) {
totalWeight += essential.weight;
const filePath = path.join(this.projectRoot, essential.file);
if (await fs.pathExists(filePath)) {
foundWeight += essential.weight;
try {
const stats = await fs.stat(filePath);
if (stats.size < 100) {
issues.push({
type: 'incomplete',
severity: 'medium',
file: essential.file,
message: `${essential.file} exists but appears incomplete (${stats.size} bytes)`,
suggestion: `Add substantial content to ${essential.file}`
});
}
else {
strengths.push(`${essential.file} exists and has content`);
}
}
catch (error) {
// Skip if can't read file stats
}
}
else {
missingDocs.push(essential.file);
const severity = essential.required ? 'critical' : 'medium';
issues.push({
type: 'missing',
severity,
file: essential.file,
message: `Missing ${essential.file}`,
suggestion: `Create ${essential.file} with appropriate content`
});
}
}
// Check package.json documentation
await this.checkPackageJsonDocumentation(issues, suggestions, strengths);
// Check docs directory structure
await this.checkDocsDirectoryStructure(issues, suggestions, strengths);
const coverage = totalWeight > 0 ? Math.round((foundWeight / totalWeight) * 100) : 0;
const qualityScore = this.calculateQualityScore(issues, strengths);
return { coverage, qualityScore };
}
async checkPackageJsonDocumentation(issues, suggestions, strengths) {
const fs = await import('fs-extra');
const path = await import('path');
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
try {
const packageJson = await fs.readJson(packageJsonPath);
if (!packageJson.description || packageJson.description.length < 10) {
issues.push({
type: 'missing',
severity: 'medium',
file: 'package.json',
message: 'Missing or insufficient package description',
suggestion: 'Add a meaningful description (at least 10 characters)'
});
}
else {
strengths.push('Package has descriptive description');
}
if (!packageJson.repository) {
suggestions.push('Add repository URL to package.json for better discoverability');
}
else {
strengths.push('Repository information is provided');
}
if (!packageJson.keywords || packageJson.keywords.length === 0) {
suggestions.push('Add keywords to package.json for better discoverability');
}
else {
strengths.push(`Package has ${packageJson.keywords.length} keywords`);
}
if (!packageJson.author && !packageJson.contributors) {
suggestions.push('Add author or contributors information to package.json');
}
else {
strengths.push('Author/contributor information is provided');
}
if (packageJson.homepage) {
strengths.push('Homepage URL is provided');
}
if (packageJson.bugs) {
strengths.push('Bug reporting information is provided');
}
}
catch (error) {
issues.push({
type: 'poor_quality',
severity: 'low',
file: 'package.json',
message: 'Cannot parse package.json',
suggestion: 'Verify package.json syntax is valid'
});
}
}
}
async checkDocsDirectoryStructure(issues, suggestions, strengths) {
const fs = await import('fs-extra');
const path = await import('path');
const docsDir = path.join(this.projectRoot, 'docs');
if (await fs.pathExists(docsDir)) {
strengths.push('Documentation directory exists');
try {
const entries = await fs.readdir(docsDir, { withFileTypes: true });
const files = entries.filter(entry => entry.isFile()).map(entry => entry.name);
const dirs = entries.filter(entry => entry.isDirectory()).map(entry => entry.name);
if (files.length === 0 && dirs.length === 0) {
issues.push({
type: 'incomplete',
severity: 'medium',
file: 'docs/',
message: 'Documentation directory is empty',
suggestion: 'Add documentation files or subdirectories'
});
}
else {
strengths.push(`Documentation directory contains ${files.length} files and ${dirs.length} subdirectories`);
}
// Check for common documentation patterns
if (files.some(f => f.toLowerCase().includes('api'))) {
strengths.push('API documentation detected');
}
if (dirs.includes('examples') || files.some(f => f.toLowerCase().includes('example'))) {
strengths.push('Examples documentation detected');
}
if (dirs.includes('guides') || files.some(f => f.toLowerCase().includes('guide'))) {
strengths.push('User guides detected');
}
}
catch (error) {
// Skip if can't read docs directory
}
}
else {
suggestions.push('Create a docs/ directory for comprehensive documentation');
}
}
async analyzeReadability() {
const fs = await import('fs-extra');
const path = await import('path');
let totalScore = 0;
let fileCount = 0;
// Analyze markdown files
const markdownFiles = ['README.md', 'CHANGELOG.md', 'CONTRIBUTING.md'];
for (const file of markdownFiles) {
const filePath = path.join(this.projectRoot, file);
if (await fs.pathExists(filePath)) {
try {
const content = await fs.readFile(filePath, 'utf-8');
const metrics = this.calculateReadabilityMetrics(content);
// Score based on reading level and structure
let fileScore = 50; // Base score
switch (metrics.readingLevel) {
case 'high_school':
case 'college':
fileScore += 30;
break;
case 'middle':
fileScore += 20;
break;
default:
fileScore += 10;
}
// Bonus for good sentence length
if (metrics.averageWordsPerSentence >= 10 && metrics.averageWordsPerSentence <= 20) {
fileScore += 10;
}
// Bonus for good paragraph structure
if (metrics.averageSentencesPerParagraph >= 2 && metrics.averageSentencesPerParagraph <= 5) {
fileScore += 10;
}
totalScore += Math.min(100, fileScore);
fileCount++;
}
catch (error) {
// Skip if can't read file
}
}
}
return fileCount > 0 ? Math.round(totalScore / fileCount) : 70; // Default decent score
}
calculateReadabilityMetrics(content) {
// Remove code blocks and other non-prose content
const prose = content
.replace(/```[\s\S]*?```/g, '')
.replace(/`[^`]+`/g, '')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
.replace(/#{1,6}\s+/g, '')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1');
const sentences = prose.split(/[.!?]+/).filter(s => s.trim().length > 0);
const paragraphs = prose.split(/\n\s*\n/).filter(p => p.trim().length > 0);
const words = prose.split(/\s+/).filter(w => w.length > 0);
const averageWordsPerSentence = sentences.length > 0 ? words.length / sentences.length : 0;
const averageSentencesPerParagraph = paragraphs.length > 0 ? sentences.length / paragraphs.length : 0;
// Count complex words (3+ syllables, simplified)
const complexWordCount = words.filter(word => {
const syllables = word.toLowerCase().replace(/[^aeiou]/g, '').length;
return syllables >= 3;
}).length;
// Determine reading level based on average sentence length and complex words
let readingLevel = 'elementary';
const complexWordRatio = words.length > 0 ? complexWordCount / words.length : 0;
if (averageWordsPerSentence > 20 || complexWordRatio > 0.3) {
readingLevel = 'graduate';
}
else if (averageWordsPerSentence > 15 || complexWordRatio > 0.2) {
readingLevel = 'college';
}
else if (averageWordsPerSentence > 12 || complexWordRatio > 0.15) {
readingLevel = 'high_school';
}
else if (averageWordsPerSentence > 8 || complexWordRatio > 0.1) {
readingLevel = 'middle';
}
// Estimate reading time (average 200 words per minute)
const readingTimeMinutes = Math.max(1, Math.ceil(words.length / 200));
return {
averageWordsPerSentence: Math.round(averageWordsPerSentence * 10) / 10,
averageSentencesPerParagraph: Math.round(averageSentencesPerParagraph * 10) / 10,
complexWordCount,
readingLevel,
readingTimeMinutes
};
}
async analyzeFileDocumentation() {
const fs = await import('fs-extra');
const path = await import('path');
const glob = await import('glob');
const results = [];
try {
const files = await glob.glob('**/*.{ts,js,tsx,jsx,md}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
});
for (const file of files.slice(0, 20)) { // Limit for performance
const filePath = path.join(this.projectRoot, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const analysis = this.analyzeIndividualFile(content, file);
results.push(analysis);
}
catch (error) {
// Skip files that can't be read
}
}
}
catch (error) {
// Skip if glob fails
}
return results;
}
analyzeIndividualFile(content, file) {
const isMarkdown = file.endsWith('.md');
const isSource = /\.(ts|js|tsx|jsx)$/.test(file);
let score = 50; // Base score
const strengths = [];
const weaknesses = [];
// File header check
const hasFileHeader = content.startsWith('/**') || content.startsWith('/*') ||
(isMarkdown && content.startsWith('#'));
if (hasFileHeader) {
score += 10;
strengths.push('Has file header/description');
}
else {
weaknesses.push('Missing file header or description');
}
// Examples check
const hasExamples = content.includes('example') || content.includes('Example') ||
content.includes('```') || content.includes('@example');
if (hasExamples) {
score += 15;
strengths.push('Contains examples or code samples');
}
let functionDocumentationRatio = 0;
let classDocumentationRatio = 0;
if (isSource) {
// Count functions and documentation
const functions = (content.match(/(?:function|=>|\([^)]*\)\s*{)/g) || []).length;
const classes = (content.match(/class\s+\w+/g) || []).length;
const jsdocs = (content.match(/\/\*\*[\s\S]*?\*\//g) || []).length;
if (functions > 0) {
functionDocumentationRatio = Math.min(100, (jsdocs / functions) * 100);
score += functionDocumentationRatio * 0.3; // Up to 30 points
if (functionDocumentationRatio > 80) {
strengths.push('Well-documented functions');
}
else if (functionDocumentationRatio < 30) {
weaknesses.push('Poor function documentation');
}
}
if (classes > 0) {
classDocumentationRatio = Math.min(100, (jsdocs / classes) * 100);
score += classDocumentationRatio * 0.2; // Up to 20 points
if (classDocumentationRatio > 80) {
strengths.push('Well-documented classes');
}
else if (classDocumentationRatio < 50) {
weaknesses.push('Poor class documentation');
}
}
// Check for TypeScript types
if (file.endsWith('.ts') || file.endsWith('.tsx')) {
if (content.includes('interface') || content.includes('type ')) {
score += 10;
strengths.push('Uses TypeScript types/interfaces');
}
}
}
if (isMarkdown) {
// Markdown-specific analysis
const headings = (content.match(/^#{1,6}\s+/gm) || []).length;
const links = (content.match(/\[([^\]]+)\]\([^)]+\)/g) || []).length;
const codeBlocks = (content.match(/```[\s\S]*?```/g) || []).length;
if (headings > 0) {
score += Math.min(15, headings * 3);
strengths.push(`Well-structured with ${headings} headings`);
}
if (links > 0) {
score += Math.min(10, links * 2);
strengths.push(`Contains ${links} helpful links`);
}
if (codeBlocks > 0) {
score += Math.min(15, codeBlocks * 5);
strengths.push(`Includes ${codeBlocks} code examples`);
}
}
// Length analysis
if (content.length < 200) {
score -= 10;
weaknesses.push('File is very short');
}
else if (content.length > 1000) {
score += 5;
strengths.push('Substantial content');
}
return {
file,
type: isMarkdown ? 'markdown' : isSource ? 'source' : 'other',
score: Math.max(0, Math.min(100, Math.round(score))),
issues: weaknesses.length,
strengths,
weaknesses,
functionDocumentationRatio: Math.round(functionDocumentationRatio),
classDocumentationRatio: Math.round(classDocumentationRatio),
hasFileHeader,
hasExamples
};
}
calculateApiQualityScore(apiDoc) {
let score = apiDoc.apiCoveragePercentage;
// Bonus for complete documentation
if (apiDoc.missingParameterDocs.length === 0)
score += 10;
if (apiDoc.missingReturnDocs.length === 0)
score += 10;
if (apiDoc.missingExampleDocs.length === 0)
score += 15;
// Penalty for many missing elements
score -= Math.min(20, apiDoc.missingParameterDocs.length * 2);
score -= Math.min(15, apiDoc.missingReturnDocs.length * 2);
score -= Math.min(10, apiDoc.missingExampleDocs.length);
return Math.max(0, Math.min(100, Math.round(score)));
}
calculateQualityScore(issues, strengths) {
let score = 70; // Base quality score
// Deduct for issues
for (const issue of issues) {
switch (issue.severity) {
case 'critical':
score -= 15;
break;
case 'high':
score -= 10;
break;
case 'medium':
score -= 5;
break;
case 'low':
score -= 2;
break;
}
}
// Add for strengths
score += Math.min(30, strengths.length * 3);
return Math.max(0, Math.min(100, score));
}
calculateOverallScore(coverage, qualityScore, readabilityScore, apiDocumentationScore, readmeScore) {
// Weighted average
const weights = {
coverage: 0.25,
quality: 0.20,
readability: 0.15,
api: 0.25,
readme: 0.15
};
return Math.round(coverage * weights.coverage +
qualityScore * weights.quality +
readabilityScore * weights.readability +
apiDocumentationScore * weights.api +
readmeScore * weights.readme);
}
generateRecommendations(readmeQuality, apiDocumentation, issues, coverage) {
const recommendations = [];
// Critical recommendations
if (!readmeQuality.exists) {
recommendations.push({
priority: 'critical',
category: 'content',
title: 'Create comprehensive README',
description: 'README.md is missing - this is essential for any project',
effort: 'medium',
impact: 'high',
actionItems: [
'Create README.md file',
'Add project description and overview',
'Include installation instructions',
'Add usage examples',
'Document basic API or CLI usage'
]
});
}
// API documentation recommendations
if (apiDocumentation.apiCoveragePercentage < 50) {
recommendations.push({
priority: 'high',
category: 'api',
title: 'Improve API documentation coverage',
description: `Only ${apiDocumentation.apiCoveragePercentage}% of API elements are documented`,
effort: 'high',
impact: 'high',
actionItems: [
'Add JSDoc comments to public functions',
'Document function parameters with @param',
'Document return values with @returns',
'Add usage examples with @example',
'Document class constructors and methods'
]
});
}
// README quality recommendations
if (readmeQuality.exists && readmeQuality.score < 60) {
recommendations.push({
priority: 'medium',
category: 'content',
title: 'Enhance README quality',
description: 'README exists but lacks essential sections or content',
effort: 'medium',
impact: 'medium',
actionItems: [
...readmeQuality.missingEssentialSections.map(section => `Add ${section} section`),
'Include more code examples',
'Add links to detailed documentation',
'Consider adding badges or status indicators'
]
});
}
// Structure recommendations
if (coverage < 70) {
recommendations.push({
priority: 'medium',
category: 'structure',
title: 'Create comprehensive documentation structure',
description: 'Missing key documentation files and directories',
effort: 'medium',
impact: 'medium',
actionItems: [
'Create docs/ directory',
'Add API documentation files',
'Create user guides and tutorials',
'Add CHANGELOG.md for version history',
'Consider adding CONTRIBUTING.md'
]
});
}
// Example recommendations
if (apiDocumentation.missingExampleDocs.length > 5) {
recommendations.push({
priority: 'medium',
category: 'examples',
title: 'Add more code examples',
description: 'Many functions lack usage examples',
effort: 'low',
impact: 'medium',
actionItems: [
'Add @example tags to JSDoc comments',
'Create examples/ directory with sample code',
'Include common use cases in README',
'Add interactive examples or demos'
]
});
}
// Maintenance recommendations
const criticalIssues = issues.filter(issue => issue.severity === 'critical').length;
if (criticalIssues > 0) {
recommendations.push({
priority: 'high',
category: 'maintenance',
title: 'Address critical documentation issues',
description: `${criticalIssues} critical documentation issues need immediate attention`,
effort: 'low',
impact: 'high',
actionItems: issues
.filter(issue => issue.severity === 'critical')
.map(issue => issue.suggestion)
.slice(0, 5)
});
}
return recommendations.sort((a, b) => {
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
}
}
//# sourceMappingURL=DocumentationAnalyzer.js.map