UNPKG

smart-documentation-strategy

Version:

AI-powered documentation maintenance with visual staleness indicators, team collaboration, and Git integration

280 lines (230 loc) 9.74 kB
import * as vscode from 'vscode'; import { SDSChecker, loadConfig, DocumentInfo } from '@vaeshkar/sds-core'; let statusBarItem: vscode.StatusBarItem; let decorationType: vscode.TextEditorDecorationType; let staleDecorationType: vscode.TextEditorDecorationType; let currentDecorationType: vscode.TextEditorDecorationType; export function activate(context: vscode.ExtensionContext) { console.log('SDS extension is now active!'); // Initialize decorations initializeDecorations(); // Create status bar item statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); statusBarItem.command = 'sds.checkStaleness'; context.subscriptions.push(statusBarItem); // Register commands const checkCommand = vscode.commands.registerCommand('sds.checkStaleness', checkStaleness); const refreshCommand = vscode.commands.registerCommand('sds.refreshIndicators', refreshIndicators); const teamReportCommand = vscode.commands.registerCommand('sds.showTeamReport', showTeamReport); context.subscriptions.push(checkCommand, refreshCommand, teamReportCommand); // Listen for active editor changes vscode.window.onDidChangeActiveTextEditor(updateDecorations, null, context.subscriptions); vscode.workspace.onDidChangeTextDocument(event => { if (event.document === vscode.window.activeTextEditor?.document) { updateDecorations(); } }, null, context.subscriptions); // Initial decoration update updateDecorations(); } function initializeDecorations() { // Stale document decoration staleDecorationType = vscode.window.createTextEditorDecorationType({ backgroundColor: new vscode.ThemeColor('sds.staleDocument'), opacity: '0.3', isWholeLine: true, overviewRulerColor: new vscode.ThemeColor('sds.staleDocument'), overviewRulerLane: vscode.OverviewRulerLane.Right, }); // Current document decoration currentDecorationType = vscode.window.createTextEditorDecorationType({ backgroundColor: new vscode.ThemeColor('sds.currentDocument'), opacity: '0.2', isWholeLine: true, overviewRulerColor: new vscode.ThemeColor('sds.currentDocument'), overviewRulerLane: vscode.OverviewRulerLane.Right, }); } async function updateDecorations() { const editor = vscode.window.activeTextEditor; if (!editor || !editor.document.fileName.endsWith('.md')) { statusBarItem.hide(); return; } const config = vscode.workspace.getConfiguration('sds'); if (!config.get('enableIndicators')) { return; } try { const sdsConfig = loadConfig(); const checker = new SDSChecker(sdsConfig); const docInfo = await checker.extractDocumentInfoWithGit(editor.document.fileName); updateStatusBar(docInfo); highlightTimestamps(editor, docInfo); } catch (error) { console.error('SDS: Error updating decorations:', error); statusBarItem.text = '$(warning) SDS: Error'; statusBarItem.show(); } } function updateStatusBar(docInfo: DocumentInfo) { const config = vscode.workspace.getConfiguration('sds'); if (!config.get('showInStatusBar')) { statusBarItem.hide(); return; } const statusIcon = docInfo.status === 'current' ? '$(check)' : docInfo.status === 'stale' ? '$(warning)' : '$(question)'; let statusText = `${statusIcon} SDS: ${docInfo.status}`; if (docInfo.staleDays !== undefined) { statusText += ` (${docInfo.staleDays}d)`; } if (docInfo.assignedOwners && docInfo.assignedOwners.length > 0) { statusText += ` | ${docInfo.assignedOwners.join(', ')}`; } statusBarItem.text = statusText; statusBarItem.tooltip = createTooltip(docInfo); statusBarItem.show(); } function createTooltip(docInfo: DocumentInfo): string { let tooltip = `Documentation Status: ${docInfo.status}\n`; if (docInfo.lastUpdated) { tooltip += `Last Updated: ${docInfo.lastUpdated.toDateString()}\n`; } if (docInfo.staleDays !== undefined) { tooltip += `Age: ${docInfo.staleDays} days\n`; } if (docInfo.assignedOwners && docInfo.assignedOwners.length > 0) { tooltip += `Owners: ${docInfo.assignedOwners.join(', ')}\n`; } if (docInfo.lastUpdatedBy) { tooltip += `Last Updated By: ${docInfo.lastUpdatedBy}\n`; } if (docInfo.gitAuthor) { tooltip += `Git Author: ${docInfo.gitAuthor}\n`; } if (docInfo.priority) { tooltip += `Priority: ${docInfo.priority}\n`; } return tooltip.trim(); } function highlightTimestamps(editor: vscode.TextEditor, docInfo: DocumentInfo) { const text = editor.document.getText(); const lines = text.split('\n'); const staleDecorations: vscode.DecorationOptions[] = []; const currentDecorations: vscode.DecorationOptions[] = []; // Look for timestamp patterns const timestampPatterns = [ /\*\*Last Updated\*\*:\s*(.+)/i, /Last Updated:\s*(.+)/i, /lastUpdated:\s*(.+)/i, /last-updated:\s*(.+)/i, ]; lines.forEach((line, lineIndex) => { for (const pattern of timestampPatterns) { const match = line.match(pattern); if (match) { const range = new vscode.Range(lineIndex, 0, lineIndex, line.length); const decoration: vscode.DecorationOptions = { range, hoverMessage: createTooltip(docInfo) }; if (docInfo.status === 'stale') { staleDecorations.push(decoration); } else if (docInfo.status === 'current') { currentDecorations.push(decoration); } break; } } }); editor.setDecorations(staleDecorationType, staleDecorations); editor.setDecorations(currentDecorationType, currentDecorations); } async function checkStaleness() { const editor = vscode.window.activeTextEditor; if (!editor || !editor.document.fileName.endsWith('.md')) { vscode.window.showWarningMessage('Please open a markdown file to check staleness.'); return; } try { const sdsConfig = loadConfig(); const checker = new SDSChecker(sdsConfig); const docInfo = await checker.extractDocumentInfoWithGit(editor.document.fileName); const message = `Document Status: ${docInfo.status}` + (docInfo.staleDays ? ` (${docInfo.staleDays} days old)` : '') + (docInfo.assignedOwners ? `\nOwners: ${docInfo.assignedOwners.join(', ')}` : ''); if (docInfo.status === 'stale') { vscode.window.showWarningMessage(message); } else { vscode.window.showInformationMessage(message); } } catch (error) { vscode.window.showErrorMessage(`SDS Error: ${error}`); } } async function refreshIndicators() { await updateDecorations(); vscode.window.showInformationMessage('SDS indicators refreshed!'); } async function showTeamReport() { try { const sdsConfig = loadConfig(); const checker = new SDSChecker(sdsConfig); const teamReport = await checker.generateTeamReport(); const summary = checker.getTeamSummary(teamReport); // Create a new document with the team report const reportContent = generateTeamReportContent(teamReport, summary); const doc = await vscode.workspace.openTextDocument({ content: reportContent, language: 'markdown' }); await vscode.window.showTextDocument(doc); } catch (error) { vscode.window.showErrorMessage(`SDS Team Report Error: ${error}`); } } function generateTeamReportContent(teamReport: any, summary: any): string { let content = '# SDS Team Documentation Report\n\n'; content += '## Summary\n\n'; content += `- **Total owners**: ${summary.totalOwners}\n`; content += `- **Owners with stale docs**: ${summary.ownersWithStaleDocuments}\n`; content += `- **Average docs per owner**: ${summary.averageDocumentsPerOwner}\n`; content += `- **Ownerless documents**: ${summary.ownerlessDocumentCount}\n\n`; if (Object.keys(teamReport.staleDocumentsByOwner).length > 0) { content += '## Stale Documents by Owner\n\n'; for (const [owner, staleDocs] of Object.entries(teamReport.staleDocumentsByOwner)) { content += `### ${owner}\n\n`; (staleDocs as any[]).forEach(doc => { content += `- ❌ \`${doc.path}\` (${doc.staleDays} days old)\n`; if (doc.priority) { content += ` - Priority: ${doc.priority}\n`; } }); content += '\n'; } } if (teamReport.ownerlessDocuments.length > 0) { content += '## Ownerless Documents\n\n'; teamReport.ownerlessDocuments.forEach((doc: any) => { const statusIcon = doc.status === 'current' ? '✅' : doc.status === 'stale' ? '❌' : '❓'; content += `- ${statusIcon} \`${doc.path}\`\n`; }); content += '\n'; } content += '---\n\n'; content += `*Generated on ${new Date().toLocaleString()}*\n`; return content; } export function deactivate() { if (statusBarItem) { statusBarItem.dispose(); } if (staleDecorationType) { staleDecorationType.dispose(); } if (currentDecorationType) { currentDecorationType.dispose(); } }