smart-documentation-strategy
Version:
AI-powered documentation maintenance with visual staleness indicators, team collaboration, and Git integration
280 lines (230 loc) • 9.74 kB
text/typescript
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();
}
}