UNPKG

doctool

Version:

AI-powered documentation validation and management system

351 lines (342 loc) 12.9 kB
import * as fs from 'fs'; import * as path from 'path'; import { getChangesSinceDate, getLastUpdateTimestamp } from './gitUtils.js'; import { parseMarkdownSections } from './diffUtils.js'; /** * Analyzes a knowledge file for specific issues */ export function analyzeDocumentationIssues(knowledgeFilePath, basePath) { const issues = []; const dirPath = path.dirname(knowledgeFilePath); const lastUpdate = getLastUpdateTimestamp(knowledgeFilePath); // Get current files in directory const currentFiles = getCurrentDirectoryFiles(dirPath); // Get changes since last update const checkDate = lastUpdate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const changes = getChangesSinceDate(basePath, checkDate); // Filter changes relevant to this directory const relativeDirPath = path.relative(basePath, dirPath); const directoryChanges = { newFiles: changes.newFiles.filter(f => f.startsWith(relativeDirPath)), modifiedFiles: changes.modifiedFiles.filter(f => f.startsWith(relativeDirPath)), deletedFiles: changes.deletedFiles.filter(f => f.startsWith(relativeDirPath)) }; // Read and parse existing knowledge file let content = ''; let sections = []; try { content = fs.readFileSync(knowledgeFilePath, 'utf8'); sections = parseMarkdownSections(content); } catch (error) { issues.push({ type: 'missing_sections', severity: 'high', description: 'Knowledge file is missing or unreadable', suggestedFix: { action: 'add_section', content: generateBasicTemplate(path.basename(dirPath)) } }); } // Issue 1: Check for template placeholders const placeholderIssues = detectPlaceholderContent(content); issues.push(...placeholderIssues); // Issue 2: Check for missing new files in file listings const missingFileIssues = detectMissingFiles(content, currentFiles, directoryChanges.newFiles); issues.push(...missingFileIssues); // Issue 3: Check for outdated file descriptions const outdatedDescriptionIssues = detectOutdatedDescriptions(content, currentFiles, dirPath); issues.push(...outdatedDescriptionIssues); // Issue 4: Check for missing standard sections const missingSectionIssues = detectMissingSections(sections); issues.push(...missingSectionIssues); // Issue 5: Check for deleted files still mentioned const deletedFileIssues = detectDeletedFileReferences(content, directoryChanges.deletedFiles); issues.push(...deletedFileIssues); // Determine overall health const highIssues = issues.filter(i => i.severity === 'high').length; const mediumIssues = issues.filter(i => i.severity === 'medium').length; let overallHealth; if (highIssues > 0 || mediumIssues > 3) { overallHealth = 'poor'; } else if (mediumIssues > 0 || issues.length > 2) { overallHealth = 'needs_attention'; } else { overallHealth = 'good'; } return { filePath: knowledgeFilePath, issues, overallHealth, lastUpdated: lastUpdate || undefined, filesAnalyzed: currentFiles, directoryChanges }; } /** * Detects template placeholder content that needs to be replaced */ function detectPlaceholderContent(content) { const issues = []; const placeholders = [ { text: '[brief description of the directory\'s purpose]', section: 'Overview' }, { text: '[description]', section: 'Files' }, { text: '[Describe the role this directory plays', section: 'Purpose' }, { text: '[List and describe important files', section: 'Key Components' }, { text: '[List any dependencies or relationships', section: 'Dependencies' }, { text: '[Any additional notes, warnings', section: 'Notes' } ]; for (const placeholder of placeholders) { if (content.includes(placeholder.text)) { issues.push({ type: 'placeholder_content', severity: 'medium', description: `Placeholder content found in ${placeholder.section} section`, location: { section: placeholder.section }, suggestedFix: { action: 'remove_placeholder', target: placeholder.text } }); } } return issues; } /** * Detects missing files in the documentation */ function detectMissingFiles(content, currentFiles, newFiles) { const issues = []; // Check for new files not mentioned in documentation for (const newFile of newFiles) { const fileName = path.basename(newFile); if (!content.includes(fileName) && isSignificantFile(fileName)) { issues.push({ type: 'missing_files', severity: 'medium', description: `New file "${fileName}" not documented`, suggestedFix: { action: 'add_files', content: generateFileDescription(fileName), target: fileName } }); } } // Check for significant files in directory not mentioned for (const file of currentFiles) { if (!content.includes(file) && isSignificantFile(file)) { issues.push({ type: 'missing_files', severity: 'low', description: `File "${file}" not documented`, suggestedFix: { action: 'add_files', content: generateFileDescription(file), target: file } }); } } return issues; } /** * Detects outdated descriptions by checking if files have changed significantly */ function detectOutdatedDescriptions(content, currentFiles, dirPath) { const issues = []; // For now, we'll check for generic descriptions that could be improved const genericDescriptions = [ 'Core application module', 'File containing application logic', 'TypeScript/JavaScript module' ]; for (const description of genericDescriptions) { if (content.includes(description)) { // Find which file this might be describing const lines = content.split('\n'); const descriptionLine = lines.find(line => line.includes(description)); if (descriptionLine) { const fileMatch = descriptionLine.match(/`([^`]+)`/); if (fileMatch) { const fileName = fileMatch[1]; issues.push({ type: 'outdated_descriptions', severity: 'low', description: `Generic description for "${fileName}" could be improved`, suggestedFix: { action: 'update_content', content: generateBetterFileDescription(fileName, dirPath), target: fileName } }); } } } } return issues; } /** * Detects missing standard sections */ function detectMissingSections(sections) { const issues = []; const requiredSections = [ { name: 'Overview', level: 2 }, { name: 'Contents', level: 2 }, { name: 'Purpose', level: 2 } ]; for (const required of requiredSections) { const exists = sections.some(s => s.heading.toLowerCase() === required.name.toLowerCase() && s.level === required.level); if (!exists) { issues.push({ type: 'missing_sections', severity: 'medium', description: `Missing standard section: ${required.name}`, suggestedFix: { action: 'add_section', content: generateSectionTemplate(required.name), target: required.name } }); } } return issues; } /** * Detects references to deleted files */ function detectDeletedFileReferences(content, deletedFiles) { const issues = []; for (const deletedFile of deletedFiles) { const fileName = path.basename(deletedFile); if (content.includes(fileName)) { issues.push({ type: 'missing_files', severity: 'medium', description: `Reference to deleted file "${fileName}" should be removed`, suggestedFix: { action: 'remove_placeholder', target: fileName } }); } } return issues; } /** * Gets current files in a directory */ function getCurrentDirectoryFiles(dirPath) { try { return fs.readdirSync(dirPath, { withFileTypes: true }) .filter(item => item.isFile()) .map(item => item.name); } catch (error) { return []; } } /** * Determines if a file is significant enough to document */ function isSignificantFile(fileName) { // Skip hidden files, lock files, and certain extensions if (fileName.startsWith('.') || fileName.includes('lock') || fileName === 'knowledge.md') { return false; } const significantExtensions = ['.ts', '.js', '.tsx', '.jsx', '.md', '.json', '.yaml', '.yml']; const extension = path.extname(fileName); return significantExtensions.includes(extension); } /** * Generates a basic file description */ function generateFileDescription(fileName) { const ext = path.extname(fileName); const baseName = path.basename(fileName, ext); if (fileName.includes('.test.') || fileName.includes('.spec.')) { return `Test file for ${baseName} functionality`; } switch (ext) { case '.ts': case '.js': return `TypeScript/JavaScript module containing ${baseName} functionality`; case '.md': return 'Documentation file'; case '.json': return 'Configuration file'; case '.yaml': case '.yml': return 'Configuration file'; default: return `${baseName} implementation file`; } } /** * Generates a better file description by analyzing the actual file */ function generateBetterFileDescription(fileName, dirPath) { try { const filePath = path.join(dirPath, fileName); const content = fs.readFileSync(filePath, 'utf8'); // Analyze content for exports, classes, functions const exports = (content.match(/export\s+(?:class|function|const|interface)\s+(\w+)/g) || []).length; const classes = (content.match(/class\s+\w+/g) || []).length; const interfaces = (content.match(/interface\s+\w+/g) || []).length; const functions = (content.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length; if (classes > 0) { return `Contains ${classes} class${classes > 1 ? 'es' : ''} with core functionality`; } else if (interfaces > 0) { return `Defines ${interfaces} interface${interfaces > 1 ? 's' : ''} for type safety`; } else if (functions > 2) { return `Utility module with ${functions} functions`; } else if (exports > 0) { return `Module exporting ${exports} component${exports > 1 ? 's' : ''}`; } return generateFileDescription(fileName); } catch (error) { return generateFileDescription(fileName); } } /** * Generates a template for missing sections */ function generateSectionTemplate(sectionName) { switch (sectionName.toLowerCase()) { case 'overview': return '## Overview\n\nThis directory contains [brief description of purpose and functionality].'; case 'contents': return '## Contents\n\n### Files\n[File listings will be updated automatically]\n\n### Subdirectories\n[Subdirectory listings will be updated automatically]'; case 'purpose': return '## Purpose\n\nThis directory serves [specific role in the project architecture].'; default: return `## ${sectionName}\n\n[Section content to be added]`; } } /** * Generates a basic knowledge file template */ function generateBasicTemplate(directoryName) { return `# ${directoryName} ## Overview This directory contains [brief description of purpose and functionality]. ## Contents ### Files [File listings will be updated automatically] ### Subdirectories [Subdirectory listings will be updated automatically] ## Purpose This directory serves [specific role in the project architecture]. ## Notes [Additional notes and important information]`; } //# sourceMappingURL=documentationIssues.js.map