update-existing-documentation.ts•38.1 kB
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import * as fs from "fs/promises";
import * as path from "path";
import {
handleMemoryRecall,
handleMemoryEnhancedRecommendation,
handleMemoryIntelligentAnalysis,
} from "../memory/index.js";
interface UpdateOptions {
analysisId: string;
docsPath: string;
compareMode: "comprehensive" | "gap-detection" | "accuracy-check";
updateStrategy: "conservative" | "moderate" | "aggressive";
preserveStyle: boolean;
focusAreas?: string[];
}
interface DocumentationGap {
type: "missing" | "outdated" | "incorrect" | "incomplete";
location: string;
description: string;
severity: "low" | "medium" | "high" | "critical";
suggestedUpdate: string;
memoryEvidence?: any[];
}
interface CodeDocumentationComparison {
codeFeatures: any[];
documentedFeatures: any[];
gaps: DocumentationGap[];
outdatedSections: any[];
accuracyIssues: any[];
}
interface UpdateRecommendation {
section: string;
currentContent: string;
suggestedContent: string;
reasoning: string;
memoryEvidence: any[];
confidence: number;
effort: "low" | "medium" | "high";
}
interface UpdateResult {
success: boolean;
analysisPerformed: CodeDocumentationComparison;
recommendations: UpdateRecommendation[];
memoryInsights: {
similarProjects: any[];
successfulUpdatePatterns: any[];
commonGapTypes: Record<string, number>;
};
updateMetrics: {
gapsDetected: number;
recommendationsGenerated: number;
confidenceScore: number;
estimatedEffort: string;
};
nextSteps: string[];
}
class DocumentationUpdateEngine {
private memoryInsights: any = null;
private codeAnalysis: any = null;
private existingDocs: Map<string, any> = new Map();
async updateExistingDocumentation(
options: UpdateOptions,
): Promise<UpdateResult> {
// 1. Load repository analysis and memory insights
const analysis = await this.getRepositoryAnalysis(options.analysisId);
this.codeAnalysis = analysis;
// 2. Load memory insights for intelligent comparison
await this.loadMemoryInsights(analysis, options);
// 3. Analyze existing documentation structure and content
const existingDocs = await this.analyzeExistingDocumentation(
options.docsPath,
);
this.existingDocs = existingDocs;
// 4. Perform comprehensive code-documentation comparison
const comparison = await this.performCodeDocumentationComparison(
analysis,
existingDocs,
options,
);
// 5. Generate memory-informed update recommendations
const recommendations = await this.generateUpdateRecommendations(
comparison,
options,
);
// 6. Calculate metrics and confidence scores
const updateMetrics = this.calculateUpdateMetrics(
comparison,
recommendations,
);
return {
success: true,
analysisPerformed: comparison,
recommendations,
memoryInsights: this.memoryInsights,
updateMetrics,
nextSteps: this.generateMemoryInformedNextSteps(
comparison,
recommendations,
),
};
}
private async getRepositoryAnalysis(analysisId: string): Promise<any> {
// Try to get analysis from memory system first
try {
const memoryRecall = await handleMemoryRecall({
query: analysisId,
type: "analysis",
limit: 1,
});
// Handle the memory recall result structure
if (
memoryRecall &&
memoryRecall.memories &&
memoryRecall.memories.length > 0
) {
const memory = memoryRecall.memories[0];
// Handle wrapped content structure
if (
memory.data &&
memory.data.content &&
Array.isArray(memory.data.content)
) {
// Extract the JSON from the first text content
const firstContent = memory.data.content[0];
if (
firstContent &&
firstContent.type === "text" &&
firstContent.text
) {
try {
return JSON.parse(firstContent.text);
} catch (parseError) {
console.warn(
"Failed to parse analysis content from memory:",
parseError,
);
return memory.data;
}
}
}
// Try direct content access (legacy format)
if (memory.content) {
return memory.content;
}
// Try data field
if (memory.data) {
return memory.data;
}
}
} catch (error) {
console.warn("Failed to retrieve from memory system:", error);
}
// Fallback to reading from cached analysis file
const analysisPath = path.join(
".documcp",
"analyses",
`${analysisId}.json`,
);
try {
const content = await fs.readFile(analysisPath, "utf-8");
return JSON.parse(content);
} catch {
throw new Error(
`Repository analysis with ID '${analysisId}' not found. Please run analyze_repository first.`,
);
}
}
private async loadMemoryInsights(
analysis: any,
options: UpdateOptions,
): Promise<void> {
try {
// Get similar projects that had successful documentation updates
const similarProjectsQuery = `${
analysis.metadata?.primaryLanguage || ""
} ${analysis.metadata?.ecosystem || ""} documentation update`;
const similarProjects = await handleMemoryRecall({
query: similarProjectsQuery,
type: "recommendation",
limit: 10,
});
// Get patterns for successful documentation updates
const updatePatternsQuery =
"documentation update successful patterns gaps outdated";
const updatePatterns = await handleMemoryRecall({
query: updatePatternsQuery,
type: "configuration",
limit: 5,
});
// Get memory-enhanced analysis for this specific update task
const enhancedAnalysis = await handleMemoryIntelligentAnalysis({
projectPath: analysis.projectPath || "",
baseAnalysis: analysis,
});
// Get memory-enhanced recommendations for update strategy
const enhancedRecommendations = await handleMemoryEnhancedRecommendation({
projectPath: analysis.projectPath || "",
baseRecommendation: {
updateStrategy: options.updateStrategy,
compareMode: options.compareMode,
focusAreas: options.focusAreas || [],
},
projectFeatures: {
ecosystem: analysis.metadata?.ecosystem || "unknown",
primaryLanguage: analysis.metadata?.primaryLanguage || "unknown",
complexity: analysis.complexity || "medium",
hasTests: analysis.structure?.hasTests || false,
hasCI: analysis.structure?.hasCI || false,
docStructure: "existing", // Indicates we're updating existing docs
},
});
this.memoryInsights = {
similarProjects: similarProjects.memories || [],
updatePatterns: updatePatterns.memories || [],
enhancedAnalysis: enhancedAnalysis,
enhancedRecommendations: enhancedRecommendations,
successfulUpdatePatterns: this.extractUpdatePatterns(
similarProjects.memories || [],
),
commonGapTypes: this.extractCommonGapTypes(
similarProjects.memories || [],
),
};
} catch (error) {
console.warn("Failed to load memory insights:", error);
this.memoryInsights = {
similarProjects: [],
updatePatterns: [],
enhancedAnalysis: null,
enhancedRecommendations: null,
successfulUpdatePatterns: [],
commonGapTypes: {},
};
}
}
private extractUpdatePatterns(projects: any[]): any[] {
return projects
.filter(
(p) => p.content?.updatePatterns || p.content?.documentationUpdates,
)
.map((p) => p.content?.updatePatterns || p.content?.documentationUpdates)
.flat()
.filter(Boolean);
}
private extractCommonGapTypes(projects: any[]): Record<string, number> {
const gapTypes: Record<string, number> = {};
projects.forEach((p) => {
const gaps = p.content?.documentationGaps || [];
gaps.forEach((gap: any) => {
const type = gap.type || "unknown";
gapTypes[type] = (gapTypes[type] || 0) + 1;
});
});
return gapTypes;
}
private async analyzeExistingDocumentation(
docsPath: string,
): Promise<Map<string, any>> {
const docs = new Map<string, any>();
try {
await this.recursivelyAnalyzeDocuments(docsPath, docs);
} catch (error) {
console.warn("Failed to analyze existing documentation:", error);
}
return docs;
}
private async recursivelyAnalyzeDocuments(
dirPath: string,
docs: Map<string, any>,
relativePath: string = "",
): Promise<void> {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
const docPath = path.join(relativePath, entry.name);
if (entry.isDirectory()) {
await this.recursivelyAnalyzeDocuments(fullPath, docs, docPath);
} else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) {
try {
const content = await fs.readFile(fullPath, "utf-8");
const analysis = this.analyzeDocumentContent(content, docPath);
docs.set(docPath, {
content,
analysis,
lastModified: (await fs.stat(fullPath)).mtime,
path: fullPath,
});
} catch (error) {
console.warn(`Failed to read document ${fullPath}:`, error);
}
}
}
} catch (error) {
console.warn(`Failed to read directory ${dirPath}:`, error);
}
}
private analyzeDocumentContent(content: string, filePath: string): any {
return {
type: this.inferDocumentType(filePath, content),
sections: this.extractSections(content),
codeBlocks: this.extractCodeBlocks(content),
links: this.extractLinks(content),
lastUpdated: this.extractLastUpdated(content),
version: this.extractVersion(content),
dependencies: this.extractMentionedDependencies(content),
features: this.extractDocumentedFeatures(content),
wordCount: content.split(/\s+/).length,
headingStructure: this.extractHeadingStructure(content),
};
}
private inferDocumentType(filePath: string, content: string): string {
const fileName = path.basename(filePath).toLowerCase();
const pathParts = filePath.toLowerCase().split(path.sep);
// Diataxis categories
if (pathParts.includes("tutorials")) return "tutorial";
if (pathParts.includes("how-to") || pathParts.includes("howto"))
return "how-to";
if (pathParts.includes("reference")) return "reference";
if (pathParts.includes("explanation")) return "explanation";
// Common documentation types
if (fileName.includes("readme")) return "readme";
if (fileName.includes("getting-started") || fileName.includes("quickstart"))
return "getting-started";
if (fileName.includes("api")) return "api-reference";
if (fileName.includes("install") || fileName.includes("setup"))
return "installation";
if (fileName.includes("deploy")) return "deployment";
if (fileName.includes("config")) return "configuration";
// Infer from content
if (
content.includes("# Getting Started") ||
content.includes("## Getting Started")
)
return "getting-started";
if (content.includes("# API") || content.includes("## API"))
return "api-reference";
if (
content.includes("# Installation") ||
content.includes("## Installation")
)
return "installation";
return "general";
}
private extractSections(content: string): any[] {
const sections: any[] = [];
const lines = content.split("\n");
let currentSection: any = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
if (headingMatch) {
if (currentSection) {
sections.push(currentSection);
}
currentSection = {
level: headingMatch[1].length,
title: headingMatch[2],
startLine: i + 1,
content: [],
};
} else if (currentSection) {
currentSection.content.push(line);
}
}
if (currentSection) {
sections.push(currentSection);
}
return sections.map((section) => ({
...section,
content: section.content.join("\n"),
wordCount: section.content.join(" ").split(/\s+/).length,
}));
}
private extractCodeBlocks(content: string): any[] {
const codeBlocks: any[] = [];
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
let match;
while ((match = codeBlockRegex.exec(content)) !== null) {
codeBlocks.push({
language: match[1] || "text",
code: match[2],
startIndex: match.index,
endIndex: match.index + match[0].length,
});
}
return codeBlocks;
}
private extractLinks(content: string): any[] {
const links: any[] = [];
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match;
while ((match = linkRegex.exec(content)) !== null) {
links.push({
text: match[1],
url: match[2],
isInternal: !match[2].startsWith("http"),
startIndex: match.index,
});
}
return links;
}
private extractLastUpdated(content: string): string | null {
const updateMatch = content.match(
/(?:last updated|updated|modified):\s*(.+)/i,
);
return updateMatch ? updateMatch[1] : null;
}
private extractVersion(content: string): string | null {
const versionMatch = content.match(/(?:version|v)[\s:]+([\d.]+)/i);
return versionMatch ? versionMatch[1] : null;
}
private extractMentionedDependencies(content: string): string[] {
const dependencies: Set<string> = new Set();
// Extract from npm install commands
const npmMatches = content.match(/npm install\s+([^`\n]+)/g);
if (npmMatches) {
npmMatches.forEach((match) => {
const packages = match.replace("npm install", "").trim().split(/\s+/);
packages.forEach((pkg) => {
if (pkg && !pkg.startsWith("-")) {
dependencies.add(pkg);
}
});
});
}
// Extract from import statements
const importMatches = content.match(/import.*from\s+['"]([^'"]+)['"]/g);
if (importMatches) {
importMatches.forEach((match) => {
const packageMatch = match.match(/from\s+['"]([^'"]+)['"]/);
if (packageMatch && !packageMatch[1].startsWith(".")) {
dependencies.add(packageMatch[1]);
}
});
}
return Array.from(dependencies);
}
private extractDocumentedFeatures(content: string): string[] {
const features: Set<string> = new Set();
// Extract function names from code blocks
const functionMatches = content.match(
/(?:function|const|let|var)\s+(\w+)/g,
);
if (functionMatches) {
functionMatches.forEach((match) => {
const functionMatch = match.match(/(?:function|const|let|var)\s+(\w+)/);
if (functionMatch) {
features.add(functionMatch[1]);
}
});
}
// Extract API endpoints
const apiMatches = content.match(
/(?:GET|POST|PUT|DELETE|PATCH)\s+([/\w-]+)/g,
);
if (apiMatches) {
apiMatches.forEach((match) => {
const endpointMatch = match.match(
/(?:GET|POST|PUT|DELETE|PATCH)\s+([/\w-]+)/,
);
if (endpointMatch) {
features.add(endpointMatch[1]);
}
});
}
// Extract mentioned features from headings
const headings = content.match(/#{1,6}\s+(.+)/g);
if (headings) {
headings.forEach((heading) => {
const headingText = heading.replace(/#{1,6}\s+/, "").toLowerCase();
if (
headingText.includes("feature") ||
headingText.includes("functionality")
) {
features.add(headingText);
}
});
}
return Array.from(features);
}
private extractHeadingStructure(content: string): any[] {
const headings: any[] = [];
const lines = content.split("\n");
lines.forEach((line, index) => {
const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
if (headingMatch) {
headings.push({
level: headingMatch[1].length,
text: headingMatch[2],
line: index + 1,
});
}
});
return headings;
}
private async performCodeDocumentationComparison(
analysis: any,
existingDocs: Map<string, any>,
_options: UpdateOptions,
): Promise<CodeDocumentationComparison> {
const codeFeatures = this.extractCodeFeatures(analysis);
const documentedFeatures = this.extractAllDocumentedFeatures(existingDocs);
const gaps = await this.detectDocumentationGaps(
codeFeatures,
documentedFeatures,
_options,
);
const outdatedSections = await this.detectOutdatedSections(
analysis,
existingDocs,
);
const accuracyIssues = await this.detectAccuracyIssues(
analysis,
existingDocs,
);
return {
codeFeatures,
documentedFeatures,
gaps,
outdatedSections,
accuracyIssues,
};
}
private extractCodeFeatures(analysis: any): any[] {
const features: any[] = [];
// Extract from dependencies
if (analysis.dependencies?.packages) {
analysis.dependencies.packages.forEach((pkg: string) => {
features.push({
type: "dependency",
name: pkg,
source: "package.json",
});
});
}
// Extract from scripts
const packageJson = this.findPackageJsonInAnalysis(analysis);
if (packageJson?.scripts) {
Object.keys(packageJson.scripts).forEach((script) => {
features.push({
type: "script",
name: script,
command: packageJson.scripts[script],
source: "package.json",
});
});
}
// Extract from file structure
if (analysis.structure) {
if (analysis.structure.hasTests) {
features.push({
type: "testing",
name: "test suite",
source: "structure",
});
}
if (analysis.structure.hasCI) {
features.push({
type: "ci-cd",
name: "continuous integration",
source: "structure",
});
}
}
// Extract from technologies
if (analysis.technologies) {
Object.entries(analysis.technologies).forEach(([key, value]) => {
if (value) {
features.push({
type: "technology",
name: key,
value: value,
source: "analysis",
});
}
});
}
return features;
}
private findPackageJsonInAnalysis(analysis: any): any {
const files = analysis.files || [];
const packageFile = files.find((f: any) => f.name === "package.json");
if (packageFile?.content) {
try {
return JSON.parse(packageFile.content);
} catch {
return null;
}
}
return null;
}
private extractAllDocumentedFeatures(existingDocs: Map<string, any>): any[] {
const allFeatures: any[] = [];
existingDocs.forEach((doc, docPath) => {
const features = doc.analysis?.features || [];
const dependencies = doc.analysis?.dependencies || [];
features.forEach((feature: string) => {
allFeatures.push({
name: feature,
source: docPath,
type: "documented-feature",
});
});
dependencies.forEach((dep: string) => {
allFeatures.push({
name: dep,
source: docPath,
type: "documented-dependency",
});
});
});
return allFeatures;
}
private async detectDocumentationGaps(
codeFeatures: any[],
documentedFeatures: any[],
_options: UpdateOptions,
): Promise<DocumentationGap[]> {
const gaps: DocumentationGap[] = [];
const memoryGapPatterns = this.memoryInsights?.commonGapTypes || {};
// Find features in code that aren't documented
codeFeatures.forEach((codeFeature) => {
const isDocumented = documentedFeatures.some((docFeature) =>
this.featuresMatch(codeFeature, docFeature),
);
if (!isDocumented) {
const severity = this.determineGapSeverity(
codeFeature,
memoryGapPatterns,
);
const suggestedUpdate = this.generateGapSuggestion(
codeFeature,
_options,
);
gaps.push({
type: "missing",
location: `${codeFeature.source} -> documentation`,
description: `${codeFeature.type} '${codeFeature.name}' exists in code but is not documented`,
severity,
suggestedUpdate,
memoryEvidence: this.findMemoryEvidenceForGap(codeFeature),
});
}
});
// Find documented features that no longer exist in code
documentedFeatures.forEach((docFeature) => {
const existsInCode = codeFeatures.some((codeFeature) =>
this.featuresMatch(codeFeature, docFeature),
);
if (!existsInCode) {
gaps.push({
type: "outdated",
location: docFeature.source,
description: `Documented feature '${docFeature.name}' no longer exists in code`,
severity: "medium",
suggestedUpdate: `Remove or update documentation for '${docFeature.name}'`,
memoryEvidence: this.findMemoryEvidenceForOutdated(docFeature),
});
}
});
return gaps;
}
private featuresMatch(codeFeature: any, docFeature: any): boolean {
// Exact name match
if (codeFeature.name === docFeature.name) return true;
// Type-specific matching
if (
codeFeature.type === "dependency" &&
docFeature.type === "documented-dependency"
) {
return codeFeature.name === docFeature.name;
}
// Partial match for similar names
const codeName = codeFeature.name.toLowerCase();
const docName = docFeature.name.toLowerCase();
return codeName.includes(docName) || docName.includes(codeName);
}
private determineGapSeverity(
codeFeature: any,
memoryGapPatterns: Record<string, number>,
): "low" | "medium" | "high" | "critical" {
// High importance features
if (
codeFeature.type === "script" &&
["start", "dev", "build", "test"].includes(codeFeature.name)
) {
return "high";
}
if (
codeFeature.type === "dependency" &&
this.isCriticalDependency(codeFeature.name)
) {
return "high";
}
if (codeFeature.type === "testing" || codeFeature.type === "ci-cd") {
return "medium";
}
// Check memory patterns for common gaps
const gapFrequency = memoryGapPatterns[codeFeature.type] || 0;
if (gapFrequency > 5) return "medium"; // Common gap type
if (gapFrequency > 2) return "low";
return "low";
}
private isCriticalDependency(depName: string): boolean {
const criticalDeps = [
"react",
"vue",
"angular",
"express",
"fastify",
"next",
"nuxt",
"gatsby",
"typescript",
"jest",
"mocha",
"webpack",
"vite",
"rollup",
];
return criticalDeps.some((critical) => depName.includes(critical));
}
private generateGapSuggestion(
codeFeature: any,
_options: UpdateOptions,
): string {
switch (codeFeature.type) {
case "script":
return `Add documentation for the '${codeFeature.name}' script: \`npm run ${codeFeature.name}\``;
case "dependency":
return `Document the '${codeFeature.name}' dependency and its usage`;
case "testing":
return `Add testing documentation explaining how to run and write tests`;
case "ci-cd":
return `Document the CI/CD pipeline and deployment process`;
case "technology":
return `Add explanation for ${codeFeature.name}: ${codeFeature.value}`;
default:
return `Document the ${codeFeature.type} '${codeFeature.name}'`;
}
}
private findMemoryEvidenceForGap(codeFeature: any): any[] {
return (
this.memoryInsights?.similarProjects
.filter(
(p: any) =>
p.content?.gaps?.some((gap: any) => gap.type === codeFeature.type),
)
.slice(0, 3) || []
);
}
private findMemoryEvidenceForOutdated(docFeature: any): any[] {
return (
this.memoryInsights?.similarProjects
.filter(
(p: any) =>
p.content?.outdatedSections?.some(
(section: any) => section.feature === docFeature.name,
),
)
.slice(0, 3) || []
);
}
private async detectOutdatedSections(
analysis: any,
existingDocs: Map<string, any>,
): Promise<any[]> {
const outdatedSections: any[] = [];
existingDocs.forEach((doc, docPath) => {
const sections = doc.analysis?.sections || [];
sections.forEach((section: any) => {
const isOutdated = this.checkSectionOutdated(section, analysis);
if (isOutdated) {
outdatedSections.push({
location: docPath,
section: section.title,
reason: isOutdated.reason,
confidence: isOutdated.confidence,
suggestedUpdate: isOutdated.suggestedUpdate,
});
}
});
});
return outdatedSections;
}
private checkSectionOutdated(section: any, analysis: any): any {
const sectionContent = section.content.toLowerCase();
// Check for outdated Node.js versions
const nodeVersionMatch = sectionContent.match(/node(?:\.js)?\s+(\d+)/);
if (nodeVersionMatch) {
const documentedVersion = parseInt(nodeVersionMatch[1], 10);
const currentRecommended = 18; // Current LTS
if (documentedVersion < currentRecommended - 2) {
return {
reason: `Documented Node.js version ${documentedVersion} is outdated`,
confidence: 0.9,
suggestedUpdate: `Update to recommend Node.js ${currentRecommended}+`,
};
}
}
// Check for outdated package names
const packageJson = this.findPackageJsonInAnalysis(analysis);
if (packageJson?.dependencies) {
const currentDeps = Object.keys(packageJson.dependencies);
// Look for documented packages that are no longer dependencies
for (const dep of currentDeps) {
if (sectionContent.includes(dep)) {
const version = packageJson.dependencies[dep];
if (
sectionContent.includes(dep) &&
!sectionContent.includes(version)
) {
return {
reason: `Package version information may be outdated for ${dep}`,
confidence: 0.7,
suggestedUpdate: `Update ${dep} version references to ${version}`,
};
}
}
}
}
return null;
}
private async detectAccuracyIssues(
analysis: any,
existingDocs: Map<string, any>,
): Promise<any[]> {
const accuracyIssues: any[] = [];
existingDocs.forEach((doc, docPath) => {
const codeBlocks = doc.analysis?.codeBlocks || [];
codeBlocks.forEach((codeBlock: any, index: number) => {
const issues = this.validateCodeBlock(codeBlock, analysis);
issues.forEach((issue) => {
accuracyIssues.push({
location: `${docPath}:code-block-${index}`,
type: issue.type,
description: issue.description,
severity: issue.severity,
suggestedFix: issue.suggestedFix,
});
});
});
});
return accuracyIssues;
}
private validateCodeBlock(codeBlock: any, analysis: any): any[] {
const issues: any[] = [];
const code = codeBlock.code;
// Check npm install commands against actual dependencies
const npmInstallMatches = code.match(/npm install\s+([^`\n]+)/g);
if (npmInstallMatches) {
const packageJson = this.findPackageJsonInAnalysis(analysis);
const actualDeps = packageJson
? Object.keys(packageJson.dependencies || {})
: [];
npmInstallMatches.forEach((match: string) => {
const packages = match.replace("npm install", "").trim().split(/\s+/);
packages.forEach((pkg: string) => {
if (pkg && !pkg.startsWith("-") && !actualDeps.includes(pkg)) {
issues.push({
type: "incorrect-dependency",
description: `npm install command includes '${pkg}' which is not in package.json`,
severity: "medium",
suggestedFix: `Remove '${pkg}' or add it to dependencies`,
});
}
});
});
}
// Check for outdated import syntax
if (
code.includes("require(") &&
analysis.metadata?.primaryLanguage === "TypeScript"
) {
issues.push({
type: "outdated-syntax",
description: "Using require() syntax in TypeScript project",
severity: "low",
suggestedFix: "Update to ES6 import syntax",
});
}
return issues;
}
private async generateUpdateRecommendations(
comparison: CodeDocumentationComparison,
_options: UpdateOptions,
): Promise<UpdateRecommendation[]> {
const recommendations: UpdateRecommendation[] = [];
// Generate recommendations for gaps
for (const gap of comparison.gaps) {
if (
gap.severity === "critical" ||
gap.severity === "high" ||
(gap.severity === "medium" &&
_options.updateStrategy !== "conservative")
) {
const recommendation = await this.generateGapRecommendation(
gap,
_options,
);
recommendations.push(recommendation);
}
}
// Generate recommendations for outdated sections
for (const outdated of comparison.outdatedSections) {
const recommendation = await this.generateOutdatedRecommendation(
outdated,
_options,
);
recommendations.push(recommendation);
}
// Generate recommendations for accuracy issues
for (const issue of comparison.accuracyIssues) {
if (
issue.severity !== "low" ||
_options.updateStrategy === "aggressive"
) {
const recommendation = await this.generateAccuracyRecommendation(
issue,
_options,
);
recommendations.push(recommendation);
}
}
return recommendations.sort((a, b) => b.confidence - a.confidence);
}
private async generateGapRecommendation(
gap: DocumentationGap,
_options: UpdateOptions,
): Promise<UpdateRecommendation> {
const memoryEvidence = gap.memoryEvidence || [];
const successfulPatterns =
this.memoryInsights?.successfulUpdatePatterns || [];
return {
section: gap.location,
currentContent: "", // No current content for missing items
suggestedContent: this.generateContentForGap(gap, successfulPatterns),
reasoning: `${gap.description}. ${memoryEvidence.length} similar projects had similar gaps.`,
memoryEvidence,
confidence: this.calculateGapConfidence(gap, memoryEvidence),
effort: this.estimateGapEffort(gap),
};
}
private generateContentForGap(
gap: DocumentationGap,
patterns: any[],
): string {
// Use memory patterns to generate appropriate content
const relevantPatterns = patterns.filter((p) => p.gapType === gap.type);
if (relevantPatterns.length > 0) {
const bestPattern = relevantPatterns[0];
return this.adaptPatternToGap(bestPattern, gap);
}
return gap.suggestedUpdate;
}
private adaptPatternToGap(pattern: any, gap: DocumentationGap): string {
let content = pattern.template || pattern.content || gap.suggestedUpdate;
// Replace placeholders with actual gap information
content = content.replace(/\{feature\}/g, gap.description);
content = content.replace(/\{location\}/g, gap.location);
return content;
}
private calculateGapConfidence(
gap: DocumentationGap,
evidence: any[],
): number {
let confidence = 0.5; // Base confidence
// Increase confidence based on severity
switch (gap.severity) {
case "critical":
confidence += 0.4;
break;
case "high":
confidence += 0.3;
break;
case "medium":
confidence += 0.2;
break;
case "low":
confidence += 0.1;
break;
}
// Increase confidence based on memory evidence
confidence += Math.min(evidence.length * 0.1, 0.3);
return Math.min(confidence, 1.0);
}
private estimateGapEffort(gap: DocumentationGap): "low" | "medium" | "high" {
switch (gap.type) {
case "missing":
return gap.severity === "critical" ? "high" : "medium";
case "outdated":
return "low";
case "incorrect":
return "medium";
case "incomplete":
return "low";
default:
return "medium";
}
}
private async generateOutdatedRecommendation(
outdated: any,
_options: UpdateOptions,
): Promise<UpdateRecommendation> {
return {
section: outdated.location,
currentContent: outdated.section,
suggestedContent: outdated.suggestedUpdate,
reasoning: outdated.reason,
memoryEvidence: [],
confidence: outdated.confidence || 0.8,
effort: "low",
};
}
private async generateAccuracyRecommendation(
issue: any,
_options: UpdateOptions,
): Promise<UpdateRecommendation> {
return {
section: issue.location,
currentContent: "Code block with accuracy issues",
suggestedContent: issue.suggestedFix,
reasoning: issue.description,
memoryEvidence: [],
confidence: issue.severity === "high" ? 0.9 : 0.7,
effort: issue.severity === "high" ? "medium" : "low",
};
}
private calculateUpdateMetrics(
comparison: CodeDocumentationComparison,
recommendations: UpdateRecommendation[],
): any {
const totalGaps = comparison.gaps.length;
const totalRecommendations = recommendations.length;
const avgConfidence =
recommendations.reduce((sum, r) => sum + r.confidence, 0) /
recommendations.length || 0;
const effortCounts = recommendations.reduce(
(acc, r) => {
acc[r.effort] = (acc[r.effort] || 0) + 1;
return acc;
},
{} as Record<string, number>,
);
let estimatedEffort = "low";
if (effortCounts.high > 0) estimatedEffort = "high";
else if (effortCounts.medium > effortCounts.low) estimatedEffort = "medium";
return {
gapsDetected: totalGaps,
recommendationsGenerated: totalRecommendations,
confidenceScore: Math.round(avgConfidence * 100) / 100,
estimatedEffort,
};
}
private generateMemoryInformedNextSteps(
comparison: CodeDocumentationComparison,
recommendations: UpdateRecommendation[],
): string[] {
const nextSteps = [];
const highConfidenceRecs = recommendations.filter(
(r) => r.confidence > 0.8,
);
const criticalGaps = comparison.gaps.filter(
(g) => g.severity === "critical",
);
if (criticalGaps.length > 0) {
nextSteps.push(
`Address ${criticalGaps.length} critical documentation gaps immediately`,
);
}
if (highConfidenceRecs.length > 0) {
nextSteps.push(
`Implement ${highConfidenceRecs.length} high-confidence recommendations first`,
);
}
if (comparison.accuracyIssues.length > 0) {
nextSteps.push(
`Fix ${comparison.accuracyIssues.length} code accuracy issues in documentation`,
);
}
nextSteps.push(
"Review and validate all recommended changes before implementation",
);
nextSteps.push("Test updated code examples to ensure they work correctly");
const memoryInsights = this.memoryInsights?.similarProjects?.length || 0;
if (memoryInsights > 0) {
nextSteps.push(
`Leverage patterns from ${memoryInsights} similar projects for additional improvements`,
);
}
return nextSteps;
}
}
// Export the tool implementation
export const updateExistingDocumentation: Tool = {
name: "update_existing_documentation",
description:
"Intelligently analyze and update existing documentation using memory insights and code comparison",
inputSchema: {
type: "object",
properties: {
analysisId: {
type: "string",
description: "Repository analysis ID from analyze_repository tool",
},
docsPath: {
type: "string",
description: "Path to existing documentation directory",
},
compareMode: {
type: "string",
enum: ["comprehensive", "gap-detection", "accuracy-check"],
default: "comprehensive",
description: "Mode of comparison between code and documentation",
},
updateStrategy: {
type: "string",
enum: ["conservative", "moderate", "aggressive"],
default: "moderate",
description: "How aggressively to suggest updates",
},
preserveStyle: {
type: "boolean",
default: true,
description: "Preserve existing documentation style and formatting",
},
focusAreas: {
type: "array",
items: { type: "string" },
description:
'Specific areas to focus updates on (e.g., "dependencies", "scripts", "api")',
},
},
required: ["analysisId", "docsPath"],
},
};
export async function handleUpdateExistingDocumentation(
args: any,
): Promise<UpdateResult> {
const engine = new DocumentationUpdateEngine();
return await engine.updateExistingDocumentation(args);
}