sync_code_to_docs
Automatically synchronize documentation with code changes using AST-based drift detection to maintain accuracy between source code and documentation.
Instructions
Automatically synchronize documentation with code changes using AST-based drift detection (Phase 3)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectPath | Yes | Path to the project root directory | |
| docsPath | Yes | Path to the documentation directory | |
| mode | No | Sync mode: detect=analyze only, preview=show changes, apply=apply safe changes, auto=apply all | detect |
| autoApplyThreshold | No | Confidence threshold (0-1) for automatic application of changes | |
| createSnapshot | No | Create a snapshot before making changes (recommended) |
Implementation Reference
- src/tools/sync-code-to-docs.ts:82-286 (handler)Main handler function that executes the sync_code_to_docs tool logic: parses input, detects drift between code and docs, applies changes based on mode, computes stats, and returns structured results.export async function handleSyncCodeToDocs( args: unknown, context?: any, ): Promise<{ content: any[] }> { const startTime = Date.now(); try { const { projectPath, docsPath, mode, autoApplyThreshold, createSnapshot } = inputSchema.parse(args); await context?.info?.( `đ Starting code-to-documentation synchronization (mode: ${mode})...`, ); // Initialize drift detector const detector = new DriftDetector(projectPath); await detector.initialize(); // Create baseline snapshot if requested if (createSnapshot || mode !== "detect") { await context?.info?.("đ¸ Creating code snapshot..."); await detector.createSnapshot(projectPath, docsPath); } // Load previous snapshot for comparison await context?.info?.("đ Detecting documentation drift..."); const previousSnapshot = await detector.loadLatestSnapshot(); if (!previousSnapshot) { await context?.info?.( "âšī¸ No previous snapshot found. Creating baseline...", ); const baselineSnapshot = await detector.createSnapshot( projectPath, docsPath, ); const result: SyncResult = { mode, driftDetections: [], appliedChanges: [], pendingChanges: [], stats: { filesAnalyzed: baselineSnapshot.files.size, driftsDetected: 0, changesApplied: 0, changesPending: 0, breakingChanges: 0, estimatedUpdateTime: "0 minutes", }, snapshotId: baselineSnapshot.timestamp, }; const response: MCPToolResponse<typeof result> = { success: true, data: result, metadata: { toolVersion: "3.0.0", executionTime: Date.now() - startTime, timestamp: new Date().toISOString(), }, recommendations: [ { type: "info", title: "Baseline Created", description: "Baseline snapshot created. Run sync again after code changes to detect drift.", }, ], }; return formatMCPResponse(response, { fullResponse: true }); } // Create current snapshot and detect drift const currentSnapshot = await detector.createSnapshot( projectPath, docsPath, ); const driftResults = await detector.detectDrift( previousSnapshot, currentSnapshot, ); await context?.info?.( `đ Found ${driftResults.length} file(s) with documentation drift`, ); // Process based on mode const appliedChanges: AppliedChange[] = []; const pendingChanges: PendingSuggestion[] = []; for (const driftResult of driftResults) { if (driftResult.hasDrift) { for (const suggestion of driftResult.suggestions) { if (mode === "apply" || mode === "auto") { // Apply changes based on confidence const shouldApply = mode === "auto" || (suggestion.autoApplicable && suggestion.confidence >= autoApplyThreshold); if (shouldApply) { try { await applyDocumentationChange( suggestion, context, projectPath, ); appliedChanges.push({ docFile: suggestion.docFile, section: suggestion.section, changeType: "updated", confidence: suggestion.confidence, details: suggestion.reasoning, }); } catch (error: any) { await context?.warn?.( `Failed to apply change to ${suggestion.docFile}: ${error.message}`, ); pendingChanges.push({ docFile: suggestion.docFile, section: suggestion.section, reason: `Auto-apply failed: ${error.message}`, suggestion, requiresReview: true, }); } } else { pendingChanges.push({ docFile: suggestion.docFile, section: suggestion.section, reason: "Requires manual review", suggestion, requiresReview: true, }); } } else { // Preview/detect mode - just collect suggestions pendingChanges.push({ docFile: suggestion.docFile, section: suggestion.section, reason: "Detected drift", suggestion, requiresReview: !suggestion.autoApplicable, }); } } } } // Calculate stats const stats = calculateSyncStats( driftResults, appliedChanges, pendingChanges, ); // Store sync results in knowledge graph await storeSyncResults(projectPath, driftResults, appliedChanges, context); const result: SyncResult = { mode, driftDetections: driftResults, appliedChanges, pendingChanges, stats, snapshotId: currentSnapshot.timestamp, }; const response: MCPToolResponse<typeof result> = { success: true, data: result, metadata: { toolVersion: "3.0.0", executionTime: Date.now() - startTime, timestamp: new Date().toISOString(), }, recommendations: generateRecommendations(result), nextSteps: generateNextSteps(result), }; await context?.info?.( `â Synchronization complete: ${appliedChanges.length} applied, ${pendingChanges.length} pending`, ); return formatMCPResponse(response, { fullResponse: true }); } catch (error: any) { const errorResponse: MCPToolResponse = { success: false, error: { code: "SYNC_FAILED", message: `Documentation synchronization failed: ${error.message}`, resolution: "Check project and documentation paths are correct", }, metadata: { toolVersion: "3.0.0", executionTime: Date.now() - startTime, timestamp: new Date().toISOString(), }, }; return formatMCPResponse(errorResponse, { fullResponse: true }); } }
- src/tools/sync-code-to-docs.ts:560-598 (registration)MCP Tool registration defining the 'sync_code_to_docs' tool with name, description, and detailed input schema.export const syncCodeToDocs: Tool = { name: "sync_code_to_docs", description: "Automatically synchronize documentation with code changes using AST-based drift detection (Phase 3)", inputSchema: { type: "object", properties: { projectPath: { type: "string", description: "Path to the project root directory", }, docsPath: { type: "string", description: "Path to the documentation directory", }, mode: { type: "string", enum: ["detect", "preview", "apply", "auto"], default: "detect", description: "Sync mode: detect=analyze only, preview=show changes, apply=apply safe changes, auto=apply all", }, autoApplyThreshold: { type: "number", minimum: 0, maximum: 1, default: 0.8, description: "Confidence threshold (0-1) for automatic application of changes", }, createSnapshot: { type: "boolean", default: true, description: "Create a snapshot before making changes (recommended)", }, }, required: ["projectPath", "docsPath"], }, };
- src/tools/sync-code-to-docs.ts:22-41 (schema)Zod input validation schema used within the handler to parse and validate tool arguments.const inputSchema = z.object({ projectPath: z.string().describe("Path to the project root"), docsPath: z.string().describe("Path to the documentation directory"), mode: z .enum(["detect", "preview", "apply", "auto"]) .default("detect") .describe( "Mode: detect=analyze only, preview=show changes, apply=apply safe changes, auto=apply all changes", ), autoApplyThreshold: z .number() .min(0) .max(1) .default(0.8) .describe("Confidence threshold for automatic application (0-1)"), createSnapshot: z .boolean() .default(true) .describe("Create a snapshot before making changes"), });
- Helper function to apply a specific documentation change: updates file content and metadata.async function applyDocumentationChange( suggestion: DriftSuggestion, context?: any, projectPath?: string, ): Promise<void> { const filePath = suggestion.docFile; // Read current file const content = await fs.readFile(filePath, "utf-8"); // Find and replace the section const sectionPattern = new RegExp( `(#{1,6}\\s+${escapeRegex(suggestion.section)}[\\s\\S]*?)(?=#{1,6}\\s+|$)`, "g", ); let newContent = content; const match = sectionPattern.exec(content); if (match) { // Replace existing section newContent = content.replace(sectionPattern, suggestion.suggestedContent); await context?.info?.( `âī¸ Updated section '${suggestion.section}' in ${path.basename( filePath, )}`, ); } else { // Append new section newContent = content + "\n\n" + suggestion.suggestedContent; await context?.info?.( `â Added section '${suggestion.section}' to ${path.basename(filePath)}`, ); } // Write back to file await fs.writeFile(filePath, newContent, "utf-8"); // Update freshness metadata try { let currentCommit: string | undefined; if (projectPath) { try { const git = simpleGit(projectPath); const isRepo = await git.checkIsRepo(); if (isRepo) { const log = await git.log({ maxCount: 1 }); currentCommit = log.latest?.hash; } } catch { // Git not available, continue without it } } await updateDocFrontmatter(filePath, { last_updated: new Date().toISOString(), last_validated: new Date().toISOString(), auto_updated: true, validated_against_commit: currentCommit, }); await context?.info?.( `đˇī¸ Updated freshness metadata for ${path.basename(filePath)}`, ); } catch (error) { // Non-critical error, just log it await context?.warn?.(`Failed to update freshness metadata: ${error}`); } }