/ scripts / add-doc-frontmatter.js
add-doc-frontmatter.js
  1  #!/usr/bin/env node
  2  
  3  /**
  4   * Add YAML frontmatter to documentation files
  5   * Usage: node scripts/add-doc-frontmatter.js <file-path>
  6   */
  7  
  8  import fs from 'fs';
  9  import path from 'path';
 10  
 11  // Category mapping
 12  const CATEGORIES = {
 13    '01-getting-started': 'getting-started',
 14    '02-architecture': 'architecture',
 15    '03-pipeline': 'pipeline',
 16    '04-proposals': 'proposals',
 17    '05-outreach': 'outreach',
 18    '06-automation': 'automation',
 19    '07-integrations': 'integrations',
 20    '08-operations': 'operations',
 21    '09-business': 'business',
 22    '90-archive': 'archive',
 23  };
 24  
 25  // File to title mapping (can be customized)
 26  function fileToTitle(filename) {
 27    return filename
 28      .replace('.md', '')
 29      .split('-')
 30      .map(word => word.charAt(0).toUpperCase() + word.slice(1))
 31      .join(' ');
 32  }
 33  
 34  // Get related files from doc content (basic grep for common patterns)
 35  function getRelatedFiles(content) {
 36    const related = [];
 37    const patterns = [
 38      /src\/[a-zA-Z0-9/_\-.]+\.js/g,
 39      /tests\/[a-zA-Z0-9/_\-.]+\.test\.js/g,
 40      /db\/migrations\/[a-zA-Z0-9_-]+\.sql/g,
 41      /scripts\/[a-zA-Z0-9/_\-.]+\.(js|sh)/g,
 42    ];
 43  
 44    patterns.forEach(pattern => {
 45      const matches = content.match(pattern);
 46      if (matches) {
 47        related.push(...matches);
 48      }
 49    });
 50  
 51    return [...new Set(related)].slice(0, 5); // Limit to 5 unique files
 52  }
 53  
 54  // Extract tags from filename and content
 55  function getTags(filename, content) {
 56    const tags = [];
 57    const lowerContent = content.toLowerCase();
 58  
 59    // Add filename-based tags
 60    const filenameParts = filename.replace('.md', '').split('-');
 61    tags.push(...filenameParts);
 62  
 63    // Add content-based tags
 64    if (lowerContent.includes('cron')) tags.push('cron', 'scheduling');
 65    if (lowerContent.includes('test')) tags.push('testing');
 66    if (lowerContent.includes('security')) tags.push('security');
 67    if (lowerContent.includes('database')) tags.push('database');
 68    if (lowerContent.includes('api')) tags.push('api');
 69    if (lowerContent.includes('ai') || lowerContent.includes('llm')) tags.push('ai', 'llm');
 70    if (lowerContent.includes('email')) tags.push('email');
 71    if (lowerContent.includes('sms')) tags.push('sms');
 72    if (lowerContent.includes('scoring')) tags.push('scoring');
 73    if (lowerContent.includes('proposal')) tags.push('proposals');
 74    if (lowerContent.includes('outreach')) tags.push('outreach');
 75  
 76    return [...new Set(tags)].slice(0, 8); // Limit to 8 unique tags
 77  }
 78  
 79  // Detect replaces from content
 80  function getReplaces(content) {
 81    const replaces = [];
 82    const replaceMatch = content.match(/\*\*Replaces:\*\*\s+([^\n]+)/);
 83    if (replaceMatch) {
 84      const files = replaceMatch[1].split(',').map(f => f.trim());
 85      replaces.push(...files);
 86    }
 87    return replaces;
 88  }
 89  
 90  function addFrontmatter(filePath) {
 91    const content = fs.readFileSync(filePath, 'utf8');
 92  
 93    // Check if frontmatter already exists
 94    if (content.startsWith('---\n')) {
 95      console.log(`Skipping ${filePath} (already has frontmatter)`);
 96      return;
 97    }
 98  
 99    const filename = path.basename(filePath);
100    const dirPath = path.dirname(filePath);
101    const categoryDir = path.basename(dirPath);
102    const category = CATEGORIES[categoryDir] || 'other';
103  
104    const title = fileToTitle(filename);
105    const relatedFiles = getRelatedFiles(content);
106    const tags = getTags(filename, content);
107    const replaces = getReplaces(content);
108  
109    // Build frontmatter
110    const frontmatter = [
111      '---',
112      `title: "${title}"`,
113      `category: "${category}"`,
114      `last_verified: "2026-02-15"`,
115    ];
116  
117    if (relatedFiles.length > 0) {
118      frontmatter.push('related_files:');
119      relatedFiles.forEach(file => {
120        frontmatter.push(`  - "${file}"`);
121      });
122    }
123  
124    if (tags.length > 0) {
125      frontmatter.push(`tags: [${tags.map(t => `"${t}"`).join(', ')}]`);
126    }
127  
128    const status = category === 'archive' ? 'archived' : 'current';
129    frontmatter.push(`status: "${status}"`);
130  
131    if (replaces.length > 0) {
132      frontmatter.push(`replaces: [${replaces.map(r => `"${r}"`).join(', ')}]`);
133    }
134  
135    frontmatter.push('---', '');
136  
137    const newContent = frontmatter.join('\n') + content;
138  
139    fs.writeFileSync(filePath, newContent, 'utf8');
140    console.log(`✓ Added frontmatter to ${filePath}`);
141  }
142  
143  // Main execution
144  const filePath = process.argv[2];
145  if (!filePath) {
146    console.error('Usage: node add-doc-frontmatter.js <file-path>');
147    process.exit(1);
148  }
149  
150  addFrontmatter(filePath);